路由
路由(routing)就是通過(guò)互聯(lián)的網(wǎng)絡(luò)把信息從源地址傳輸?shù)侥康牡刂返幕顒?dòng)事扭。路由發(fā)生在OSI網(wǎng)絡(luò)參考模型中的第三層即網(wǎng)絡(luò)層讶舰。
基礎(chǔ)路由
一般所有的路由都被定義在app/Http/routes.php
文件中何缓,其中的內(nèi)容會(huì)被框架自動(dòng)加載普筹,大多數(shù)的基礎(chǔ)路由都可以通過(guò)簡(jiǎn)單的傳遞資源表述地址和Closure
閉包函數(shù)來(lái)進(jìn)行定義恤磷。
Route::get('foo', function () {
return 'Hello world!';
});
基礎(chǔ)路由文件
routes.php
路由文件是在 app\Providers\RouteServiceProvider.php
中被加載的面哼,在被加載的同時(shí)使用了web
中間件組(version>=5.2), 這個(gè)中間件組被定義在app\Http\kernel.php
的$middlewareGroups
變量中,該中間件組提供了cookie加密扫步、cookie響應(yīng)魔策、session啟用、自動(dòng)注入error session 到視圖河胎、csrf保護(hù)的功能闯袒。你的應(yīng)用程序的大多數(shù)路由都應(yīng)該定義在這個(gè)路由文件里。
可用的路由方法
路由允許你在注冊(cè)路由時(shí)使用任何http請(qǐng)求方法:
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
有時(shí)候你可能需要在注冊(cè)路由時(shí)允許多種請(qǐng)求方式游岳,這個(gè)時(shí)候你可能需要使用method
方法政敢,當(dāng)然你可以使用any
方法允許任何請(qǐng)求方式:
Route::match(['get', 'post'], function () {
//
});
Route::any('foo', function () {
//
})
帶參數(shù)的路由
必要參數(shù)的路由
有時(shí)候你可能需要捕獲路由中的某個(gè)片段,比如說(shuō)用戶的id, 那么你就可以定義帶參數(shù)的路由:
Route::get('user/{id}', function ($id) {
return 'user_id is: ' . $id;
});
可能你需要獲取到路由中的多個(gè)參數(shù), 那么你可以這么做:
Route::get('posts/{post}/comments/{comment}', function ($post, $comment) {
//
});
路由參數(shù)就是定義在{}
里的胚迫,每當(dāng)該路由被訪問(wèn)到喷户,相關(guān)注冊(cè)的參數(shù)就會(huì)被按序的傳遞進(jìn)閉包函數(shù)里.
ps: 路由參數(shù)不能含 **-** ,應(yīng)該使用 **_** 代替访锻,比如 {user_id}
可選的參數(shù)的路由
有時(shí)候你可能需要定義一個(gè)帶參數(shù)的路由褪尝,但是這個(gè)參數(shù)可有可無(wú),沒(méi)有的話期犬,可以給予一個(gè)默認(rèn)值河哑,那么這時(shí)候就需要用 {name?}
的方式來(lái)定義可選路由了:
Route::get('user/{name?}', function ($name = null) {
return $name;
});
Route::get('user/{name?}', function ($name = 'john'){
return $name;
});
用正則表達(dá)式約束參數(shù)
你可以使用正則表達(dá)式去約束你的參數(shù),這里你可以使用where
方法龟虎,當(dāng)參數(shù)與約束規(guī)則不匹配時(shí)就不會(huì)匹配到該路由:
Route::get('user/{name}', function ($name){
//
})
->where('name', '[a-zA-Z]+');
Route::get('user/{id}', function ($id){
//
})
->where('id', '[0-9]+');
Route::get('user/{id}/{name}', function ($id, $name){
//
})
->where(['id' => '[0-9]+', 'name' => '[a-zA-Z]+');
全局的約束表達(dá)式
你可以定義全局的約束表達(dá)式璃谨,這樣所有匹配到的路由參數(shù)都會(huì)受到相同的約束條件, 這個(gè)時(shí)候你需要在路由啟動(dòng)之前就有注冊(cè)好約束,你需要在路由服務(wù)提供者文件里在啟動(dòng)路由前增加約束。文件是app\Providers\RouteServiceProvider.php
public function boot(Router $router) {
$router->pattern('id', '[0-9]+');
parent::boot($router);
}
當(dāng)然佳吞,在5.2版本里拱雏,路由服務(wù)提供者在引入router.php
文件時(shí)已經(jīng)注入了$router
變量,所以你也可以在router.php
文件里這么提供批量約束表達(dá)式 :
$router->pattern('id','[0-9]+');
Route::get('user/{id}', function ($id) {
//
});
但是要注意注冊(cè)的前后順序容达,只有在注冊(cè)之后引入的路由才受約束古涧,而在 $router-pattern('id', '[0-9]+')
之前注冊(cè)的路由是不受約束的.
命名路由
命名路由可以方便的生成資源表述地址,可以方便的將路由重定向到指定的路由地址花盐。你可以在注冊(cè)路由時(shí)羡滑,傳遞第二個(gè)參數(shù)為數(shù)組參數(shù),并添加as
索引:
Route::get('user/profile', ['as' => 'profile', function () {
//
}]);
當(dāng)然你也可以命名使用控制器行為的路由:
Route::get('user/profile', ['as' => 'profile', 'use' => 'UserController@showProfile']);
有時(shí)候數(shù)組寫起來(lái)比較麻煩算芯,我們還可以使用name
方法去命名路由柒昏,當(dāng)然它是支持鏈?zhǔn)秸{(diào)用的:
Route::get('user/profile', 'UserController@showProfile')->name('profile');
路由組 & 命名組路由
如果你使用了路由組,你可能想在整個(gè)組中增加一個(gè)組命名前綴熙揍,當(dāng)然职祷,laravel是支持這么做的:
Route::group(['as' => 'admin::'], function () {
Route::get('dashboard', ['as' => 'dashboard', function () {
// route named 'admin::dashboard'
// you can redirect to this use route('admin::dashboard')
}]) ;
});
使用命名路由生成urls
一旦你命名了一個(gè)路由,那么你就可以使用全局幫助函數(shù)route
來(lái)生成路由url地址届囚,也可以用來(lái)做為重定向時(shí)生成重定向地址:
// Generating URLs...
$url = route('profile');
// Generating Redirects...
return redirect()->route('profile');
如果你是對(duì)一個(gè)參數(shù)路由進(jìn)行命名有梆,那么你可以在使用route
方法獲取路由url時(shí)傳遞第二個(gè)參數(shù),該參數(shù)是一個(gè)數(shù)組意系,你所傳遞的參數(shù)會(huì)正確的按索引匹配到相應(yīng)的位置,如果參數(shù)索引與路由參數(shù)不一致泥耀,則會(huì)優(yōu)先匹配相同的參數(shù)存放到相應(yīng)位置,不一致的會(huì)按序存入:
Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
//
}]);
$url = route('profile', ['id' => 1]);
路由組
路由組的使用方便共享路由的一些能力蛔添,比如共享中間件痰催,或者命名空間,這樣就不必每一個(gè)路由都要單獨(dú)去定義一次相同的中間件或命名空間迎瞧。路由組使用 Route::group
去定義夸溶,并共享第一個(gè)參數(shù)中的能力.該參數(shù)為數(shù)組.
共享中間件
你可以使用middleware
索引來(lái)建立共享中間件,這樣路由組內(nèi)定義的路由都會(huì)在匹配時(shí)引入該中間件:
Route::group(['middleware' => 'auth'], function () {
Route::get('/', function () {
// Uses Auth Middleware
});
Route::get('user/profile', function () {
// Uses Auth Middleware
});
});
共享命名空間
路由組另外一個(gè)常用的例子就是在控制器中共享命名空間,這個(gè)時(shí)候就要用到了namespace
索引:
Route::group(['namespace' => 'Admin'], function () {
// Controllers Within The 'App\Http\Controllers\Admin' Namespace
Route::group(['namespace' => 'User'], function () {
// Controllers Within The 'App\Http\Controllers\Admin\User' Namespace
});
});
因?yàn)槲覀兇蟛糠致酚啥际嵌x在routes.php
文件里凶硅,而路由服務(wù)在提供服務(wù)時(shí)將該文件引入到路由組中,并共享了App\Http\Controllers
命名空間缝裁,這樣,在該文件中定義的控制器路由足绅,可以不使用App\Http\Controllers
前綴.
// Already Within The 'App\Http\Controllers' Namespace
Route::get('user/profile', 'UserController@showProfile');
子域名路由
子域名路由可以做域名匹配路由捷绑,也就是說(shuō)可以約束域名做匹配,如果不是匹配到的域名則不會(huì)注冊(cè)該組內(nèi)路由编检, 當(dāng)然你可以對(duì)子域名進(jìn)行類參數(shù)路由的約束,這樣允許你捕獲域名的一部分或全部:
Route::group(['domain' => '{account}.myapp.com'], function () {
Route::get('user/{id}', function ($account, $id) {
//
});
});
路由前綴
使用prefix
索引可以在路由組中定義路由前綴扰才,這樣在該組內(nèi)的路由都會(huì)自動(dòng)使用該前綴:
Route::group(['prefix' => 'admin'], function () {
Route::get('dashboard', function () {
// Matches The 'admin/dashboard' URL
});
});
當(dāng)然你可以在路由前綴中使用參數(shù):
Route::group(['prefix' => 'accounts/{account_id}'], function () {
Route::get('detail', function ($account_id) {
// Matchs The '/accounts/{account_id}/detail' URL
});
});
CSRF 保護(hù)
跨站請(qǐng)求偽造(英語(yǔ):Cross-site request forgery)允懂,也被稱為 one-click attack 或者 session riding,通绸孟唬縮寫為 CSRF 或者 XSRF蕾总, 是一種挾制用戶在當(dāng)前已登錄的Web應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法粥航。跟跨網(wǎng)站腳本(XSS)相比,XSS 利用的是用戶對(duì)指定網(wǎng)站的信任生百,CSRF 利用的是網(wǎng)站對(duì)用戶網(wǎng)頁(yè)瀏覽器的信任递雀。
跨站請(qǐng)求攻擊,簡(jiǎn)單地說(shuō)蚀浆,是攻擊者通過(guò)一些技術(shù)手段欺騙用戶的瀏覽器去訪問(wèn)一個(gè)自己曾經(jīng)認(rèn)證過(guò)的網(wǎng)站并執(zhí)行一些操作(如發(fā)郵件缀程,發(fā)消息,甚至財(cái)產(chǎn)操作如轉(zhuǎn)賬和購(gòu)買商品)市俊。由于瀏覽器曾經(jīng)認(rèn)證過(guò)杨凑,所以被訪問(wèn)的網(wǎng)站會(huì)認(rèn)為是真正的用戶操作而去執(zhí)行。這利用了web中用戶身份驗(yàn)證的一個(gè)漏洞:簡(jiǎn)單的身份驗(yàn)證只能保證請(qǐng)求發(fā)自某個(gè)用戶的瀏覽器摆昧,卻不能保證請(qǐng)求本身是用戶自愿發(fā)出的撩满。
laravel框架自帶csrf攻擊保護(hù)措施,它被以中間件的形式引入到web
路由組中绅你,所以每個(gè)在routes.php
文件中定義的路由被訪問(wèn)時(shí)都會(huì)經(jīng)過(guò)csrf中間件的驗(yàn)證伺帘。你可以查看vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php
文件,閱讀其實(shí)現(xiàn)忌锯。
csrf(跨站請(qǐng)求偽造)
是一種惡意的攻擊行為伪嫁,它可以繞過(guò)用戶的授權(quán)去執(zhí)行未授權(quán)的行為。
laravel會(huì)為每個(gè)活躍用戶生成一個(gè)csrf token,而這個(gè)token就是用來(lái)驗(yàn)證請(qǐng)求是不是真實(shí)用戶授權(quán)的汉规。所以你應(yīng)該在每個(gè)表單中都使用隱藏的input并錄入csrf token,這樣提交表單的請(qǐng)求才能通過(guò)csrf中間件的驗(yàn)證.
laravel提供了方便的形式去在表單中生成csrf token:
// Vanilla PHP
<?php echo csrf_field(); ?>
// Blade Template Syntax
{{ csrf_field() }}
csrf_field()
方法會(huì)生成以下html節(jié)點(diǎn):
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
你并不需要去手動(dòng)的驗(yàn)證這些能夠修改狀態(tài)的http請(qǐng)求礼殊,比如 post, put, delete, 因?yàn)閂erifyCsrfToken中間件已經(jīng)自動(dòng)處理這些請(qǐng)求了,它會(huì)自動(dòng)的根據(jù)請(qǐng)求中的token與會(huì)話中存儲(chǔ)的token進(jìn)行匹配驗(yàn)證针史。
排除csrf保護(hù)
有時(shí)候我們可能需要排除個(gè)別url晶伦,讓他不受csrf的保護(hù),因?yàn)榭赡芪覀冃枰獟旖右恍┢渌牡谌较到y(tǒng)啄枕,這個(gè)時(shí)候系統(tǒng)之間的通訊是不應(yīng)該使用csrf機(jī)制去驗(yàn)證的婚陪,比如說(shuō),你使用了支付寶的即時(shí)付款機(jī)制频祝,那么在用戶付款的時(shí)候服務(wù)器與服務(wù)器之間會(huì)有一個(gè)回饋機(jī)制泌参,用于支付寶通知我們的應(yīng)用用戶已經(jīng)成功付款,這個(gè)時(shí)候我們可能需要排除csrf對(duì)反饋路由的保護(hù)常空。那么我們可以在VerifyCsrfToken
中間件中修改$except
屬性中增加一條規(guī)則:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'alipay/*',
];
}
X-CSRF-TOKEN
laravel的VerifyCsrfToken
中間件不僅允許通過(guò)表單中驗(yàn)證_token參數(shù)沽一,它也允許通過(guò)請(qǐng)求頭去進(jìn)行csrf驗(yàn)證,它會(huì)驗(yàn)證請(qǐng)求頭中的X-CSRF-TOKEN
值是否與會(huì)話的token值匹配漓糙,所以你可以這么做铣缠,存儲(chǔ)token到meta
中:
<meta name="csrf-token" content="{{ csrf_token() }}" >
一旦你定義了meta
標(biāo)簽,你可以使用像jquery
類似的框架去添加token到所有請(qǐng)求的頭部:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
})
X-XSRF-TOKEN
laravel自動(dòng)存儲(chǔ)了CSRF token 到 cookie中并被命名為 XSRF-TOKEN
, 你可以在每次請(qǐng)求的請(qǐng)求頭里引入X-XSRF-TOKEN
并將其值設(shè)置為 XSRF-TOKEN
的cookie值來(lái)通過(guò)csrf驗(yàn)證。有一些javascript框架里已經(jīng)自動(dòng)做了這些蝗蛙,像 angularjs. 如果你沒(méi)有引入這些前端框架蝇庭,你可能需要自己手動(dòng)去做了:
fetch('/user/password', {
method: 'POST',
headers: {
'X-XSRF-TOKEN':
}
})
綁定模型的路由
綁定模型的路由提供了一種便利的方式去注入模型實(shí)例到路由中,比如捡硅,你可以通過(guò)傳遞id哮内,來(lái)注入匹配id的用戶的整個(gè)模型到路由中。
隱式綁定
laravel會(huì)自動(dòng)的解析定義在路由或者控制器中的Eloquent
模型, 根據(jù)變量名與參數(shù)名匹配的壮韭,比如api/users/{user}
匹配 $user
變量:
Route::get('api/users/{user}', function (App\User $user) {
return $user->email;
});
在上面的例子中北发,$user是根據(jù)url中{user}
片段的值進(jìn)行實(shí)例生成的.這種綁定模型到路由一般都是默認(rèn)根據(jù)id進(jìn)行匹配注入實(shí)例。比如說(shuō) users/1
泰涂,匹配到的就是id = 1的用戶實(shí)例鲫竞。如果沒(méi)有匹配的實(shí)例,則會(huì)返回404逼蒙。
自定義索引綁定模型到路由
如果你不想用id去綁定模型到路由从绘,laravel提供了一種可自定義的方式,你只需要在相應(yīng)的模型里重寫getRouteKeyName
方法就行了是牢,比如我們用名字來(lái)代替id注入模型到路由:
public function getRouteKeyName() {
return 'name';
}
這樣在路由url中就是類似這種形式了 users/wang
顯式綁定
如果需要注冊(cè)一種顯式的模型綁定到路由僵井,你需要使路由的model
方法去指定類與參數(shù)的映射。并且你應(yīng)該在路由服務(wù)的boot
方法中進(jìn)行綁定注冊(cè)操作:
綁定到模型
public function boot(Router $router) {
parent::boot($router);
$router->model('user', 'App\User');
}
這樣驳棱,在routes.php
文件中注冊(cè)的路由如果使用了{user}
的命名路由就可以自動(dòng)綁定到模型:
$router->get('profile/{user}', function (App\User $user) {
//
});
自定義綁定解析邏輯
如果你想自定義解析返回實(shí)例的邏輯批什,那么你需要使用Route::bind
方法,該方法第一個(gè)參數(shù)為綁定url的參數(shù)名社搅,第二個(gè)參數(shù)為Closure
, 閉包中會(huì)接收一個(gè)參數(shù)驻债,這個(gè)參數(shù)是url中對(duì)應(yīng)參數(shù)片段的值,你應(yīng)該在閉包中返回解析后的實(shí)例:
$router->bind('user', function ($value) {
return App\User::where('name', $value)->first();
});
自定義找不到實(shí)例時(shí)的行為
如果你想要自定義無(wú)法匹配實(shí)例時(shí)的行為形葬,你可以向model
方法傳遞第三個(gè)參數(shù)合呐,該參數(shù)是一個(gè)閉包函數(shù),它將在無(wú)法匹配到實(shí)例時(shí)執(zhí)行:
$router->model('user', 'App\User', function () {
throw new NotFoundHttpException;
});
表單提交方式模擬
由于HTML的表單不能支持像PUT
,PATCH
或者DELETE
的請(qǐng)求方式笙以,所以淌实,當(dāng)需要訪問(wèn)類似的路由時(shí),你需要在表單中增加一個(gè)隱藏的字段_method
猖腕,用來(lái)表明你真實(shí)的表單請(qǐng)求方式:
<form action=“/foo/bar" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
laravel也提供了便捷生成隱藏字段_method
的方法拆祈,你可以使用method_field
幫助方法:
<?php echo method_field(); ?>
使用blade模板引擎可以直接這么用:
{{ method_field('PUT') }}
訪問(wèn)當(dāng)前路由
Route::current()
會(huì)返回一個(gè)Illuminate\Routing\Route
的實(shí)例,它允許你在當(dāng)前路由內(nèi)處理相關(guān)請(qǐng)求:
$route = Route::current();
$name = $route->getName();
$actionName = $route->getActionName();
當(dāng)然你也可以通過(guò)Route
facade去訪問(wèn)路由的name或action:
$name = Route::currentRouteName();
$action = Route::currentRouteAction();