无论 Content-Type 标头如何,在 Python Flask 中获取原始 POST 正文

Posted

技术标签:

【中文标题】无论 Content-Type 标头如何,在 Python Flask 中获取原始 POST 正文【英文标题】:Get raw POST body in Python Flask regardless of Content-Type header 【发布时间】:2012-06-15 12:45:57 【问题描述】:

之前,我问过How to get data received in Flask request,因为request.data 是空的。答案解释说request.data 是原始帖子正文,但如果解析表单数据,则为空。如何无条件获取原始帖子正文?

@app.route('/', methods=['POST'])
def parse_request():
    data = request.data  # empty in some cases
    # always need raw data here, not parsed form data

【问题讨论】:

【参考方案1】:

使用request.get_data() 获取原始数据,无论内容类型如何。数据被缓存,后续可以随意访问request.datarequest.jsonrequest.form

如果您首先访问request.data,它将首先调用get_data,并带有一个参数来解析表单数据。如果请求具有表单内容类型(multipart/form-dataapplication/x-www-form-urlencodedapplication/x-url-encoded),则将使用原始数据。在这种情况下,request.datarequest.json 将显示为空。

【讨论】:

这似乎在使用 raven-python (Sentry) 时中断,这里有错误和解决方法:github.com/getsentry/raven-python/issues/457 谢谢。这真的节省了一天。当您需要手动解析原始请求数据时,这是必要的。尤其是当请求是 multipart/form-data 时。【参考方案2】:

request.stream 是 WSGI 服务器传递给应用程序的原始数据流。阅读时不进行解析,尽管您通常需要request.get_data()

data = request.stream.read()

如果之前由request.data 或其他属性读取,则该流将为空。

【讨论】:

感谢您指出如果之前通过 request.data 读取流将是空的!在调试过程中差点让我失望【参考方案3】:

我创建了一个 WSGI 中间件,它存储来自 environ['wsgi.input'] 流的原始正文。我将值保存在 WSGI 环境中,因此我可以在我的应用程序中从request.environ['body_copy'] 访问它。

这在 Werkzeug 或 Flask 中不是必需的,因为request.get_data() 将获取原始数据而不管内容类型如何,但可以更好地处理 HTTP 和 WSGI 行为。

这会将整个正文读入内存,例如,如果发布一个大文件,这将是一个问题。如果缺少 Content-Length 标头,它将不会读取任何内容,因此它不会处理流请求。

from io import BytesIO

class WSGICopyBody(object):
    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):
        length = int(environ.get('CONTENT_LENGTH') or 0)
        body = environ['wsgi.input'].read(length)
        environ['body_copy'] = body
        # replace the stream since it was exhausted by read()
        environ['wsgi.input'] = BytesIO(body)
        return self.application(environ, start_response)

app.wsgi_app = WSGICopyBody(app.wsgi_app)
request.environ['body_copy']

【讨论】:

【参考方案4】:

如果request.headers["Content-Type"]被识别为表单数据,request.data将为空,将被解析为request.form。要获取任何内容类型的原始数据,请使用request.get_data()

request.data 调用request.get_data(parse_form_data=True),导致表单数据的行为不同。

【讨论】:

以上是关于无论 Content-Type 标头如何,在 Python Flask 中获取原始 POST 正文的主要内容,如果未能解决你的问题,请参考以下文章

使用 HttpClient 时如何不排除 Content-Type 中的字符集?

如何在tomcat 8中添加content-Type标头

如何从 HttpClient.PostAsJsonAsync() 生成的 Content-Type 标头中删除 charset=utf8?

如何在使用 Cloud Scheduler 发出的 HTTP POST 请求上添加 JSON 正文?它会添加“Content-Type”:“application/json”标头吗?

如何使用 Spring Boot RestTemplate 将 Microsoft Office Mime 类型作为 Content-Type 标头发送

将标头添加到 ResponseWriter