場景描述
比如我們需要對 API 限流拋出的異常進行接管,並重寫響應消息,首先應用中間件:
use Dingo\Api\Routing\Router;
$api->group([
'middleware' => 'api.throttle', // 限流中間件
'expires' => 1, // 時間範圍,單位“分”
'limit' => 2, // 時間範圍內請求次數
], function (Router $api) {
$api->post('auth/login', 'LoginController@login');
});
使用 Postman 進行接口調試,我們會發現在正常請求階段會多出三個響應頭:
X-RateLimit-Limit # 時間範圍內可請求次數
X-RateLimit-Remaining # 時間範圍內剩餘可請求次數
X-RateLimit-Reset # 到期時間戳
繼續重複請求兩次後會得到類似如下結果(修改過):
{
"message": "You have exceeded your rate limit.",
"result": 0,
"status_code": 429
}
異常接管
這裏有兩種接管方式
-
單一異常接管:
建議在
App\Providers\AppServiceProvider
文件中的register()
方法內進行編寫:$this->app->make(Dingo\Api\Exception\Handler::class)->register(function (RateLimitExceededException $e) { return response([ 'message' => '當前請求太過頻繁', 'result' => 0, 'status_code' => 429 ])->setStatusCode($e->getStatusCode())->withHeaders($e->getHeaders()); });
如果無需使用狀態碼,可去掉 setStatusCode 方法,僅保留 withHeaders 即可。去掉後 HTTP 狀態碼響應爲 200。
-
多異常接管:
顧名思義,單一異常接管僅適用於單一的服務場景,而 Dingo API 提供了多項服務,如果應用多項時,上述方式就不適用了。
首先,我們在
app/Exceptions
目錄內創建名爲DingoExceptionHandler
的類文件,同樣我們以限流異常示例,內容如下:<?php namespace App\Exceptions; use Dingo\Api\Contract\Debug\ExceptionHandler; use Dingo\Api\Exception\Handler as DingoHandler; use Dingo\Api\Exception\RateLimitExceededException; use Exception; class DingoExceptionHandler extends DingoHandler implements ExceptionHandler { public function handle(Exception $exception) { if ($exception instanceof RateLimitExceededException) { return response([ 'message' => '當前請求太過頻繁', 'result' => 0, 'status_code' => 429 ])->withHeaders($exception->getHeaders()); } // TODO: 此處可對其它異常進行同樣方式的處理 return parent::handle($exception); } }
上方類文件還未應用,此時應當將其注入到框架容器中。
打開
App\Providers\AppServiceProvider
文件,在register()
方法中添加:$this->app->singleton('api.exception', function () { return new App\Exceptions\DingoExceptionHandler( $this->app['Illuminate\Contracts\Debug\ExceptionHandler'], config('api.errorFormat'), config('api.debug') ); });
至此接管完成,再次進行請求測試,響應結果變更爲:
{ "message": "當前請求太過頻繁", "result": 0, "status_code": 429 }
響應頭中會多出一項
Retry-After
,值爲剩餘可請求時間,單位秒,即 n 秒後允許請求。
說句題外話,之前看到網上很多人在 Lumen 框架中對服務的註冊都是通過 $app->register()
寫在 bootstrap/app.php
文件內,
個人建議不要這麼做,應當統一寫在 app\Providers\AppServiceProvider.php
文件內,因爲在 app.php
中人家已經註冊了這玩意兒
$app->register(App\Providers\AppServiceProvider::class);
那何不規範的寫在 Providers
裏面呢?例如:
<?php
namespace App\Providers;
use App\Exceptions\DingoExceptionHandler;
use App\Http\DingoAPI\StrictHeaderAccept;
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
use Dingo\Api\Http\Validation\Accept;
use Dingo\Api\Provider\LumenServiceProvider as DingoAPI;
use Illuminate\Redis\RedisServiceProvider;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
/**
* Register any application services.
*
* @return void
*/
public function register() {
// Dingo API
$this->app->register(DingoAPI::class);
// Overwrite request header Accept verify
$this->app->singleton(Accept::class, function () { // Dingo API Accept 嚴格頭的簡易白名單方式,StrictHeaderAccept 類參考下方
return new Accept(new StrictHeaderAccept(
config('api.standardsTree'),
config('api.subtype'),
config('api.version'),
config('api.defaultFormat')),
config('api.strict'));
});
// Overwrite rate limit exception render
$this->app->singleton('api.exception', function () {
return new DingoExceptionHandler(
$this->app['Illuminate\Contracts\Debug\ExceptionHandler'],
config('api.errorFormat'),
config('api.debug')
);
});
// Redis
$this->app->register(RedisServiceProvider::class);
// IDE Helper
if ($this->app->environment() !== 'production') {
$this->app->register(IdeHelperServiceProvider::class);
}
}
}
StrictHeaderAccept.php
內容:
<?php
namespace App\Http\DingoAPI;
use Dingo\Api\Http\Parser\Accept;
use Illuminate\Http\Request;
class StrictHeaderAccept extends Accept {
public function parse(Request $request, $strict = false) {
if (in_array($request->getPathInfo(), config('whitelist.request.header'))) {
$strict = false;
}
return parent::parse($request, $strict);
}
}