DISCLAIMER: 最後の「お世話になったサイト」を先に見られることをオススメします。
CORS でありがちな ERROR
このエラーでググりました?今、なにかPOST
しようとしてますよね?
Access to XMLHttpRequest at 'http://localhost:5000/your-firebase-project-name/your-firebase-region/analyzeTweet' 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.
ここで、多くの人は「そんなことはない、おれのサーバサイドはちゃんと Access-Control-Allow-Origin
を返すようにしている!」と思って curl
で確認を始めますね。-H "Origin: localhost:3000"
をつけるだけです。
curl
で CORS のヘッダを確認する
$ curl -H "Content-Type:application/json" -d '{"tweet":"i am happy"}' http://localhost:5000/your-firebase-project-name/your-firebase-region/analyzeTweet -vvv -H "Origin: localhost:3000"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (your-firebase-project-namewer-your-firebase-region-central1/analyzeTweet HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Origin: localhost:3000
> Content-Length: 22
>
* upload completely sent off: 22 out of 22 bytes
< HTTP/1.1 200 OK
< x-powered-by: Express
< cache-control: no-cache, no-store, must-revalidate
< pragma: no-cache
< expires: 0
< access-control-allow-origin: localhost:3000
< vary: Origin
< content-type: application/json; charset=utf-8
< content-length: 57
< etag: W/"39-yPolt7ZZODo5n87HTQf8sBxIYwg"
< date: Tue, 30 Apr 2019 01:03:44 GMT
< connection: close
<
* Closing connection 0
はい。POST
でちゃんと Origin: localhost:3000
を渡していて、サーバサイドも access-control-allow-origin: localhost:3000
と合言葉を返しています。問題なさそうです
でも、もともとのエラーはResponse to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
です
Preflight?
- ある一定条件のリクエストを投げる際には事前に
preflight
という通信が発生するようです - 詳細な発生条件はここにあります。いろんな条件がありますが、Ajaxなコールだとだいたい
content-type: application/json
なので、ここでpreflight
対象となるんじゃないでしょうか - ちなみに preflightの
Request Method:
はOPTIONS
となるので、先のcurl
だと一見うまくいっているように見えて、実は再現できてません
サーバサイドは普通はこれでいける
要はサーバサイドで preflight 含めて、正しくヘッダつけて返せばいいのです。やりかたはググればたくさんでてきます。例えば expressjs/cors
を使えば、よきに計らってくれるはずです。(以下の #1
, #2
を追加するだけ)
// Use https://github.com/expressjs/cors ... #1
const cors = require('cors')({ origin: true });
exports.getTweets = functions.https.onRequest((request, response) => {
const params = {"params-that-you-need": "x"};
twitter_client.get('statuses/user_timeline.json', params, (error, tweets, res) => {
if (!error) {
// Add necessary headers automatically. Thanks expressjs/cors! ... #2
cors(request, response, () => {
response.send(tweets);
});
}
});
});
この設定で curl
で preflight - Request Method: OPTIONS
してみると
$ curl -X OPTIONS -H "Content-Type:application/json" -d '{"twitter_screen_name":"your-twitter-name"}' http://localhost:5000/your-firebase-project-name/your-firebase-region/getTweets -H "Origin: http://localhost:3000" -vvv
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)your-firebase-project-name/your-firebase-region/getTweets HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Origin: http://localhost:3000
> Content-Length: 33
>
* upload completely sent off: 33 out of 33 bytes
< HTTP/1.1 204 No Content
< x-powered-by: Express
< cache-control: no-cache, no-store, must-revalidate
< pragma: no-cache
< expires: 0
< access-control-allow-origin: http://localhost:3000
< vary: Origin, Access-Control-Request-Headers
< access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE
< content-length: 0
< date: Tue, 30 Apr 2019 02:04:33 GMT
< connection: close
<
* Closing connection 0
サーバからは access-control-allow-origin
に加えて access-control-allow-methods
で次に受け付けるメソッドが返ってきています。このケースではサーバ側のコードは preflightである OPTIONS
とその後の POST
を 意識せずに対応できています
それでもうまくいかない場合
ただ、curl
での POST
は動くけど、ブラウザからの preflight だけどうしてもうまくいかな場合があったので、こんな感じで OPTIONS
きたら必要なヘッダつけて 200 OK
返す方法で凌いどいたぜ...
const cors = require('cors')({ origin: true });
exports.analyzeTweet = functions.https.onRequest(async (request, response) => {
if (request.method == "OPTIONS") {
cors(request, response, () => {
response.status(200).send()
});
return
}
// Logic for other than OPTIONS from here - eg. handling POST, etc.
});
あと、お世話になったサイトに挙げますが onRequest
じゃなくて onCall
使う方が簡単でいいという話もあります
お世話になったサイト
- FirebaseのCloud FunctionsでCORSが~とかAccess-Control-Allow-Originが~と言われたらこれ
- CORS を分かってないから動くコード書いて理解する
- オリジン間リソース共有 (CORS)
さて、これでやっと次にいけます。