HTTP 路由


1、基本路由

大部分路由都定义在被App\Providers\RouteServiceProvider类载入的app/Http/routes.php文件中。

最基本的Laravel路由接收一个URI和一个闭包:

  1. Route::get('/', function () {
  2. return 'Hello World';
  3. });
  4. Route::post('foo/bar', function () {
  5. return 'Hello World';
  6. });
  7. Route::put('foo/bar', function () {
  8. //
  9. });
  10. Route::delete('foo/bar', function () {
  11. //
  12. });

为多个动作注册路由

有时候需要注册一个路由来响应多个不同的HTTP动作,你可以使用Route门面的match方法来实现:

  1. Route::match(['get', 'post'], '/', function () {
  2. return 'Hello World';
  3. });

或者,还可以使用any方法注册一个路由响应所有HTTP动作:

  1. Route::any('foo', function () {
  2. return 'Hello World';
  3. });

生成路由对应的URLs

可以使用帮助函数url来生成路由对应的URLs:

  1. $url = url('foo');

2、路由参数

2.1 必选参数

有时我们需要在路由中捕获URI片段,比如,如果想要从URL中捕获用户ID,可以通过如下方式定义路由参数:

  1. Route::get('user/{id}', function ($id) {
  2. return 'User '.$id;
  3. });

可以按需要定义在路由中定义多个路由参数:

  1. Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
  2. //
  3. });

路由参数总是通过花括号进行包裹,参数在路由被执行时会被传递到路由的闭包。

注意:路由参数不能包含’-‘字符,需要的话可以使用_替代。

2.2 可选参数

有时候可能需要指定路由参数,并且使得该路由参数是可选的,可以通过在参数名后加一个?来标记:

  1. Route::get('user/{name?}', function ($name = null) {
  2. return $name;
  3. });
  4. Route::get('user/{name?}', function ($name = 'John') {
  5. return $name;
  6. });

2.3 正则约束

可以使用路由实例上的where方法来约束路由参数的格式。where方法接收参数名和一个正则表达式来定义该参数如何被约束:

  1. Route::get('user/{name}', function ($name) {
  2. //
  3. })->where('name', '[A-Za-z]+');
  4. Route::get('user/{id}', function ($id) {
  5. //
  6. })->where('id', '[0-9]+');
  7. Route::get('user/{id}/{name}', function ($id, $name) {
  8. //
  9. })->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
2.3.1 全局约束

如果想要路由参数在全局范围内被给定正则表达式约束,可以使用pattern方法。可以在RouteServiceProvider类的boot方法中定义约束模式:

  1. /**
  2. * 定义路由模型绑定,模式过滤器等
  3. *
  4. * @param \Illuminate\Routing\Router $router
  5. * @return void
  6. * @translator http://laravelacademy.org
  7. */
  8. public function boot(Router $router){
  9. $router->pattern('id', '[0-9]+');
  10. parent::boot($router);
  11. }

一旦模式被定义,将会自动应用到所有包含该参数名的路由中。

扩展阅读:实例教程——HTTP路由实例教程(一)—— 基本使用及路由参数

3、命名路由

命名路由使生成URLs或者重定向到指定路由变得很方便,在定义路由时指定路由名称,然后使用数组键as指定路由别名:

  1. Route::get('user/profile', ['as' => 'profile', function () {
  2. //
  3. }]);

还可以为控制器动作指定路由名称:

  1. Route::get('user/profile', [
  2. 'as' => 'profile', 'uses' => 'UserController@showProfile'
  3. ]);

3.1 路由分组 & 命名路由

如果你在使用路由分组,可以在路由分组属性数组中指定as关键字来为分组中的路由设置一个共用的路由名前缀:

  1. Route::group(['as' => 'admin::'], function () {
  2. Route::get('dashboard', ['as' => 'dashboard', function () {
  3. // 路由被命名为 "admin::dashboard"
  4. }]);
  5. });

3.2 为命名路由生成URLs

一旦你为给定路由分配了名字,通过route函数生成URLs时就可以使用路由名字:

  1. $url = route('profile');
  2. $redirect = redirect()->route('profile');

如果路由定义了参数,可以将路由参数作为第二个参数传递给route函数。给定的路由参数将会自动插入URL中:

  1. Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
  2. //
  3. }]);
  4. $url = route('profile', ['id' => 1]);

4、路由分组

路由分组允许我们在多个路由中共享路由属性,比如中间件和命名空间等,这样的话一大波共享属性的路由就不必再各自定义这些属性。共享属性以数组的形式被作为第一个参数传递到Route::group方法中。

想要了解更多路由分组,我们希望通过几个简单的应用实例来展示其特性。

4.1 中间件

要分配中间件给分组中的所有路由,可以在分组属性数组中使用middleware键。中间件将会按照数组中定义的顺序依次执行:

  1. Route::group(['middleware' => 'auth'], function () {
  2. Route::get('/', function () {
  3. // 使用 Auth 中间件
  4. });
  5. Route::get('user/profile', function () {
  6. // 使用 Auth 中间件
  7. });
  8. });

4.2 命名空间

另一个通用的例子是路由分组分配同一个PHP命名空间给多个控制器,可以在分组属性数组中使用namespace参数来指定分组中控制器的命名空间:

  1. Route::group(['namespace' => 'Admin'], function(){
  2. // 控制器在 "App\Http\Controllers\Admin" 命名空间下
  3. Route::group(['namespace' => 'User'], function()
  4. {
  5. // 控制器在 "App\Http\Controllers\Admin\User" 命名空间下
  6. });
  7. });

默认情况下,RouteServiceProvider包含routes.php并指定其所在命名空间,因此,我们只需要指定命名空间的App\Http\Controllers之后的一部分。

4.3 子域名路由

路由分组还可以被用于子域名路由通配符,子域名可以像URIs一样被分配给路由参数,从而允许捕获子域名的部分用于路由或者控制器,子域名可以通过分组属性数组中的domain键来指定:

  1. Route::group(['domain' => '{account}.myapp.com'], function () {
  2. Route::get('user/{id}', function ($account, $id) {
  3. //
  4. });
  5. });

4.4 路由前缀

属性prefix可以用来为分组中每个给定URI添加一个前缀,比如,你想要为所有路由URIs前面添加前缀admin

  1. Route::group(['prefix' => 'admin'], function () {
  2. Route::get('users', function () {
  3. // 匹配 "/admin/users" URL
  4. });
  5. });

你还可以使用prefix参数为分组路由指定公共参数:

  1. Route::group(['prefix' => 'accounts/{account_id}'], function () {
  2. Route::get('detail', function ($account_id) {
  3. // 匹配 accounts/{account_id}/detail URL
  4. });
  5. });

扩展阅读:实例教程——HTTP路由实例教程(二)—— 路由命名和路由分组

5、防止CSRF攻击

5.1 简介

Laravel使得防止应用遭到跨站请求伪造攻击变得简单。跨站请求伪造是一种通过伪装授权用户的请求来利用授信网站的恶意漏洞。

Laravel自动为每一个被应用管理的有效用户Session生成一个CSRF“令牌”,该令牌用于验证授权用户和发起请求者是否是同一个人。想要生成包含CSRF令牌的隐藏输入字段,可以使用帮助函数csrf_field来实现:

  1. <?php echo csrf_field(); ?>

帮助函数csrf_field生成如下HTML:

  1. <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

当然还可以使用Blade模板引擎提供的方式:

  1. {!! csrf_field() !!}

你不需要了解在POST、PUT或者DELETE请求时CSRF令牌是如何进行验证的,HTTP中间件VerifyCsrfToken会为我们做这项工作:将请求中输入的token值和session中的存储的作对比。

5.2 从CSRF保护中排除URIs

有时候我们想要从CSRF保护中排除一些URIs,比如,如果你在使用Stripe来处理支付并用到他们的webhook系统,这时候你就需要从Laravel的CSRF保护中排除webhook处理器路由。

你可以通过在VerifyCsrfToken中间件中将要排除的URIs添加到$except属性:

  1. <?php
  2. namespace App\Http\Middleware;
  3. use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
  4. class VerifyCsrfToken extends BaseVerifier
  5. {
  6. /**
  7. *从CSRF验证中排除的URL
  8. *
  9. * @var array
  10. */
  11. protected $except = [
  12. 'stripe/*',
  13. ];
  14. }

5.3 X-CSRF-Token

除了将CSRF令牌作为一个POST参数进行检查,Laravel的VerifyCsrfToken中间件还会检查X-CSRF-TOKEN请求头,你可以将令牌保存在”meta”标签中:

  1. <meta name="csrf-token" content="{{ csrf_token() }}">

创建完这个meta标签后,就可以在js库如jQuery中添加该令牌到所有请求头,这为基于AJAX的应用提供了简单、方便的方式来避免CSRF攻击:

  1. $.ajaxSetup({
  2. headers: {
  3. 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
  4. }
  5. });

5.4 X-XSRF-Token

Laravel还将CSRF令牌保存到了名为XSRF-TOKEN的cookie中,你可以使用该cookie值来设置X-XSRF-TOKEN请求头。一些JavaScript框架,比如Angular,将会为你自动进行设置,基本上你不太会手动设置这个值。

扩展阅读:实例教程——HTTP路由实例教程(三)—— CSRF攻击原理及其防护

6、路由模型绑定

Laravel路由模型绑定为注入类实例到路由提供了方便,例如,你可以将匹配给定ID的整个User类实例注入到路由中,而不是直接注入用户ID。
首先,使用路由的model方法为给定参数指定一个类,你应该在RouteServiceProvider::boot方法中定义模型绑定:

绑定参数到模型

  1. public function boot(Router $router)
  2. {
  3. parent::boot($router);
  4. $router->model('user', 'App\User');
  5. }

接下来,定义一个包含{user}参数的路由:

  1. $router->get('profile/{user}', function(App\User $user) {
  2. //
  3. });

由于我们已经绑定了{user}参数到App\User模型,一个User实例将会被注入到路由中。也就是说,如果请求URL是profile/1,那么相应的将会注入ID为1的User实例到路由中。

注:如果在匹配模型实例的时候在数据库中找不到对应记录,那么就会自动抛出404异常。

如果你想要指定自己的“not found”行为,可以传递一个闭包作为第三个参数到model方法:

  1. $router->model('user', 'App\User', function() {
  2. throw new NotFoundHttpException;
  3. });

如果你想要使用自己的路由模型绑定解决方案,应该使用Route::bind方法,这样的话传递到bind方法的闭包将会接受URI中的参数值,然后返回你想要注入到路由的类实例:

  1. $router->bind('user', function($value) {
  2. return App\User::where('name', $value)->first();
  3. });

7、表单方法伪造

HTML表单不支持PUTPATCH或者DELETE动作,因此,当定义被HTML表单调用的PUTPATCHDELETE路由时,需要添加一个隐藏的_method字段到给表单中,其值被用作HTTP请求方法名:

  1. <form action="/foo/bar" method="POST">
  2. <input type="hidden" name="_method" value="PUT">
  3. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  4. </form>

8、抛出404错误

有两者方法手动从路由触发404错误。

第一种,使用帮助函数abortabort函数会抛出一个指定状态码的Symfony\Component\HttpFoundation\Exception\HttpException

  1. abort(404);

第二种,手动抛出Symfony\Component\HttpKernel\Exception\NotFoundHttpException.的实例。

更多关于处理404异常的信息以及如何自定义视图显示这些错误信息,请查看错误文档一节。