RustでAxumを使ってAPIサーバーを作っていたのですが、適切にCORS設定をしているのにも関わらずCORSエラーが発生する事案が発生しました。その報告とエラー解消を記録します。
状況
Axumを使ってAPIサーバーを建てていました。以下のようなイメージです(Axumのサンプルを改変)。
use axum::{
routing::get,
Router,
};
use tower_http::cors::CorsLayer;
#[tokio::main]
async fn main() {
let origins = [
"http://aaa.com".parse::<HeaderValue>().unwrap(),
"http://bbb.com/".parse::<HeaderValue>().unwrap()
];
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }));
.layer(
CorsLayer::new()
.allow_origin(origins)
);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
ポイントとしては以下の点になります。
- 複数のオリジンに対しアクセス許可をするため、
origins
をHeaderValue
のListとしている - 2つ目のoriginが
http://bbb.com/
のように最後に/
が挿入されている
上記のようなシチュエーションで、http://aaa.com
からはリソースにアクセスできるのに、http://bbb.com
からはCORSエラーが発生し、リソースにアクセスできない、という問題が発生していました。
原因と解決策
解決策ですが、オリジン設定の際にhttp://bbb.com/
ではなく、http://bbb.com
のように /
を削除して設定を行うことが必要です。
つまり、以下のようにorigin変数を束縛します。
// Error
let origins = [
"http://aaa.com".parse::<HeaderValue>().unwrap(),
"http://bbb.com/".parse::<HeaderValue>().unwrap()
];
// OK
let origins = [
"http://aaa.com".parse::<HeaderValue>().unwrap(),
"http://bbb.com".parse::<HeaderValue>().unwrap()
];
自分的には、http://bbb.com
でもhttp://bbb.com/
でもオリジンは同じなので問題ないのでは?と考えており、上記解決策になかなか気付けずハマってしまいました。
原因としてはtower-http
ライブラリの実装に起因します。
CORSの設定・処理を行うCorsLayer
はtower-http
ライブラリからインポートして使用しているのですが、その中でAllow-Contrl-Allow-Originの設定を行うAllowOrigin
の実装を見ると、以下のようにヘッダーを作っています。
// ~ 略
pub(super) fn to_header(
&self,
origin: Option<&HeaderValue>,
parts: &RequestParts,
) -> Option<(HeaderName, HeaderValue)> {
let allow_origin = match &self.0 {
OriginInner::Const(v) => v.clone(),
OriginInner::List(l) => origin.filter(|o| l.contains(o))?.clone(),
OriginInner::Predicate(c) => origin.filter(|origin| c(origin, parts))?.clone(),
};
Some((header::ACCESS_CONTROL_ALLOW_ORIGIN, allow_origin))
}
// ~ 略
紛らわしいかもしれませんが、to_header
メソッドの第一引数&self
はアクセスを許可するオリジン、第二引数origin
はリクエストが送られてきたオリジンになります(to_header
メソッドを使っているこちらも参照)。
複数のオリジンを指定した場合、origin.filter(|o| l.contains(o))?.clone()
の部分を見れば分かる通り、 リクエストのオリジンと指定したアクセス許可するオリジンのリストを比較し、そのリストの中にリクエストのオリジンが含まれていれば、そのオリジンをAccess-Control-Allow-Originヘッダに設定しています。
そのため、エラーが発生した原因としては、
- リクエストのオリジンは
http://bbb.com
である - アクセスを許可は
http://bbb.com/
と設定してる -
http://bbb.com
はhttp://bbb.com/
と一致していないと判断され、Allow-Control-Allow-Origin設定が反映されていない(ように見える)
となります。
また、上記のコードの通り、単一のオリジンのみにアクセスを許可する場合はリクエストのオリジンとの比較を行っていないため、http://bbb.com/
でも問題なさそうです。
スラッシュを削除するだけなのでエラー解消自体は簡単ですが、中々原因に気付けずハマってしまいました、、、