CORS(preflight request)にハマったけど解決した話


CORS(preflight request)にハマったので、解決方法を備忘録として残しておきます。

エラーが起きた場面

異なるドメインからHttpリクエストを送る場合は、CORSに注意だよなぁ。
サーバー側のレスポンスで、ヘッダーをつけてあげれば良いんだろう。
簡単じゃん。

クライアント側

何かしらのデータをjasonでPOSTする。

var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", API_ENDPOINT);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send(JSON.stringify(data));

APIサーバー側

サーバー側では、レスポンスのヘッダーを付けてあげる。

func (a *API) HandleFunc(w http.ResponseWriter, r *http.Request) {
    //略...

    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")

    //略...

    fmt.Fprint(w, "ok")
}

結果、エラー...!

当然、結果が正常に返ってくると思いきや
プリフライトリクエスト?がどうのこうのといったエラーが出た。。。なんやこれ。。。


Access to XMLHttpRequest at 'http://localhost:8080/api' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

CORS(preflight request)エラー解決

preflight requestとは?

リクエストによっては CORS プリフライトを引き起こさないものがあります。これをこの記事では「単純リクエスト」と呼んでいますが、 (CORS を定義している) Fetch 仕様書ではこの用語を使用していません。 「単純リクエスト」は、以下のすべての条件を満たすものです。
・許可されているメソッドのうちの一つであること。
GET
HEAD
POST
・ユーザーエージェントによって自動的に設定されたヘッダー (たとえば Connection、 User-Agent、 または Fetch 仕様書で「禁止ヘッダー名」として定義されているヘッダー) を除いて、手動で設定できるヘッダーは、 Fetch 仕様書で「CORS セーフリストリクエストヘッダー」として定義されている以下のヘッダーだけです。
Accept
Accept-Language
Content-Language
Content-Type (但し、下記の要件を満たすもの)
DPR
Downlink
Save-Data
Viewport-Width
Width
・Content-Type ヘッダーでは以下の値のみが許可されています。
application/x-www-form-urlencoded
multipart/form-data
text/plain
・リクエストに使用されるどの XMLHttpRequestUpload にもイベントリスナーが登録されていないこと。これらは正しく XMLHttpRequest.upload を使用してアクセスされます。
・リクエストに ReadableStream オブジェクトが使用されていないこと。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Preflighted_requests

上記の条件を満たさないものはpreflight requestがメインのリクエストの前に行われます。
今回は、POSTですが、"Content-Type", "application/json"をヘッダーに付けているので、プリフライトリクエストが起きました。
画像の最初のリクエストがpreflight requestです。
ここでは、実際にはOPTIONSメソッドがpreflight requestとして走ります。

解決方法

 OPTIONSメソッドの時は、http.StatusOKを返すようにしたら解決しました!

func (a *API) HandleFunc(w http.ResponseWriter, r *http.Request) {
    //略...

    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
    w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")

    if r.Method == "OPTIONS" {
        w.WriteHeader(http.StatusOK)
        return
    }
    //略...

    fmt.Fprint(w, "ok")
}