lxyuma BLOG

開発関係のメモ

androidのcanvasのCORSが動かない

経緯

canvasを画像に変換したかった。(toDataURL)

しかし、canvas内の画像をCDN経由にすると、

後述のとおり、ブラウザのsecrity policyにひっかかり画像に変換できない。

そこで、Cross Origin Resource Sharing(CORS)を使って、これを回避したかった。

これは、そのメモ。

結論

先に結論を書いておくと、androidだけ無理だったので諦めた。

canvasでの画像変換でのCORS対応は、まだまだ未整備な感じ。

ブラウザのsame origin policy

ブラウザには、same origin policy 同一生成元ポリシーというルール(RFC)がある。

これは、

  • 同じprotocol
  • 同じポート
  • 同じホスト

ではない、ページ間の操作やアクセスを制限するというもの

xhrやiframeなど、ブラウザの幾つかの機能で、上記の制約が入っているが、

その中に、canvasもこの制約が含まれている。

canvasでのsame origin policy

canvasは、toDataURL()をすると、

canvasの内容を画像データ(base64のdataスキーマuri)に変換できる。

この時、documentと異なるhostから取得した画像をcanvasに入れていると、

ブラウザのsame origin policyに抵触してしまい、

canvasが汚染(taint)して、画像に変換する事ができない。

コンソールにエラーが出てくる。

回避するには

回避するには、

  • Reverse Proxy
  • CORS

といった、やり方があるが、

わざわざこれだけのために、Reverse Proxy組みたくないので、

CORS使う事が一般的。

CORS

CORSは、Cross Origin Resource Sharingの事。

異なるホストのURLを事前に教えてこの制約から特別に回避してしまおうという話。

サーバー側と、クライアント側と両方に対応が必要。

以下、今回の、canvasの画像変換に限って書くと、

CORS サーバー対応

例えば、documentはくhttp://nantoka-host-aと、

その画像をはく、http://nantoka-host-bがあったとして、

画像の方のレスポンスのヘッダーに、

Access-Control-Allow-Origin: *

を追加する。(* がワイルドカード。ここに、host入れる)

他、幾つか、ヘッダがある。

参考

CORS クライアント側対応

画像を追加する時に、crossOrigin属性を指定すれば良い。

var img = new Image();
img.src = “http://nantoka-host-b”
img.crossOrigin = “Anonymous”;

参考

結果

  • PC(chrome)では、エラーでなくなった。
  • iOS系も、エラーでなくなった。
  • android系では、エラーはそのまま、無くならず、まだ出てきた。

androidとCORS

一応、caniuse見ても、android端末は古くからCORS対応されている事になっている。

ところが、2.3系、4.x系の端末で、

サーバー側のレスポンスヘッダー微調整したり、クライアント側の記述もいろいろ微調整して、

試行錯誤してみたが、結局、ずっと、以下のエラーが出る。

DOM Exception: SECURITY_ERR (18)

androidcanvasのCORSは機能していないようにみえる。

※全端末ではないかもしれないが、少なくとも、自分が見た端末は動かなかった。

stack over flow

stack over flow見てみると、やっぱり、皆動いてない。

ちなみに、自分の場合、はじめ古いios(5だったと思う)も動かなかったが、

Access-Control-Allow-MethodsとAccess-Control-Allow-Headersを追加したら、

とりあえず、古いiosも動くようになった。

androidbase64?

ググると、androidは、画像をxhr経由のblob(android2は無理なのだが)で取得して、

それをbase64に変換すると、policy突破できるような記述があり、

試してみると、確かに動く!

のだが、これ、実は、NGらしい。

same origin policyは、uriスキーマの違いもチェックしているので、

documentのhostにあたる、httpと、base64画像の、data uri scheme

異なるスキーマなので、仕様上は、NGにしなくてはいけないらしい。

参考

これをちゃんと実装しているのが、chromeらしく、それ以外の多くが実装されていないみたい。

確かに同じソースをandroidchromeで見ると、エラーになった。

chromeでエラーになる時点でOUTで、デフォルトのブラウザもいつ実装変わるのかわからないので、諦めた。

結局

androidの問題あるので、CORS諦めて、

documentと同じhostから画像をとるように妥協した。

但し、当然、そこだけ負荷かかるので、

ページの作り工夫することにした。

(本当に必要な時だけ、documentから画像を取得したい。。。)