LoginSignup
4
5

More than 5 years have passed since last update.

FormDataを力づくで実装

Last updated at Posted at 2016-10-13

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にすればよかったなぁ。

4
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5