こんにちは。
Go独学中です。
Go✖︎ReactでSPAを作っている最中corsのエラーに何度も遭遇し、その度に悪戦苦闘したのでまとめてみたいと思います。
CORSの記事はQiitaでLTGM数が100を超える記事がゴロゴロあるので、脳死でそのコードをコピペしていると痛い目にあってしまいます。
それではよろしくお願いします。
エラー内容
今回出たエラー内容です。
Access to XMLHttpRequest at 'http://localhost:8080/login' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
corsのエラーと言っても色々な種類があり、今回は PreflightRequestでエラーが出ていました。
プリフライトリクエストとは特定の条件下でhttpリクエストを送るときに、指定したリクエストの前に事前にOPTIONSメソッドで通信を行う事です。
結果的にこのエラーを良く見ずに、サーバーやフロントのヘッダーばかりに目が向いてしまいエラー解決にかなりの時間がかかってしまいました。
APIのソースコード
ハンドラー
dbのusersテーブルから該当するユーザーを検出し、jwtを返すという処理です。
ルーティングには gorirra/mux というgoのパッケージを使っています。
func (a *App) serveHTTPHeaders(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
}
func (a *App) loginHandler(w http.ResponseWriter, r *http.Request) {
//ログイン処理 (パスワードとemailを受け取り,jwtを返す)
}
ルーティング
import (
"github.com/gorilla/mux"
)
func Route(app *App) *mux.Router {
r.HandleFunc("/login", app.loginHandler).Methods("POST")
}
フロントのソースコード
ReduxでsignIn関数を作り、axiosでログインのapiを叩いて、emailとpasswordを送っています。
import { signInAction } from './actions';
import axios from 'axios';
export const signIn = (email, password) => {
return async (dispatch) => {
try {
const response = await axios.post('/login', {
email: email,
password: password,
});
const data = response.data;
console.log(data);
dispatch(
signInAction({
isSignedIn: true,
})
);
} catch (error) {
console.log(error);
}
};
};
結論
上記のGOコードにあるようにcorsの設定はすでに済んでおり、get通信は成功していたので何が原因か分からず、ひたすらヘッダー周りを調べていましたが、解決には至りませんでした。
どのようにしてプリフライトエラーを解決したかを先に明示しておきます。
まずルーティングです。
プリフライトはOPTIONSメソッドが走るので、 gorilla/muxなどでメソッドを指定していた場合、それ以外のメソッドはすべて405エラーが走ります。このプリフライトリクエストはcors通信の際にブラウザが行うものなので、 postmanなどでapiを叩いても再現できません。
まずは OPTIONS を許可します。
ルーティング
import (
"github.com/gorilla/mux"
)
func Route(app *App) *mux.Router {
- r.HandleFunc("/login", app.loginHandler).Methods("POST")
+ r.HandleFunc("/login", app.loginHandler).Methods("POST","OPTIONS")
}
これでOPTIONSが飛んできても405エラーは回避できるようになりました。
次に、OPTIONSにはbodyが付与されません。
つまりフロント(React)でPOSTした時のemailとpasswordは渡って来ません。
そのため、ハンドラー内でgoのエラーハンドリングに引っかかりエラーを返してしまいます。
(例えば、 email == "" の場合にエラーを返すようにしている事を想定)
OPTIONSの際は200okで返すようにしてあげます。
func (a *App) loginHandler(w http.ResponseWriter, r *http.Request) {
//エラーハンドリングより上部に以下を追加
+ if r.Method == "OPTIONS" {
+ a.serveHTTPHeaders(w) //ヘッダーを付与
+ w.WriteHeader(http.StatusOK)
+ return
+ }
}
※ この時200okを返すだけではcorsを許可するヘッダー情報が付与される前にreturnしてしまうので注意です。
以上が解決方法になります。
なぜこんなにもcorsに苦しんだのか
- corsのヘッダー周りしか見ていなかった。
- corsのエラーを一括りにしてしまっていた。
- デバッグの方法が分からなかった。(Postman等では再現できない点)
デバック方法
- ブラウザの検証ツールで何のリクエストで失敗しているのかを確認する NetoWork > Headers
- エディタのデバッガーでhttpメソッドは何がきているか、bodyが入っているかを確認する
さらに
- 今回のコードではハンドラー内でOPTIONSに対応していますが、ミドルウェアを作ると綺麗に書けるみたいです。私の場合、認証用にもミドルウェアを作っていたので、ミドルウェアが増えすぎるのが嫌でとりあえず上記のような感じにしています。
以上です。お付き合い頂きありがとうございました。