LoginSignup
26
18

More than 5 years have passed since last update.

Firebase で何かを POST しようとしたら CORS の preflight で怒られましたね?

Last updated at Posted at 2019-04-30

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 使う方が簡単でいいという話もあります

お世話になったサイト

さて、これでやっと次にいけます。

26
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
18