Google App Engineでクロスドメイン通信


前回「秋はまだですか」と書きましたが、その日のうちに気温下がったw これでちょっと過ごしやすくなったかも。…薄着で寝ててちょっと風邪引きそうになったのは内緒(・ω・)

本題。
クロスドメインでの非同期通信(XMLHttpRequest Level2)をGoogle App Engineで実装したのでメモ。はまったポイントもいくつか書いておきます。

特にハマったのは出力ヘッダ周り。

Access-Control-Allow-Origin
これはあちこちのサイトに書かれてますね。このヘッダを「*」で出力してあげると他ドメインからの通信を受け付けるようになります。・・・が、これだけじゃダメなんです。

Access-Control-Allow-Methods
こいつを指定してあげないとサーバーが受け付けてくれません。GETならGET、POSTならPOST・・・と指定してあげないといけません。これは複数を一括指定できます(例:「GET, POST」)。あとPOST通信の時は「OPTIONS」も追加してあげる必要があります。

FirefoxはPOSTする前にOPTIONSを投げるので、受信サーバー側でそれを受け取るようにしてあげることが必要です。POST時、Firebugで通信を覗くと「OPTIONS」が投げられているのが分かります(下図)。

これFirefoxだけなんかな?

Access-Control-Allow-Headers
上に書いた二種のヘッダ出力でいける・・・と思ったらIEが通らない。なんでじゃいと思って調べたら、この「Access-Control-Allow-Headers」に’*’を指定してあげないと動かなかった・・・。(本当はヘッダの中身をちゃんと調べて指定するべきなんだろうけど、めんどくさかったのでw)

そういえば自分も最初勘違いしてたんですが、これらのヘッダはデータ受け取り側のサーバー(GET/POST先のサーバ)が出力しないとダメですよ。発信側がjavascriptの”setRequestHeader”でこれらのヘッダをつけても無意味です。

そうそう、クロスドメインでの通信で忘れちゃならないのがCookie。クロスドメインでの通信にはCookieを付与できません。いや正確にいえば、「IE以外」は付与できます。 IE、ま た お 前 か orz
このため、セッション保持は別の手法を取る必要があります。

参考になるかわかりませんがApp Engine側のサンプルコードをおいておきます。
追記: コメントでいただいた指摘事項を反映させました

class Hoge(webapp.RequestHandler):
    def get(self):
        referer = self.response.headers.get('Referer', '')
        origin = self.response.headers.get('Origin', '')

        # この方法でOriginやRefererが取れるので
        # 必要に応じてチェック処理などを入れるといいのでは。

        fugafuga()  # 何らかの処理

        # これはあちこちのサイトでよく言われている処理
        self.response.headers['Access-Control-Allow-Origin'] = '*'

        # IE8は、これがないとうまく動きませんでした。
        self.response.headers['Access-Control-Allow-Headers'] = '*'

        # GET/POSTなど、必要に応じて許可するメソッドを指定します。
        # POSTの場合はOPTIONSも指定する必要があります。
        self.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
        self.response.headers['Content-Type'] = 'text/plain;charset=UTF-8'
        self.response.out.write('Hello, World')

    def post(self):
        # 何らかの処理を行う
        self.response.headers['Access-Control-Allow-Origin'] = '*'
        self.response.headers['Access-Control-Allow-Headers'] = '*'
        self.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
        self.response.headers['Content-Type'] = 'text/plain;charset=UTF-8'
        self.response.out.write('Hello, World')

    def options(self):
        # 何らかの処理を行う
        # POSTの場合は、このoptionsも指定して下さい。
        self.response.headers['Access-Control-Allow-Origin'] = '*'
        self.response.headers[‘Access-Control-Allow-Headers’] = self.request.headers[‘Access-Control-Request-Headers’]
        self.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'

ご存じだとは思いますが念のための注意書き。
XHR lv2はIE8以上, Firefox, Chrome, Safariでのみ有効です。IE6, 7, Operaでは使えません。
あとIE8では独自オブジェクトXDomainRequestを使用する必要があります。他のブラウザは今までと同じXHRを使えばOKです。Microsoft、なぜ独自実装なんかしたんだオイ。めんどくさいったらありゃしないぞ。

…大体こんなもんだったと思いますが、他に何かあったかなぁ。思い出したら書きます。

クロスドメインに対応させた非同期通信だけを行うjsライブラリも書いたんだけど、需要あるかなぁ。あればいずれアップしますです(・ω・)b

5件のコメント

  1. こんにちは。

    def options(self):の
    self.response.headers[‘Access-Control-Allow-Headers’] = ‘*’

    だと、少なくともChromeとSafariではエラーになります。

    Access-Control-Allow-Headersは「Access-Control-Request-Headersを引き写し、さらにサーバーが受け入れられるHeaderを追加する」仕様なので、options()を以下のようにすると動きました。

    def options(self):
    self.response.headers[‘Access-Control-Allow-Origin’] = ‘*’
    self.response.headers[‘Access-Control-Allow-Headers’] = self.request.headers[‘Access-Control-Request-Headers’]
    self.response.headers[‘Access-Control-Allow-Methods’] = ‘GET, POST, OPTIONS’

    ちなみに、キーが存在しないかもしれないdictをひくときは、こうすると例外が出ません。キーがないとfooには第2引数が入ります。

    foo = mydict.get(‘key’, None)

    平野 聡

  2. 返事が遅くなり申し訳ございません。
    ご指摘ありがとうございます!エントリー内容にいただいた指摘内容を反映させました。

    Access-Control-Allow-Headersは「Access-Control-Request-Headersを引き写し、さらにサーバーが受け入れられるHeaderを追加する」仕様なので、

    おお、そういう仕様だったのですね・・・。勉強になりました。ありがとうございます。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください