Android4.xとかではHTML5なFormDataは使えない。
バイナリデータを送信したい時どうしようかと探していたけど、一番安定したのがこの方法。
FormDataのMultipartを力づくで実装。
ココを参考に
http://qiita.com/KNaito/items/54b1bf61a3c678ca28b1
ES6でこう書いた。
import Promise from 'es6-promise'
export default class Multipart{
constructor({url}){
super()
this.url = url
this.init()
}
init(){
this.boundary = this.createBoundary()
this.buffer = new Uint8Array(0)
}
send(){
return new Promise((resolve, reject)=>{
var xhr = new XMLHttpRequest();
xhr.open("POST",this.url, true);
xhr.setRequestHeader("Content-type" , this.createHeader())
this.buffer = this.appendBuffer(this.buffer, this.createFooter())
xhr.send(this.buffer)
xhr.onreadystatechange = ()=>{
console.log(xhr.readyState)
if(xhr.readyState === 4 && xhr.status === 200){
if(xhr.status === 200){
const res = JSON.parse(xhr.response)
resolve(res)
}else{
reject(new Error(xhr.status))
}
}
}
})
}
createHeader(){
if(!this.boundary) return false
return `multipart/form-data; boundary=${this.boundary}`
}
createFooter(){
return this.unicode2buffer(`\r\n--${this.boundary}--\r\n`)
}
append(_key, _value){
let buffer = this.unicode2buffer(`--${this.boundary}\r\nContent-Disposition: form-data; name="${_key}";`)
if(typeof _value === 'string'){
buffer = this.appendBuffer(buffer, this.unicode2buffer(`\r\n\r\n${_value}\r\n`) )
}
if(_value instanceof Uint8Array){
buffer = this.appendBuffer(buffer, this.unicode2buffer(` filename="blob"\r\nContent-Type: image/png\r\n\r\n`) )
buffer = this.appendBuffer(buffer, _value)
buffer = this.appendBuffer(buffer, this.unicode2buffer(`\r\n`) )
}
this.buffer = this.appendBuffer(this.buffer, buffer)
}
/*
* ランダムなBoundaryStringを生成 Requestごとに一意である
*/
createBoundary(){
const multipartChars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const length = 16
let boundary = "------WebKitFormBoundary";
for (let i=0; i<length; i++) {
boundary += multipartChars.charAt( Math.floor( Math.random() * multipartChars.length ) );
}
return boundary;
}
unicode2buffer(str){
var n = str.length,
idx = -1,
byteLength = 512,
bytes = new Uint8Array(byteLength),
i, c, _bytes;
for(i = 0; i < n; ++i){
c = str.charCodeAt(i);
if(c <= 0x7F){
bytes[++idx] = c;
} else if(c <= 0x7FF){
bytes[++idx] = 0xC0 | (c >>> 6);
bytes[++idx] = 0x80 | (c & 0x3F);
} else if(c <= 0xFFFF){
bytes[++idx] = 0xE0 | (c >>> 12);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
} else {
bytes[++idx] = 0xF0 | (c >>> 18);
bytes[++idx] = 0x80 | ((c >>> 12) & 0x3F);
bytes[++idx] = 0x80 | ((c >>> 6) & 0x3F);
bytes[++idx] = 0x80 | (c & 0x3F);
}
if(byteLength - idx <= 4){
_bytes = bytes;
byteLength *= 2;
bytes = new Uint8Array(byteLength);
bytes.set(_bytes);
}
}
idx++;
var result = new Uint8Array(idx);
result.set(bytes.subarray(0,idx),0);
return result.buffer;
}
appendBuffer(buf1,buf2) {
var uint8array = new Uint8Array(buf1.byteLength + buf2.byteLength);
uint8array.set(new Uint8Array(buf1),0);
uint8array.set(new Uint8Array(buf2),buf1.byteLength);
return uint8array.buffer;
}
}
RequestBodyの種類によっては動かないパターンもあるので、適宜調整が必要だとおもいます。
BoundaryStringもWebkitって書いてあるし。(なんでもいいのかな?)
あと、ちゃんとしたFormDataのPolyfillにすればよかったなぁ。