天天看點

如何在函數計算中使用 Node.js 處理 multipart 檔案上傳請求

前言

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,然後再通過函數計算來處理相關請求。

更多參考