ふとしです。
要はハンドラからのレスポンスと違って、ミドルウェアから Err
を返すようなエラーは基本回復不能な「失敗」としてケアしません、回復可能なエラーなら Ok
を返してねということらしいです。
A middlware error is indicative of a fatal failure. All middlwares should return Ok(...) responses if the error is recoverable. This is a different expectation to handlers.
ひっかかったミドルウェア
このブログの編集周りの処理で、ヘッダーの JWT を検証して期限切れならそこでハネるみたいな処理でした。
match di.authing().verify(token).await {
Ok(_) => service.call(req).await,
Err(e) => Err(error::ErrorUnauthorized("expired")),
}
Nuxt のサーバーサイドアクセスでは 401
かつ expired
と判断できるのでトークンのリフレッシュをかけることができて問題はありませんでした。しかしクライアント側で期限切れを迎えると上の挙動から CORS にひっかかり単なる Network Error
としか認識できず、リフレッシュの経路に入れずエラーになってて気づいたわけです。
対応
ミドルウェアのレスポンスの型を確定する
独自で返すのはエラーだけだったので MiddlewareBody
の関連型 Response
は Service
の関連型におまかせで不確定にしていました。
impl<DI, S> Service<ServiceRequest> for MiddlewareBody<DI, S>
where
DI: Di,
S: Service<ServiceRequest, Error = actix_web::Error>,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
今回は処理中で type Response
を作成しなければならないため、具体的な型を指定します。actix_web::App
に合わせて ServiceResponse
を指定します。
impl<DI, S> Service<ServiceRequest> for MiddlewareBody<DI, S>
where
DI: Di,
S: Service<ServiceRequest, Error = actix_web::Error, Response = ServiceResponse>,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
レスポンスを Ok で返す
これで返すべき型が確定するので、call
から Ok
かつ 401
のレスポンスを返すようにします。
match di.authing().verify(token).await {
Ok(_) => service.call(req).await,
Err(e) => {
let (req, _) = req.into_parts();
Ok(ServiceResponse::new(
req,
HttpResponse::Unauthorized().body("expired"),
))
}
}
}
これでミドルウェアで中断しても CORS 対応が行われるようになり、ブラウザからのアクセスからでも 401
であることが判別できるようになりました。
まとめ
今回は Nuxt 側でブラウザではないアクセスがあったので原因がすぐわかりました。
しかし Internal Server Error などでこの手のミドルウェアが働かず CORS エラーとして表示されてなかなか本当の原因がわからないということはよくありますね。いままで大丈夫だったのに CORS エラーが出た場合は、まず curl などで確認してみる習慣をつけたいですね。(Chrome の dev tool から簡単にヘッダー付きの curl を生成できたりしますし)