Try   HackMD

Upload Multiple Files

from Javascript to Python

一般來說,要從前端上傳一個檔案到後端,最簡單的做法是:
<input type="file">拿到一個檔案的Blob
application/octet-stream直接往後端送,後端收到就會是一個二元字串,可以直接使用。

但這樣有個前提是,你一次只上傳一個檔案

在現實情況很常有需要送很多資料的情況,這時候不是放在query string就是放在HTTP header;
若是資料很多的情況,這兩種做法都不現實,勢必變成需要夾在request body和檔案一起送。

常見的格式是JSON,但Javascript要如何處理把檔案轉成JSON?

  1. 先用FileReader讀成ArrayBuffer
function getArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.addEventListener('load', () => { resolve(reader.result); }); reader.readAsArrayBuffer(file); }) }
  1. 再將ArrayBuffer轉成JSON字串,此時的字串是整數序列,例如:[1,2,3]
var body = JSON.stringify({ context: Array.from(new Uint8Array(arrayBuffer)), });
  1. 傳到後端就可以當成JSON解開,但是還需要做些手腳將整數序列倒轉回二元字串
context = request_body['context'] file_str = bytearray(context)
  1. 這樣就可以正常使用了,例如寫回檔案
with open('/tmp/context', 'wb') as f: f.write(file_str)

Append ArrayBuffer

在處理檔案上傳的時候,通常都會需要計算integrity,例如MD5。
在Javascript的世界目前SparkMD5算是一個常見的套件。
為了計算加速,要把大的檔案切成小的chunk,這時就會產生一個問題:

ArrayBuffer是immutable,不能被更改

這樣要如何組回一個完整的ArrayBuffer後送?

以下是一個例子:

const appendBuffer = (buffer1, buffer2) => { var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); tmp.set(new Uint8Array(buffer1), 0); tmp.set(new Uint8Array(buffer2), buffer1.byteLength); return tmp.buffer; };

Python unittest

另外一個問題是,在Python端,要如何產生用於unittest的受試JSON?
要做的事情就和Javascript有點相似,將檔案讀入然後轉成整數序列:

with open('tests/fake_context', 'rb') as f: context = f.read() fake_req = { 'context': list(bytearray(context)) }

這樣一個前端到後端完整的故事就搭上了,只能說,前端的水好深!