環境についてざっくり

クライアント側

  • ブラウザ: Google Chrome バージョン 87.0.4280.141 (Official Build) (64ビット)
  • OS: Windows 10 Home 19041.746
  • HTTPクライアント: axios v0.21.1

サーバ側

  • OS: Raspbian GNU/Linux 10 (buster)
  • webフレームワーク: actix-web v3.3.2

現象

Access to XMLHttpRequest ... has been blocked by CORS policyというエラーが出力される

Raspberry Pi 4 Model B上でバックエンド(actix-web)とフロントエンド(Vue CLI)を稼働させている。WindowsPCからブラウザを使ってフロントエンドにアクセスし、axiosを使ってバックエンド(actix-web)にGETリクエストを実行した。すると、ブラウザのコンソールに以下のようなエラーが出力された。(ローカルIPが記述されていたところは一部編集している。)

Access to XMLHttpRequest at 'http://<localIP>:50001/' from origin 'http://<localIP>:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

問題発生時のソースコード

この時のサーバ側(actix-web)のコードは以下の通り。(ローカルIPとメールアドレスが記述されていたところは一部編集している。)

main.rs

Cargo.toml

この時クライアント側はJavaScriptから以下のようにaxiosを実行していた。(ローカルIPが記述されていたところは一部編集している。)

調査及び解決方法

actix_corsを利用し、GETリクエストについてはエラーが解消した

エラーログを見ると、CORSポリシーによって、リソースへのアクセスがブロックされているようだ。CORSポリシーに対応するため、サーバ側のプログラムを修正していく。web上で"actix CORS"あたりで調べてみる。すると、actix_corsというcrateがあった。actix_corsを使うために、main.rsCargo.tomlについて、以下のように修正した。(ローカルIPが記述されていたところは一部編集している。)

main.rs

+ use actix_cors::Cors;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
...
HttpServer::new(|| {
+ let cors = Cors::default()
+   .allowed_origin("http://<localIP>:8080")
+   .allowed_methods(vec!["GET", "POST"]);
+ App::new().wrap(cors).service(index).service(post_operations)
- App::new().service(index).service(post_operations)
})

Cargo.toml

[dependencies]
actix-web = "3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
+ actix-cors = "0.5.4"

この状態で再度確認してみた。GETリクエストは正常に処理されるようになった。しかし、POSTリクエストはまだCORSエラーが出る状態だった。なお、ブラウザ経由ではなく、Postmanを利用してリクエストを投げた場合、特にエラーなくレスポンスが返ってきた。

デバッグログを確認したところ、ブラウザからPOSTする前にOPTIONSリクエストが実行されていた

ブラウザを経由したPOSTリクエストのみエラーが出続ける問題について、web上で調べても自分の検索力では解決のための情報を見つけられなかった。そこで、サーバ側のデバッグログを出力し、その出力から原因を調べることにした。

デバッグログ出力のためにactix_web::middleware::Loggerを使用した。main.rsCargo.tomlについて、以下のように修正した。(ローカルIPが記述されていたところは一部編集している。)

main.rs

+ extern crate env_logger;
use actix_cors::Cors;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
+ use actix_web::middleware::Logger;
...
async fn main() -> std::io::Result<()> {
+ std::env::set_var("RUST_LOG", "actix_web=debug");
+ env_logger::init();

  HttpServer::new(|| {
    let cors = Cors::default()
      .allowed_origin("http://<localIP>:8080")
      .allowed_methods(vec!["GET", "POST"]);
+    App::new().wrap(cors).wrap(Logger::default()).service(index).service(post_operations)
-    App::new().wrap(cors).service(index).service(post_operations)
  })

Cargo.toml

[dependencies]
actix-web = "3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
actix-cors = "0.5.4"
+ env_logger = "0.8.2

上記のようにサーバ側のソースを変更し、ログを確認した。するとPOSTリクエスト実行時はOPTIONSリクエストが最初に実行されていることが分かった。(ログ内容について一部を省略・編集している。)また、OPTIONSリクエスト時にHeaderNotAllowedというエラーが発生している。

ログの内容

[2021-01-16T05:56:21Z DEBUG actix_web::middleware::logger] Error in response: HeadersNotAllowed
[2021-01-16T05:56:21Z INFO  actix_web::middleware::logger] ... "OPTIONS /operations HTTP/1.1" 400 44 "http://<localIP>:8080/" ...

content-typeヘッダを許可するように修正することで、エラーが解消された

ChromeのデベロッパーツールのNetworkタブから、OPTIONSリクエスト実行時のヘッダーを見てみる。Access-Control-Request-Headersヘッダーに以下のような設定がされていた。

Access-Control-Request-Headers: content-type

恐らくこれがエラーの原因と思われる。content-typeヘッダーを許可するように、main.rsについて、以下のように修正した。(ローカルIPが記述されていたところは一部編集している。)

main.rs

...
use actix_cors::Cors;
+ use actix_web::{get, post, http, web, App, HttpResponse, HttpServer, Responder};
- use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
...
HttpServer::new(|| {
  let cors = Cors::default()
    .allowed_origin("http://<localIP>:8080")
+   .allowed_methods(vec!["GET", "POST"])
-   .allowed_methods(vec!["GET", "POST"]);
+   .allowed_header(http::header::CONTENT_TYPE);
    App::new().wrap(cors).wrap(Logger::default()).service(index).service(post_operations)
})
...

ここまでの変更で、ようやくChromeからのPOSTリクエストが通るようになった。最初GETだけ通ってPOSTが通らなかったのは、ヘッダー許可の設定が足りていなかったということだった。

結論

actix-webに対してブラウザからHTTPリクエストを実行した際、Access to XMLHttpRequest ... has been blocked by CORS policyというエラーが発生した場合は、actix_cors等を使ってCORSポリシーに対応させる必要がある。

[備考]OPTIONSリクエストについて

以下が参考になった。

アフィリエイト