gorilla/muxのCORS対応


はじめてgolangでgorilla/muxを使用したAPIを作ったのですが、CORSで諸々ハマったので記事としてまとめます。

ことの始まり

以下のような実装のAPIを作りました。

main.go
package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    getTestestHandler := NewGetTestHandler()
    r.HandleFunc("/test", getTestestHandler.Get).Methods("GET")
    http.ListenAndServe(":8080", r)
}
get_test_handler.go
package main

import (
    "encoding/json"
    "net/http"
)

type GetTestHandler struct{}

type TestResponse struct {
    Test string
}

func NewGetTestHandler() *GetTestHandler {
    return &GetTestHandler{}
}

func (h *GetTestHandler) Get(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(TestResponse{Test: "test"})
}

動作確認のためにhtml.htmlを作成してブラウザからアクセスしてみます。

test.html
<html>
<script type="text/javascript">
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200) {
            console.log(xhr.response);
        }
    }
    xhr.open('GET', "http://localhost:8080/test");
    xhr.responseType = 'blob';
    xhr.send();
</script>
</html>

ブラウザのデベロッパーツールを開くと以下のようなエラーが。

対応①

No 'Access-Control-Allow-Origin' header is present on the requested resource.と書いてあるので、とにかくヘッダを追加すればいいのかと思いました。
「CORS ヘッダ」で検索したら出てくるようなヘッダを3つほど追加してみました。

get_test_handler.go
//省略

func (h *GetTestHandler) Get(w http.ResponseWriter, r *http.Request) {
    //ヘッダの追加
    w.Header().Set("Access-Control-Allow-Headers", "*")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(TestResponse{Test: "test"})
}

そして再度確認してみると、

エラーは変わらず、よく見るとヘッダが追加されていません。

対応②

試しにTalendAPIでリクエストしてみると

ヘッダがついている?なんで?
ということでいろいろ調べてみたところ、プリフライトリクエストへの応答が必要だと判明しました。
ということでOPTIONSリクエストに応答できるようにソースコードを変更してみました。
(ついでにhandlerの共通処理を持たせるtest_handler.goを作成)

main.go
package main

import (
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    getTestestHandler := NewGetTestHandler()
    //Methodsに"OPTIONS"を追加 
    r.HandleFunc("/test", TestHandler(testHandler.Get)).Methods("GET", "OPTIONS")
    http.ListenAndServe(":8080", r)
}
get_test_handler.go
//省略

func (h *GetTestHandler) Get(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(TestResponse{Test: "test"})
}
test_handler.go
package main

import (
    "net/http"
)

type TestHandle func(w http.ResponseWriter, r *http.Request)

func TestHandler(handle TestHandle) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        //ヘッダの追加
        w.Header().Set("Access-Control-Allow-Headers", "*")
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")

        //プリフライトリクエストへの応答
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }

        //Handler関数の実行
        handle(w, r)
    }
}

ということで確認してみると。

やっとできましたー

まとめ

CORSに対応するには、
 1. ヘッダの付与
 2. プリフライトリクエストへのレスポンス
が必要ということでした。
対応内容は単純ですが意外と時間を要してしまったので、同じような状況でハマっている方の助けになれば幸いです。