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

lxyuma BLOG

開発関係のメモ

androidのcanvasのCORSが動かない

android canvas

経緯

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から画像を取得したい。。。)