API网关 - POST multipart/form-data

49

看起来我的问题可能有点类似这个

我在我的API网关内部有一个API,并通过代理HTTP到POST的multipart/form-data文件端点。

如果我直接调用HTTP端点(不通过API网关)-使用postman,它会按预期工作,但是使用API网关端点(通过postman)失败了。

我已经比较了两个请求(通过fiddlerCloudWatch日志),它们似乎是相同的:

直接调用API的请求(正常工作):

POST https://domainname/api/v1/documents HTTP/1.1
Host: api.service
Connection: keep-alive
Content-Length: 202
Authorization: AuthToken
Postman-Token: a75869d6-1d64-6b9f-513d-a80ac192c8e1
Cache-Control: no-cache
Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
docMetaInfo: some extra data needed
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryB85rsPlMffA2fziS
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8

------WebKitFormBoundaryB85rsPlMffA2fziS
Content-Disposition: form-data; name=""; filename="Test.txt"
Content-Type: text/plain

This is a test Text File
------WebKitFormBoundaryB85rsPlMffA2fziS--

API 网关的请求(不起作用):

POST https://GATEWAY_domainname/api/v1/documents HTTP/1.1
Host: api-Gateway.service
Connection: keep-alive
Content-Length: 202
Authorization: AuthToken
Postman-Token: e25536fa-3dfa-ddcb-8ca6-3f3552d2bc40
Cache-Control: no-cache
Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
docMetaInfo: some extra data needed
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarybX9MyWBsuLGm6QIC

x-api-key: *********************
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8

------WebKitFormBoundarybX9MyWBsuLGm6QIC
Content-Disposition: form-data; name=""; filename="Test.txt"
Content-Type: text/plain

This is a test Text File
------WebKitFormBoundarybX9MyWBsuLGm6QIC--

我已经尝试了一些网关方面的事情,包括更改Integration Request以映射相同内容类型的新主体,但没有成功。

据我所知,我应该只需要passthrough这个调用,因此它变得有点令人困惑 - 不应该需要数据操作/拦截吗?

我收到的错误是400-错误请求(抱怨找不到file),但是正如您在请求中看到的那样,它确实存在。

有什么想法吗?

编辑 来自CloudWatch的日志,位于同一APIGateway POST上

enter image description here

错误仍为400-未找到文件


你好,我遇到了完全相同的问题。目前还没有找到解决方案。你有找到什么吗?我在AWS API网关中使用了二进制类型,但仍然存在同样的问题。我没有使用Lambda,而是直接使用API网关到HTTP代理穿透。 - Mihir Shah
@MihirShah - 是的,正如评论中提到的那样。我通过使用 S3 签名 URL 来解决了这个问题——它们简单易用,事后看来非常有用。或者,您可以尝试下面的其他答案之一,但是我认为使用 S3 与此功能很匹配。 - Hexie
6个回答

58

API Gateway目前不支持多部分表单数据。这将在未来的开发中考虑到。同时,您需要修改客户端使用多个请求或一个单一的一部分请求。

更新:API Gateway现在支持二进制负载。只需为API定义multipart/form-data作为二进制媒体类型,并直接代理负载到Lambda函数。然后,您可以解析正文以获取文件内容。应该有可用的库来帮助解析多部分正文(例如Node.js中的parse-multipart)。


8
@RyanG-AWS 这不太合理。如果 passthrough 不能将请求透明地传递到后端,那它是做什么用的?(为什么还叫“passthrough”?)这只是一个带有“Content-Type: multipart/form-data”的简单的 POST 请求,称其为“multipart upload”似乎并不是正确的术语。 - Michael - sqlbot
3
我使用了“上传”这个术语,因为多部分表单数据通常包含文件内容作为其中一个内容部分。我已经编辑了帖子以提高清晰度。请记住,API网关不是纯反向代理——即使在“透传”模式下,API网关数据平面也会对请求数据执行操作,并且特定的HTTP功能(例如multipart/form-data)是根据情况实现的。话虽如此,我理解造成的困惑,并认为这是一种限制。我们将考虑优先处理此工作。 - RyanG
3
嗨 @Matt,是的 - 解决此限制最简单的方法是使用AWS SDK和S3签名URL。请参阅以下链接:http://docs.aws.amazon.com/AmazonS3/latest/dev/ShareObjectPreSignedURLDotNetSDK.html 和 http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadObjectPreSignedURLDotNetSDK.html - Hexie
7
包含“multipart/form-data”的二进制媒体类型是否也适用于“multipart/form-data; boundary=90a8997f”? - bckygldstn
8
"API Gateway现在支持二进制负载。只需将“multipart/form-data”定义为二进制媒体类型即可。" 这个文档有记录吗? - Hexie
显示剩余13条评论

53

2
这非常有帮助,谢谢!另外,如果您正在使用Lambda代理集成({proxy+}),则只需要步骤1和4即可。 - lakshminb7
2
值得注意的是,一旦添加了二进制媒体类型,Lambda的“主体”中的内容将被base64编码。 - Danilo Souza Morães
非常感谢,我已经花了过去4个小时来找出原因。 - Sahil Yadav
1
如果您正在使用AWS模板定义Lambda函数,请查看此链接https://github.com/aws/aws-sam-cli/issues/1216中的`anton-khodak`的答案。@Dre的答案是正确的--但是,提供的链接仅描述了如何从AWS控制台设置API Gateway。 - Drew H

6

我在将其集成到我的Tomcat服务器时遇到了同样的问题,我发现需要以下更改才能解决它。

  1. Add Content-Type in your api's HTTP Request Headers in api gateway by console or add it in open api documentation like

    {
        "/yourApi":{
            "post":{
                "operationId":"uploadImageUsingPOST",
                "produces":[
                    "application/json"
                ],
                "parameters":[
                {
                    "name":"Content-Type",
                    "in":"header",
                    "required":false,
                    "type":"string"
                },
                {
                    //Other headers
                }]   
            }
        }
    
  2. Above step also add Content-Type in your api's HTTP Headers of integration request, If not add it there also and add one more header Accept ='/' in api gateway by console or or add it in open api documentation like

    "requestParameters":{
        "integration.request.header.Accept":"'*/*'",
        "integration.request.header.Content-Type":"method.request.header.Content-Type",
        //Other headers
    }
    
  3. Set Content Handling as Passthrough in your api's integration request.

  4. Add multipart/form-data as Binary Media Types in your api's settings through console or by open api documentation

    "x-amazon-apigateway-binary-media-types": [
        "multipart/form-data"
    ]
    
  5. Deploy above changes to desired stage where you are going to upload image as multipart.

API网关将以二进制数组的形式传递您的多部分文件,您仍然可以在控制器中使用@RequestBody MultipartFile multipartFile,并且Spring将为您解析此二进制文件以进行多部分处理。


你在第一点中的JSON格式不正确,一些闭合括号是错误的。 - Suraj Rao

5

你仍然会面临10MB有效载荷大小的问题。 S3是前进的道路-我以艰苦的方式学到了这一点。 - Hexie
重新部署你的 Lambda。以防万一有人像我一样忘了它。:D - Gaurav Jeswani

2

使用AWS API Gateway代理和Lambda进行服务器上的图像上传和检索,以下是上传和检索图像以及通过AWS Gateway和lambda代理+管理二进制数据的代码示例。

点击此处查看


2
似乎有所变化,API Gateway不再对整个Content-Type头值进行严格匹配,因此现在所有与“二进制”支持相关的内容都可以按预期工作。
将您的API设置为POST(或PUT),并将Lambda集成设置为“代理”。转到API的设置中,添加要用作“二进制”的媒体类型。我已经添加了multipart / signed。 实际接收到的媒体类型是: Content-Type:multipart / signed; protocol =“application / pkcs7-signature”; micalg =“sha256”; boundary =“---- 54645645645664564563424768” API GW仍然将其选为“二进制”,并将其作为base64传递给我的Lambda。
在Lambda中,您将会捕获到这个结果:
Context:
{
  "callbackWaitsForEmptyEventLoop": true,
  "logGroupName": "/aws/lambda/api-invoice",
  "logStreamName": "2018/04/27/[$LATEST]3454",
  "functionName": "api-invoice",
  "memoryLimitInMB": "128",
  "functionVersion": "$LATEST",
  "invokeid": "345-49e2-11e8-34-345",
  "awsRequestId": "345-49e2-11e8-34-345",
  "invokedFunctionArn": "arn:aws:lambda:eu-west-1:12345:function:api-invoice"
}
-------
Event:
{
  "resource": "/peppol/as2",
  "path": "/peppol/as2",
  "httpMethod": "POST",
  "headers": {
    "Accept": "*/*",
    "AS2-From": "PEPPOL_AP",
    "AS2-To": "234567890",
    "AS2-Version": "1.1",
    "cache-control": "no-cache",
    "Content-Type": "multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=\"sha256\"; boundary=\"----54645645645664564563424768\"",
    "Date": "Fri, 27 Apr 2018 06:17:10 GMT",
    "Disposition-Notification-Options": "signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional, sha1,md5",
    "Disposition-Notification-To": "ignored@example.com",
    "Host": "123.execute-api.eu-west-1.amazonaws.com",
    "Message-ID": "<456-9d44-4c61-456-456546@172.17.0.3>",
    "MIME-Version": "1.0",
    "Postman-Token": "ert-59c1-45656-94d1-456546",
    "Recipient-Address": "as2s://123.execute-api.eu-west-1.amazonaws.com/dev/peppol/as2",
    "Subject": "234567890;PEPPOL_AP",
    "User-Agent": "PostmanRuntime/7.1.1",
    "Via": "1.1 ert-",
    "X-Amzn-Trace-Id": "Root=1-4556-ertfd6554",
    "X-CLIENT-IP": "172.17.0.1",
    "X-Forwarded-For": "xx.xxx.xx.80",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "queryStringParameters": null,
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "80r6gp",
    "resourcePath": "/peppol/as2",
    "httpMethod": "POST",
    "extendedRequestId": "sdsdd343434=",
    "requestTime": "27/Apr/2018:06:17:11 +0000",
    "path": "/dev/peppol/as2",
    "accountId": "123",
    "protocol": "HTTP/1.1",
    "stage": "dev",
    "requestTimeEpoch": 1524809831262,
    "requestId": "354-49e2-3445-b2ba-535345",
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "sourceIp": "xx.xxx.xx.80",
      "accessKey": null,
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "PostmanRuntime/7.1.1",
      "user": null
    },
    "apiId": "123"
  },
  "body": "VGhpcyBpcyBhbiBTL01/ [snip] /S0NCg==",
  "isBase64Encoded": true
}

没错,现在API网关已经支持二进制数据了。然而问题在于,请求/负载长度仍然存在限制,对于二进制文件来说这是个问题。虽然它能处理小文件,但我仍认为S3签名URL是处理这种情况的最佳实践方式。 - Hexie
2
是的,API GW 对于任何请求都有 10MB 的限制。Lambda 的最大限制是 6MB,但我认为它已经被移除或增加了,因为我们已经通过 Lambda 处理了大于 6MB 的文件... - Anders
1
正确 - 现在将Lambda超时时间增加到15分钟(供参考) - Hexie
@Hexie,对于API的客户端来说,预签名URL是一个两步骤的过程吗?调用者需要调用API获取预签名URL,然后使用该URL上传文件吗? - LP13
@LP13 是的,这正是它的工作方式。他们获取一个预签名URL,然后用它来进行POST /工作请求。这是一个非常快速的过程,并避免了使用其他方法会遇到的任何限制。 - Hexie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接