blog

multipart/form-dataを深く掘り下げる

基本的な文字列型であれば、www-form-で転送できますが、-dataの威力はバイナリファイルを転送できることなので、バイナリファイルを含む場合の扱い方を見てみましょう。ファイルタイプの入力を追加し...

Aug 21, 2020 · 4 min. read
シェア

フロントエンド開発において、フォーム、つまりmultipart/form-dataに遭遇することは避けられません:

  • HTTP multipart/form-dataを転送する方法
  • サーバ側でmultipart/form-dataを解析する方法。
  • ブラウザがどのようにmultipart/form-dataをアセンブルするか

簡単なフォームを見てみましょう:

<form action="/submit" method="POST" enctype="multipart/form-data">
 <input type="text" name="username"><br>
 <input type="text" name="password"><br>
 <button> </button> 
</form>

送信するときは、ブラウザのウェブリクエストを見てください:

リクエストヘッダ

POST /submit HTTP/1.1
Host: localhost:3000
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------340073633417401055292887335273
Content-Length: 303

依頼主:

-----------------------------340073633417401055292887335273
Content-Disposition: form-data; name="username"
 
-----------------------------340073633417401055292887335273
Content-Disposition: form-data; name="password"
123456
-----------------------------340073633417401055292887335273--

これはmultipart/form-dataの転送プロセスですが、これには3つの大きな穴があります:

  • リクエストヘッダContent-Typeの境界セパレータは、リクエストボディで使われるセパレータより2本小さい。

    リクエストヘッダからセパレータを取った後、リクエストボディを分割するために必ず2つの - を追加してください。

  • リクエストヘッダContent-Lengthの改行が "Ъ "ではなく "Ъ "になっています。

    リクエスト・ボディの本当の顔は以下の文字列です: "-----------------------------340073633417401055292887335273rnContent-Disposition: form-data; name="ユーザ名"୧⃛(๑⃙⃘⁼̴̀꒳⁼̴́๑⃙⃘)r Zhang San -----------------------------340073633417401055292887335273 Content-Disposition: form-data; name="password" \ n123456 -----------------------------340073633417401055292887335273-- "

  • リクエストヘッダのContent-Lengthの値は、文字列ではなくバイト長を表します。

    console.log('a1'.length) // 2
    console.log(Buffer.from('a1').length) // 2
    console.log(' .length) // 2
    console.log(Buffer.from('張さん').length) // 6
    
    -----------------------------114007818631328932362459060915
    Content-Disposition: form-data; name="avatar"; filename="1.jpg"
    Content-Type: image/jpeg
    xxxxxxファイルxxxxxxのバイナリデータ
    

multipart/form-dataの強力な点はバイナリファイルを送信できることです。ファイルタイプの入力を追加し、アバターとしてイメージをアップロードし、リクエストボディの余分な部分があることに気づきます:

const http = require('http')
const fs = require('fs')
http
 .createServer(function (req, res) {
 // content-typeヘッダーをmultipart/form-data形式で取得する。; boundary=--------------------------754404743474233185974315
 const contentType = req.headers['content-type']
 const headBoundary = contentType.slice(contentType.lastIndexOf('=') + 1) // ヘッダーの境界部分をインターセプトする
 const bodyBoundary = '--' + headBoundary // ボディ内の本当のセパレータは、その前にある2つの-である。
 const arr = [], obj = {}
 req.on('data', (chunk) => arr.push(chunk))
 req.on('end', function () {
 const parts = Buffer.concat(arr).split(bodyBoundary).slice(1, -1) // セパレータに従って分割する
 for (let i = 0; i < parts.length; i++) {
 const { key, value } = handlePart(parts[i])
 obj[key] = value
 }
 res.end(JSON.stringify(obj))
 })
 })
 .listen(3000)

ファイルタイプ部分は文字列形式とは異なり、ヘッド部分には2つのヘッダーフィールドがあり、Content-Typeヘッダーが1つ多く、Content-Dispositionヘッダーにはファイル名フィールドが1つ多く、ボディ部分はファイルのバイナリデータであることがわかります。

これらのルールがわかれば、サーバー側でmultipart/form-dataをデコードすることができます:

function handlePart(part) {
 const [head, body] = part.split('

') // buffer  
 const headStr = head.toString()
 const key = headStr.match(/name="(.+?)"/)[1]
 const match = headStr.match(/filename="(.+?)"/)
 if (!match) {
 const value = body.toString().slice(0, -2) // の最後でバッファを分割する。 
  
 return { key, value }
 }
 const filename = match[1]
 const content = part.slice(head.length + 4, -2) // ファイルのバイナリ部分は、head + 

 最後の 

 fs.writeFileSync(filename, content)
 return { key, value: filename }
}

キーの1つは、handlePartの部分で、つまり、個々の処理の各部分を分離するために、ファイルに保存するバイナリであれば、キーと値のペアを返す文字列です:

Buffer.prototype.split = function (sep) {
 let sepLength = sep.length, arr = [], offset = 0, currentIndex = 0
 while ((currentIndex = this.indexOf(sep, offset)) !== -1) {
 arr.push(this.slice(offset, currentIndex))
 offset = currentIndex + sepLength
 }
 arr.push(this.slice(offset))
 return arr
}

これはバッファの分割を伴いますが、nodejsは分割メソッドを提供していません:

Read next

axiosラッピングapiインタフェース2

ダウンロードaxiosutilフォルダにajax.jsファイルmain.jsを作成する設定4では、axiosが設定され、次のデータインターフェイスを取得するメソッドを記述するには、apiフォルダにapi.jsファイルを作成する必要があります、ファイル内に記述するコードは次のとおりです。

Aug 21, 2020 · 1 min read