前言
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,然后再通过函数计算来处理相关请求。