场景描述
比如我们需要对 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);
}
}