如何正確地在 Response 加 Header(2)

續昨天,先來列一下 Http Kernel 預設有哪些 middleware:

protected $middleware = [
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];

// 為簡化問題,我們來看 web 的就好
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

但查過原始碼,雖然有的 middleware 有調整過 response 內容,但並沒有任何一個 middleware 去改過 header,理論上是不會受影響的。但我們應該用驗證來證實程式是如我們所預期的,因此跟朋友確認程式內容,並實際做了一點實驗。

測試驗證

首先得知版本為 Laravel v5.7.0,我們先打開專案,來寫個 Feature 測試如下:

public function testBasicTest()
{
$routeMiddleware = function ($request, $next) {
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
};

/** @var Router $router */
$router = $this->app->make('router');
$router->middleware(['web', $routeMiddleware])->get('/foo', function () {
return '';
});

$this->get('/foo')
->assertStatus(200)
->assertHeader('Access-Control-Allow-Origin', '*')
->assertHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
}

對於設定 Router 的方法有疑問的話,可以參考之前分析 Routing 文章。

這個測試是通過的,但畢竟這只是程式上運行,跟啟用 web 服務或許會有落差,所以實際照著朋友的做法做一次:

建立 Cors middleware:

<?php

namespace App\Http\Middleware;

class Cors
{
public function handle($request, $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
}
}

Kernel 加上 alias:

protected $routeMiddleware = [
// 略
'cors' => \App\Http\Middleware\Cors::class,
];

routes/api.php 加上測試用 route:

Route::middleware(['api', 'cors'])->group(function() {
Route::get('/foo', function () {
return '';
});
});

接著使用 Artisan 指令啟動服務:

php artisan serve

然後打開 http://localhost/api/foo 即可看到剛剛建立的測試 route。

驗證一切正常,該有的 header 是有的,所以可以推測是使用或測試的方法出錯導致錯誤的。


這篇記錄並不是責怪朋友,主要是想分享:這是從發現問題,到確認問題在或不在某個範圍內的過程。可以看到我們一開始從原始碼開始說明,但原始碼畢竟只是紙上談兵,所以也使用單元測試驗證,也做了實際整合程式的驗證。

開發程式的過程中,也會遇到許多奇怪的問題,我們可以像一開始一樣,直接翻原始碼確認,但時間容易拖很長,而且最後的結果依然不是可靠的;或許直接寫單元測試會是個可行的做法,寫測試即可馬上驗證想法是否正確,而且真的是一翻兩瞪眼啊!