【问题标题】:Does AWS apigateway change http body? How can I stop it from doing this?AWS apigateway 是否更改 http 正文?我怎样才能阻止它这样做?
【发布时间】:2019-12-16 01:42:58
【问题描述】:

AWS apigateway 会更改 http 正文吗?我怎样才能阻止它这样做?

我的申请:

(1) 前端“UI”使用“POST 方法”发送“http 请求”,在“body”中通过“form-data”包含“zip 文件”。

(2) AWS“apigateway”接收到这个请求并转发给“Lambda Proxy”

(3)用python编码实现的AWS“Lambda”接收到这个请求,将这个zip文件解压到一个临时文件夹中。

我面临的问题: (1) 和 (2) 工作正常,但在 (3) 中,lambda 的 pythong 程序无法解压文件。

我的发现:

似乎从“UI”发送时,正文包含 zip 文件的二进制数据 如下:

"PK\x03\x04\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ x06\x00\x00\x00x2.txtPK\x03\x04\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ x00\x00\x06\x00\x00\x00x1.txtPK\x01\x02\x14\x00\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00\x00\ x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00x2.txtPK\x01\x02\x14\x00\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00$\x00\x00\x00x1.txtPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00h\x00\x00\x00H\x00\x00\x00\x00\ x00"

但是在 (3) 处的 lambda 中的 python 代码,如果我们只是简单地返回如下响应:

response = {
    "statusCode": 200,
    "headers": {
        "lambda-response": str(event["body"])
    },
    "body": "",
    "isBase64Encoded": False
}

返回响应

会发现body中的二进制数据, 好像apigateway改了内容

来自:

"PK\x03\x04\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ x06\x00\x00\x00x2.txtPK\x03\x04\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ x00\x00\x06\x00\x00\x00x1.txtPK\x01\x02\x14\x00\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00\x00\ x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00x2.txtPK\x01\x02\x14\x00\n\x00\x00\x00\x00\x00\xd6;TO\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00$\x00\x00\x00x1.txtPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00h\x00\x00\x00H\x00\x00\x00\x00\ x00"

进入:

"PK\u0003\u0004\n\u0000\u0000\u0000\u0000\u0000\ufffd;TO\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 u0006\u0000\u0000\u0000x2.txtPK\u0003\u0004\n\u0000\u0000\u0000\u0000\u0000\ufffd;TO\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 u0000\u0000\u0006\u0000\u0000\u0000x1.txtPK\u0001\u0002\u0014\u0000\n\u0000\u0000\u0000\u0000\u0000\ufffd;TO\u0000\u0000\u0000\u00000 u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 \u0000\u0000\u0000\u0000\u0000\u0000\u0000x2.txtPK\u0001\u0002\u0014\u0000\n\u0000\u0000\u0000\u0000\u0000\ufffd;TO\u0000\u0000\u00000 \u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 \u0000\u0000\u0000$\u0000\u0000\u0000x1.txtPK\u0005\u0006\u0000\u0000\u0000\u0000\u0002\u0000\u0002\u0000h\u0000\u0000\u00000\u0000H\u0000 u0000\r\n"

这很奇怪,我该怎么做才能阻止这种情况?

(2019/12/17 更新)低于我正在使用的 lambda 代码。

import json # to decode json
import os   # file IO
import shutil   # file IO (use this to recursively force remove a directory)

print('Loading function')

def decompress_zip_file(src_file_path, dest_dir_path):
    '''
        Decompress a zip file into a directory.

    Args:
        src_file_path (Srting): source zip file's path.
        dest_dir_path (Srting): the destination of the output directory.
    Returns:
        isSuccess (bool): the operation is successful or not.
    '''
    error_msg = "Nothing."
    try:
        if(os.path.isdir(dest_dir_path)):
            shutil.rmtree(dest_dir_path)

        with zipfile.ZipFile(src_file_path, 'r') as zip_ref:
            zip_ref.extractall(dest_dir_path)
    except Exception as ep:
        error_msg = "Error in decompress_zip_file(), ep={}:{}".format(type(ep).__name__, str(ep))
        print(error_msg)
        return (False, error_msg)

    return (True, error_msg)


def decompress_zip_file_from_content_in_binary(src_file_in_binary, dest_dir_path):
    '''
        Decompress a zip file content into a directory.

    Args:
        src_file_in_binary (byte array): source zip file's content in binary format.
        dest_dir_path (Srting): the destination of the output directory.
    Returns:
        isSuccess (bool): the operation is successful or not.
    '''

    # write the obtained binary data into a tmp zip file
    tmp_file_path = "/tmp/tmp.zip"
    if(os.path.isfile(tmp_file_path)):
        os.remove(tmp_file_path)

    output_file = open(tmp_file_path, 'wb')
    output_file.write(src_file_in_binary)
    output_file.close()

    (isSuccess, error_msg) = decompress_zip_file(tmp_file_path, dest_dir_path)

    return (isSuccess, error_msg)

def convert_from_http_body_encoding_to_local_binary(extracted_file_from_http_body_str):
    '''
        Extract the file (in binary string format) from event['body'] encoding to local binary encoding.

    Args:
        extracted_file_from_http_body_str (string): the event['body'] file (in binary string format),.
    Returns:
        zipfile_binary1 (binary array): the conversion result.
    '''
    zipfile_binary1 = bytes(extracted_file_from_http_body_str, encoding = "ascii") # convert into a zipfile in binary format    

    return zipfile_binary1

def extract_zipfile_binary_from_body(body_str):
    '''
        Extract the zipfile (in binary format) from event['body'] string.

    Args:
        body_str (string): the event['body'] string.
    Returns:
        (binary array): the conversion result.
    '''

    retValue = ""

    tmpArray = body_str.split("application/zip") # split the content based on MIME part field data; cut the head
    if(len(tmpArray) > 1):
        retValue += "entered-Lv1."
        tmpArray = tmpArray[1].split("PK") # split the content based on zip file header.
        if(len(tmpArray) > 1):
            retValue += "entered-Lv2."
            zipfile_str = "PK" + 'PK'.join(tmpArray[1:]) # add back the zip file header            
            tmpArray = zipfile_str.split("------WebKitFormBoundary") # split the content based on MIME part field data; cut the tail
            if(len(tmpArray) > 1):                
                zipfile_str = tmpArray[0]                            
                zipfile_binary = convert_from_http_body_encoding_to_local_binary(zipfile_str)
                retValue = zipfile_binary

    return retValue

def handler(event, context):
    '''Provide an event that contains the following keys:
      - operation: one of the operations in the operations dict below
      - payload: a parameter to pass to the operation being performed
    '''    

    # set the mapping table for "operation" x "return value"
    operations = {        
        'unzip': lambda x: decompress_zip_file_from_content_in_binary(**x), # unzip an uploaded file
        'ping': lambda x: 'pong' # respond to ping req.
    }

    # because we use "Lambda Proxe", means we have api-gateway forward the whole packet without resolving it for lambda.
    event_headers = event["headers"] 
    operation = event_headers['operation']    
    event_body = event["body"] 

    if(operation == 'unzip'):
        src_file_in_binary = extract_zipfile_binary_from_body(event_body)
        payload_json = {}
        payload_json['src_file_in_binary'] = src_file_in_binary
        payload_json['dest_dir_path'] = "/tmp/tmp_zipfile_output"
        event_headers["payload"] = payload_json        

    if operation in operations:
        responseBody = operations[operation](event_headers.get('payload'))

        response = {
            "statusCode": 200,
            "headers": {
                "lambda-response": str(responseBody) # the api-gateway will forward the header to the front end.
            },
            "body": "",
            "isBase64Encoded": False
        }

        return response

    else:
        raise ValueError('Unrecognized operation "{}"'.format(operation))

【问题讨论】:

  • 你能分享你的 lambda 代码吗
  • @ArunK 感谢您指出这一点。我已将其添加到帖子中。
  • 你怎么知道api网关改变了值,你在哪里看到改变的值?
  • in "zipfile_binary1 = bytes(extracted_file_from_http_body_str, encoding = "ascii")" 由于字符 "/ufffd" 导致转换失败。所以我修改了代码,让它返回extracted_file_from_http_body_str。这就是我获得上述结果的方式。

标签: aws-lambda http-post aws-api-gateway multipartform-data unzip


【解决方案1】:

以下是来自 AWS 支持的回复。 LGTM。将其留在这里,以便人们将来可以看到此问题的解决方案。

=====================以下是来自 AWS 支持的回复 ==================

嗨,

感谢您联系 AWS 高级支持。我是 Jyoti,今天我将协助您处理此案。

从案例对应,了解到您担心 API Gateway 修改 代理到您的 Lambda 函数之前的二进制数据有效负载。如果我的理解有误,请纠正我。

预期行为:

API 网关确实将二进制数据负载修改为 UTF-8 编码的 JSON 字符串,如果 API 配置为默认设置。因此,这是一种预期的行为。
请注意,根据 [1],我们必须配置 API 以支持二进制有效负载 我们在 API Gateway 中的 API。 API Gateway 不能按原样发送二进制文件,因为它必须发送 lambda 代理的 JSON 主体。因此,默认情况下,它以 UTF-8 编码数据/有效负载。

解决方案:

为了克服上述挑战,我们需要添加所需的 二进制媒体类型(在这种情况下为 application/zip)到 binaryMediaTypes 列表 在 RestApi 资源的设置页面上。有关如何实现的更多信息 这个,请参考这里 --> [2]。如果未定义此属性,则有效负载 如 [1] 中所述,作为 UTF-8 编码的 JSON 字符串处理。

这就是您请求中的文件看起来采用 UTF-8 编码的原因。配置 API 后, Lambda 接收到的事件将是 Base64 编码的字符串。

如果要对这个对象(编码后的请求体或'event["body"]')进行操作, 然后您可以通过以下操作将 base64 编码的字符串解码为其原始二进制形式 以下几行(在 python 运行时的情况下):

import base64
coded_string = str(event["body"])
base64.b64decode(coded_string)

疑难解答:

我尝试在我的环境中复制您的设置。而不是应用程序的前端“UI”, 我使用 Postman 作为客户端,而其余设置(API 网关和 Lambda)是类似的。 我正在从 Postman 向我的 API 发出 POST 请求,请求标头为“Content-Type”和“Accept”, 两者都设置为值“application/zip”,这是正在发送的二进制媒体类型,并且 在响应中也可以预期。我的 API 已配置为支持二进制媒体类型 在请求正文中传递。我在 API 的 binaryMediaTypes 列表中添加了“application/zip”。 最后,在 Lambda 函数中,我正在解码 base64 编码的请求正文(即 event["body"]) 使用 base64 库(在 python 中)将其转换为原始二进制形式。

如果您仍想通过返回二进制文件来确认请求的表单数据的一致性 回复中的数据,可以参考下面的sn-p:

response {
        'isBase64Encoded': True,                            #Ensure the body is base encoded 
        'statusCode': 200,
        'headers': { "Content-Type": "applicaiton/zip" },   #Define the Content-Type
        'body': event["body"]                               #Response Body returns the Base64-encoded value    
    }

我们将 isBase64Encoded 参数设置为 True,API 网关会自动解码 响应正文取决于 Content-Type(即二进制数据/媒体类型) 客户端(在我的情况下是邮递员)设置为接收(即应用程序/zip)。请注意,“接受” 我在标头中发送的标头是为了验证响应正文是否包含二进制文件 数据类型,请求的对象。

上面的响应正文与第一次发送的请求正文二进制数据相同 通过 API,在我的设置中。

希望我已经解决了您的疑虑。但是,如果您仍然需要实施方面的帮助, 请再次与我们联系,我很乐意为您提供帮助。

References: 
=-=-=-=--=-=-=-=-=-=
[1] Support Binary Payloads in API Gateway: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html 
[2] Enable Binary Support Using the API Gateway Console: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-configure-with-console.html 

最好的问候,

Jyoti Prakash P. 亚马逊网络服务

2019/12/20 更新

我意识到我的内容类型实际上是 multipart 而不是 application/zip,所以我再次修改了设置,然后它就起作用了。 以下是来自 AWS 支持的帮助。非常感谢他们的帮助。

嗨,

非常感谢您详细说明您的应用程序流程和日志。我现在了解您的 HTTP 请求标头“内容类型”设置为“多部分/表单数据”。我同意,对于上传文件的 Web 表单,将内容类型设置为表单数据是很常见的,AWS API Gateway 确实支持它。您想知道是否可以在不更改前端代码的情况下阻止 UTF-8 编码。如果我的理解有误,请纠正我。

为了便于讨论,我想将 HTTP 请求和响应的故障排除方法分开。

对于 API 的请求:

请在 API Gateway 控制台的“API 设置”页面中的 binaryMediaType 列表中添加“multipart/form-data”作为值之一。您无需更改代码或 HTTP 请求或其任何标头。请注意,在 API Gateway 中处理二进制媒体/数据时,HTTP 请求 Content-Type 标头必须与 binaryMediaType 列表中的值匹配。

在您的用例中,如果您想在请求的响应中发回二进制媒体、HTTP 请求“Content-Type”和“Accept”标头、API 的 binaryMediaType 值和 HTTP 响应“内容” -Type' 必须全部设置为'multipart/form-data'。我尝试了上述方法,它与邮递员客户端一起为我工作。如果 HTTP 请求“Content-Type”设置为“multipart/form-data”,则 Postman 会自动设置“boundary”指令。因此,您只需在“binaryMediaType”列表中添加“multipart/form-data”。请看一下我的 HTTP 请求,如下:


POST /stg-with-logs HTTP/1.1
Host: <some-api-id>.execute-api.us-east-1.amazonaws.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Accept: multipart/form-data
Cache-Control: no-cache
Postman-Token: 123b64f9-5669-f794-b9df-34a7561e9708

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="File"; filename="archive.zip"
Content-Type: application/zip


------WebKitFormBoundary7MA4YWxkTrZu0gW--

对于来自 API 的响应:

我注意到在查看您的 API 网关日志时,未设置标头“isBase64Encoded”。请设置为真。如果“isBase64Encoded”设置为 true,API Gateway 会自动解码 HTTP 响应正文中的任何 base64 编码字符串。请查看下面我的 lambda 的 HTTP 响应:


(a6729f56-b245-45a4-9ac4-7e00b23c8957) Endpoint response body before transformations: 
{
    "isBase64Encoded": true,
    "statusCode": 200,
    "headers": {
        "Content-Type": "multipart/form-data",
        "Accpet": "multipart/form-data"
    },
    "body": "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5SmxkSW1aV1lHczlSTndPWQ0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJGaWxlIjsgZmlsZW5hbWU9ImFyY2hpdmUuemlwIg0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi96aXANCg0KUEsDBBQAAAAIAKF4kE9/Mo7/XgAAAJcAAAAaABwASGVsbG8tV29ybGQtNjY3MzMxNTI4MS50eHRVVAkAA8ZP910SUPdddXgLAAEEHZHreQTMewNxNY1BDgIxDAPvvIVPOY3SEC+9WCrfJ13EZWTNHKwKkzMmxIp5dpsnFMlqrjzBF/SKxCW2/8dl3ttGGjTqnkdMG+Wwj96jA3/YJsC2QF9iesuLUXPfv80KrpaVYeDjC1BLAQIeAxQAAAAIAKF4kE9/Mo7/XgAAAJcAAAAaABgAAAAAAAEAAACkgQAAAABIZWxsby1Xb3JsZC02NjczMzE1MjgxLnR4dFVUBQADxk/3XXV4CwABBB2R63kEzHsDcVBLBQYAAAAAAQABAGAAAACyAAAAAAANCi0tLS0tLVdlYktpdEZvcm1Cb3VuZGFyeUpsZEltWldZR3M5Uk53T1kNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsgbmFtZT0iVGVzdCBEYXRhIg0KDQpUZXN0aW5nIEJvdW5kYXJ5IGluIG11bHRpcGFydC9mb3JtLWRhdGENCi0tLS0tLVdlYktpdEZvcm1Cb3VuZGFyeUpsZEltWldZR3M5Uk53T1ktLQ0K"
}

除此信函外,我还附上了我的 API Gateway Swagger 文件和 Lambda 函数代码供您参考。该设置对我来说很好,我能够在发出 HTTP 请求时返回二进制有效负载。如果您想在您的环境中对其进行测试,请在 Swagger 文件中设置适当的凭据和 lambda uri。

希望这能解决您的问题。但是,如果问题仍然存在或者您还有其他问题,请再次与我们联系,我们将很乐意为您提供帮助。

要查看此通信中包含的名为“binaryPost-stg-with-logs-oas30-apigateway.yaml,python-binary-response.py”的文件,请使用签名下方给出的案例链接。

最好的问候,

Jyoti Prakash P. 亚马逊网络服务

查看 AWS Support 知识中心,这是一个回答客户有关 AWS 服务问题的文章和视频的知识库:https://aws.amazon.com/premiumsupport/knowledge-center/?icmpid=support_email_category

【讨论】:

  • 是的,它有效。我已经更新了帖子的更多细节。
猜你喜欢
  • 1970-01-01
  • 2018-07-15
  • 2022-01-12
  • 1970-01-01
  • 2022-11-09
  • 1970-01-01
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
相关资源
最近更新 更多