前言
web 開發中我們經常會允許使用者通過 HTTP POST 請求上傳文檔到伺服器,如何使用函數計算來做檔案上傳服務呢?下面我們使用 nodejs 來實作一個檔案上傳的案例:
我們知道浏覽器中上傳文檔通常會使用
multipart
form-data
來多檔案同時上傳檔案。
例如,我們可以使用curl來做這個測試模拟上傳兩個檔案:
test.txt
和
index.js
curl -v --request POST --header "Content-Type:multipart/form-data" --form upload=@"test.txt" --form upload=@"index.js" https://example.com/upload/
格式分析
為了實作這個上傳服務,我們先來了解一下 multipart 的 HTTP header 及 body 格式:
為了簡單起見,我們使用一個檔案内容為
111111\n
的文本檔案
test.txt
通過上述指令,我們可以看到 request 的 header 部分,會多出一個類似下述的 header:
Content-Type:multipart/form-data; boundary=------------------------7b433b8e13cbec1c
而 POST body 則大緻如下:
"--------------------------7b433b8e13cbec1c\r\nContent-Disposition: form-data; name=\"upload\"; filename=\"test.txt\"\r\nContent-Type: text/plain\r\n\r\n111111\n\r\n--------------------------7b433b8e13cbec1c--\r\n"
我們可以認為 body 被分成了多個片,每個片使用 header 中的 boundary 來做前後分隔,中間的内容則包含了這個分片的各種屬性。
實作
安裝第三方庫
接下來,我們使用一個三方 parser 庫
parse-multipart來解析 POST body 的資料。
npm install parse-multipart
函數實作示例
我們在代碼目錄新增
index.js
粘貼以下内容:
// curl -v --request POST --header "Content-Type:multipart/form-data" --form upload=@"test.txt" https://<your-endpoint>/2016-08-15/proxy/test_service/file-upload/
var getRawBody = require('raw-body');
// see https://www.npmjs.com/package/parse-multipart
// npm install parse-multipart
var multipart = require('parse-multipart');
// regexp to parse boundary from HTTP header
var RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i
module.exports.handler = function(req, resp, context) {
console.log('file upload');
var params = {
path: req.path,
queries: req.queries,
headers: req.headers,
method : req.method,
requestURI : req.url,
clientIP : req.clientIP,
}
getRawBody(req, function(err, body) {
for (var key in req.queries) {
var value = req.queries[key];
resp.setHeader(key, value);
}
params.body = body.toString();
var m = RE_BOUNDARY.exec(req.headers['content-type'])
var boundary = m[1] || m[2]
params.boundary = boundary
var parts = multipart.Parse(body, boundary);
params.parts = parts
resp.send(JSON.stringify(params, null, ' '));
});
}
建立函數後,我們可以為這個函數建立
HTTP trigger,并為選擇
POST
作為可接受 HTTP Method。
測試
建立完成後,我們可以使用上面的
curl
指令來做測試:
curl -v --request POST --header "Content-Type:multipart/form-data" --form upload=@"test.txt" https://<endpoint>/2016-08-15/proxy/test_service/file-upload/
測試結果大緻如下:
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate: *.fc.aliyuncs.com
* Server certificate: GlobalSign Organization Validation CA - SHA256 - G2
* Server certificate: GlobalSign Root CA
> POST /2016-08-15/proxy/test_service/file-upload/ HTTP/1.1
> Host: ************.cn-shanghai.fc.aliyuncs.com
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 195
> Expect: 100-continue
> Content-Type:multipart/form-data; boundary=------------------------7b433b8e13cbec1c
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Access-Control-Expose-Headers: Date,x-fc-request-id,x-fc-error-type,x-fc-code-checksum,x-fc-invocation-duration,x-fc-max-memory-usage,x-fc-log-result,x-fc-invocation-code-version
< Content-Disposition: attachment
< Content-Length: 1089
< Content-Type: application/octet-stream
< X-Fc-Code-Checksum: 8133380019009032134
< X-Fc-Invocation-Duration: 4
< X-Fc-Invocation-Service-Version: LATEST
< X-Fc-Max-Memory-Usage: 17.31
< X-Fc-Request-Id: b227b724-b14b-6413-22be-c868b395c732
< Date: Tue, 04 Jun 2019 03:41:04 GMT
<
{
"path": "/",
"queries": {},
"headers": {
"accept": "*/*",
"content-length": "195",
"content-type": "multipart/form-data; boundary=------------------------7b433b8e13cbec1c",
"expect": "100-continue",
"user-agent": "curl/7.54.0"
},
"method": "POST",
"requestURI": "/2016-08-15/proxy/test_service/file-upload/",
"clientIP": "123.123.123.123",
"body": "--------------------------7b433b8e13cbec1c\r\nContent-Disposition: form-data; name=\"upload\"; filename=\"test.txt\"\r\nContent-Type: text/plain\r\n\r\n111111\n\r\n--------------------------7b433b8e13cbec1c--\r\n",
"boundary": "------------------------7b433b8e13cbec1c",
"parts": [
{
"filename": "test.txt",
"type": "text/plain",
"data": {
"type": "Buffer",
"data": [
49,
49,
49,
49,
49,
49,
10
]
}
}
]
}
我們可以看到
parts
數組中的資料,其中
data
為一個
Buffer
位元組數組
[49, 49, 49, 49, 49, 49, 10]
,轉成
string
即
"111111\n"
使用限制
由于目前函數計算對于調用請求有最多 6MB 的大小限制,如果上傳需要處理大檔案,請先上傳到 OSS bucket,然後再通過函數計算來處理相關請求。
更多參考
- 如何安裝第三方庫
- 如何建立函數
- 如何建立 HTTP trigger
- node.js 三方庫
- multipart/form-data rfc2388