読者です 読者をやめる 読者になる 読者になる

Carpe Diem

備忘録。https://github.com/jun06t

サーバサイドのCORS対応

Go CORS

概要

No 'Access-Control-Allow-Origin' header is present on the requested resource.というエラーが出た際に外部API(サーバ側)でどう対応すべきかをまとめました。

CORSでググると幸せになれます。

環境

  • go 1.7.4
  • gin 1.1.4

CORSが必要になるのはどんな時か

シンプルに言うと以下の条件のときです。

  • ブラウザからの外部APIへのリクエストである
  • 今開いてるページのURLとは別ドメイン、ポートのAPIをページ内で叩こうとしている

CORSのリクエストは2種類

以下の2つがあります。

  1. そのままAPIへリクエスト
  2. 先にpreflightリクエストを送ってから、その後でAPIへリクエスト

preflightリクエストを送るかどうかはブラウザが決めており、以下の条件全てに当てはまらないリクエストの場合は自動で送られます

  • HTTPメソッドがGET, POST, HEADのいずれか
  • HTTPヘッダにAccept, Accept-Language, Content-Language, Content-Type以外のフィールドが含まれない
  • Content-Typeの値はapplication/x-www-form-urlencoded, multipart/form-data, text/plainのいずれか

a. そのままAPIへリクエスト

この場合は外部APIからのレスポンスヘッダに

レスポンスヘッダ 説明 具体例
Access-Control-Allow-Origin 許可するリクエスト元のURL "*", "http://www.example.com"など

が設定されていればOKです。


b. 先にpreflightリクエストを送ってから、その後でAPIへリクエスト

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource

というエラーが出てる場合はこちらです。

この場合はまずはpreflightリクエストにレスポンスを返す口を用意する必要があります。
またそのレスポンスでは

レスポンスヘッダ 説明 具体例
Access-Control-Allow-Origin 許可するリクエスト元のURL "*", "http://www.example.com"など
Access-Control-Allow-Methods 次に送る実際のAPIコールで使用するメソッドを含ませる "GET,POST,PUT,DELETE"など
Access-Control-Allow-Headers 次に送る実際のAPIコールで使用するヘッダを含ませる "content-type,X-User-Id"など

という各種ヘッダをつけて返す必要があります。

Access-Control-Allow-Headersについてはpreflightリクエストが自動で次に使うヘッダの一覧をAccess-Control-Request-Headersというリクエストヘッダに追加するので、サーバ側はこれをみて返せば大丈夫です。

具体的な実装方針

ミドルウェアとして挟むのが疎に設計できて良いと思います。

github.com

こちらを参考に作ります。

前提として

  • OPTIONSは全てpreflightリクエストとみなす
  • xxx.example.comのサービスからならアクセス可能
  • "Access-Control-Allow-Origin", "*"とする

とします。
ginというgolangのwebフレームワークのミドルウェアとして用意すると以下のようになります。

var reg = regexp.MustCompile("https?:\\/\\/(.+)\\.example\\.com:?.*")

func CORS() gin.HandlerFunc {
    return func(c *gin.Context) {

        if c.Request.Method == "OPTIONS" {
            // for preflight
            origin := c.Request.Header.Get("Origin")

            r := reg.Copy()
            if r.MatchString(origin) {
                headers := c.Request.Header.Get("Access-Control-Request-Headers")

                c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
                c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
                c.Writer.Header().Set("Access-Control-Allow-Headers", headers)

                c.Data(200, "text/plain", []byte{})
            } else {
                c.Data(403, "text/plain", []byte{})
            }
        } else {
            // for actual response
            c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
            //c.Writer.Header().Set("Access-Control-Expose-Headers", "")
            c.Next()
        }

        return
    }
}

その他よく見るレスポンスヘッダ

レスポンスヘッダ 説明 具体例
Access-Control-Allow-Credentials cookieの送信をOKするか。
ただし"Access-Control-Allow-Origin", "*"の場合は使えない
true
Access-Control-Expose-Headers APIから返ってくるレスポンスヘッダの内、Javascriptでアクセスしてよいとするヘッダ "X-User-Id"

ソース