#目的
・Angular環境でWebカメラで撮影したjpg画像ファイルをS3に直接アップロードする実装にハマったのでメモしておく。
・webカメラから画像を出力するところは簡単なので省略。(ブラウザの種類によって動かなかったりしますが。)
・S3についてはPre-signed URLをサーバ側で取得しましたが、そこもハマらなかったので省略。
・問題になったのは、画像ファイルをアップロードする部分なので、その界隈のみをクローズアップする。
#ハマったところ
・まず、html5 canvasのtoDataURLで画像出力するとBase64形式になり、かつデータの前にフォーマットを示す文字列がくっついてしまう。このため、Base64形式のデータをいったんBLOBに出力する必要がある。この辺まではググればいくつか情報があるのでなんとかなる。(後ほどソースつけます。)
・BLOBデータをアップロードする部分で、Ajax呼び出しというかXMLHttpRequestで実装しようとして、FormDataとか準備してやったところ、S3にアップロードしたファイルがMIME形式のまま(いわゆるBoundaryの文字列とかそのままくっついたままのこと)保存されてしまい、どうにもならなかった。
・Webで調べてもXHRでアップロード文字列を組み立ててS3アップロードしている事例は結構見つかったが、どうにもうまくいかず。結局、XHRではなくAngularの通常の$httpでBLOBをアップロードしたところうまくいった。なんだ、それだけのことか、、という感じですが、かれこれ3日ほど悩みました。
#実装サンプル
・Controller部分だけだが、以下の通り。
var contentType = 'image/jpeg';
//var contentType = 'image/png';
//DataURL(Base64形式)をBlobに変換
function dataURItoBlob(dataURI) {
//DataURLのデータ部分をBase64からデコード
var binary = atob(dataURI.split(',')[1]);
var array = [];
// Arrayに 1 バイトずつ値を埋める
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
//BLOB形式に変換
return new Blob([new Uint8Array(array)], {type: contentType});
}
//ファイルアップロード
function uploadPicture(callback) {
console.log('uploadPicture START!!');
//画像情報作成(BLOB形式)
var canvas = document.getElementById('myCanvas');
var dataURL = canvas.toDataURL(contentType);
var blob = dataURItoBlob(dataURL);
//アップロード用のURL取得
$http({
method: 'GET',
url: '/api/xxxx/'
})
.success(function(data) {
var uploadURL = data.url;
//画像アップロード
$http({
method: 'PUT',
url: uploadURL,
headers: {
'Content-Type': contentType,
},
data: blob,
})
.success(function(data) {
console.log('アップロード完了 data:',data);
callback();
})
.error(function(data, status) {
console.log('アップロード失敗 status:',status);
console.log('アップロード失敗 data:',data);
});
})
.error(function(data, status) {
console.log('get upload url error status:',status);
console.log('get upload url error data:',data);
});
}