cors的機制是,當跨網域透過ajax要求資料時,瀏覽器會先向來源網站詢問是否允許跨網域要求資料,此為prelight request,只回傳相關headers,不輸出任何內容。
如果來源網站有回應cors相關header時,並且ajax request符合headers規範的條件,瀏覽器就會放行連線。若來源網站沒有回應cors相關headers時,視為不允許跨網域連線。
這限制的執行是由瀏覽器端來負責,若瀏覽器不實作cors機制,就算來源網站有正常回覆 cors headers也不會有效果。
所以瀏覽器在實作ajax request時,實際會有兩個request,一個是預先詢問的options request (prelight request),後面才是實際的request。
當你實作 yii\rest\Controller 或 yii\rest\ActiveController時,yii2官方建議 authenticator filter 需移到 cors filter 之後,實際上 verb filter 可能也需要移到 cors filter 之後,如下段敘述所示。
public function behaviors()
{
$behaviors = parent::behaviors();
// remove authentication filter
$auth = $behaviors['authenticator'];
unset($behaviors['authenticator']);
// remove verb filter
$verb = $behaviors['verbFilter'];
unset($behaviors['verbFilter']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::class,
];
// re-add verb filter
$behaviors['verbFilter'] = $verb;
// re-add authentication filter
$behaviors['authenticator'] = $auth;
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
因為基礎類別的behaviors並不包括cors filter,因此在繼承類別設定cors filters時,其執行順序會晚於verb filter,而按預設verb filter設定,options是會被擋掉的。
/**
* {@inheritdoc}
*/
public function behaviors()
{
return [
'contentNegotiator' => [
'class' => ContentNegotiator::className(),
'formats' => [
'application/json' => Response::FORMAT_JSON,
'application/xml' => Response::FORMAT_XML,
],
],
'verbFilter' => [
'class' => VerbFilter::className(),
'actions' => $this->verbs(),
],
'authenticator' => [
'class' => CompositeAuth::className(),
],
'rateLimiter' => [
'class' => RateLimiter::className(),
],
];
}
基礎類別的behaviors
/**
* {@inheritdoc}
*/
protected function verbs()
{
return [
'index' => ['GET', 'HEAD'],
'view' => ['GET', 'HEAD'],
'create' => ['POST'],
'update' => ['PUT', 'PATCH'],
'delete' => ['DELETE'],
];
}
預設verb設定。
這裡有兩個作法,一個即是前述將 verb filter 移到 cors filter 之後。
另外一個就是在verb設定裡,將會產生options prelight request的method裡再加上options,實測會產生options的method應該就是非get、post的method,因此若採用第二種方法,put、 patch、 delete等method都要再加上options,才能通過verb filter。參閱簡易請求。
protected function verbs()
{
return [
'index' => ['GET', 'HEAD'],
'view' => ['GET', 'HEAD'],
'create' => ['POST'],
'update' => ['PUT', 'PATCH','OPTIONS'],
'delete' => ['DELETE','OPTIONS'],
];
}
另外,cors filter預設設定會讓ajax處理回傳值時無法取得所有headers。下列圖示由左至右,分別是chrome實際記錄的ajax response headers, ajax呼叫敘述以及敘述裡取得的所有headers輸出。看得出來ajax回傳結果能處理的headers少於實際的headers。
這時可以針對指定action再覆寫預設cors設定,如下段敘述所示。這樣 Access-Control-Expose-Headers 裡指定的header就會出現在 xhr的getAllResponseHeaders裡面了。
// add CORS filter
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::class,
//覆寫預設的 Access-Control-Expose-Headers
'actions' => [
'index' => [
'Access-Control-Expose-Headers' => [
'X-Pagination-Current-Page',
'X-Pagination-Page-Count',
'X-Pagination-Per-Page',
'X-Pagination-Total-Count',
],
],
],
];