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”;
結果
androidとCORS
一応、caniuse見ても、android端末は古くからCORS対応されている事になっている。
ところが、2.3系、4.x系の端末で、
サーバー側のレスポンスヘッダー微調整したり、クライアント側の記述もいろいろ微調整して、
試行錯誤してみたが、結局、ずっと、以下のエラーが出る。
DOM Exception: SECURITY_ERR (18)
androidのcanvasのCORSは機能していないようにみえる。
※全端末ではないかもしれないが、少なくとも、自分が見た端末は動かなかった。
stack over flow
stack over flow見てみると、やっぱり、皆動いてない。
ちなみに、自分の場合、はじめ古いios(5だったと思う)も動かなかったが、
Access-Control-Allow-MethodsとAccess-Control-Allow-Headersを追加したら、
とりあえず、古いiosも動くようになった。
androidとbase64?
ググると、androidは、画像をxhr経由のblob(android2は無理なのだが)で取得して、
それをbase64に変換すると、policy突破できるような記述があり、
試してみると、確かに動く!
のだが、これ、実は、NGらしい。
same origin policyは、uriスキーマの違いもチェックしているので、
documentのhostにあたる、httpと、base64画像の、data uri schemeは
異なるスキーマなので、仕様上は、NGにしなくてはいけないらしい。
これをちゃんと実装しているのが、chromeらしく、それ以外の多くが実装されていないみたい。
確かに同じソースをandroidのchromeで見ると、エラーになった。
chromeでエラーになる時点でOUTで、デフォルトのブラウザもいつ実装変わるのかわからないので、諦めた。
結局
androidの問題あるので、CORS諦めて、
documentと同じhostから画像をとるように妥協した。
但し、当然、そこだけ負荷かかるので、
ページの作り工夫することにした。
(本当に必要な時だけ、documentから画像を取得したい。。。)