在Flask或WSGI中打印原始的HTTP请求

74

我正在调试一个由我构建的微控制器,它逐行编写原始的HTTP请求。 我正在使用 Flask 作为后端,并希望以此格式查看整个请求:

GET / HTTP/1.1
Content-length: 123
User-agent: blah
...

我知道Flask基于WSGI。有没有办法让它与Flask一起工作?

7个回答

63

是的,Flask是一个WSGI应用程序,因此将您的应用程序包装在额外的层中记录请求非常容易:

import pprint

class LoggingMiddleware(object):
    def __init__(self, app):
        self._app = app

    def __call__(self, env, resp):
        errorlog = env['wsgi.errors']
        pprint.pprint(('REQUEST', env), stream=errorlog)

        def log_response(status, headers, *args):
            pprint.pprint(('RESPONSE', status, headers), stream=errorlog)
            return resp(status, headers, *args)

        return self._app(env, log_response)

这定义了一个中间件来包装您的Flask应用程序。其优点是它完全独立于Flask,让您可以对输入输出进行无过滤的洞察。

如何应用中间件取决于您使用的确切WSGI服务器;请参阅您的WSGI服务器文档。

当使用内置服务器运行Flask(app.run())时,请执行以下操作:

if __name__ == '__main__':
    app.wsgi_app = LoggingMiddleware(app.wsgi_app)
    app.run()

小小的app.wsgi_app包装操作是将LoggingMiddleware放置在Flask WSGI应用程序周围。

输出结果会被发送到wsgi.error流中;具体将其发送到哪里取决于您的WSGI服务器:mod_wsgi将其放置在Apache错误日志中,而捆绑的Flask服务器则将其打印到stderr中。


我该如何获取此解决方案的请求内容? - Arthur Colombini Gusmão
@ArthurColombiniGusmão:你确定要这样做吗?请求体可能非常大。您还必须使用进行处理,它类似于只能读取一次的文件,并且必须提供新流以替换下一个WSGI组件。 env['wsgi.input'] 是流,您可以将数据复制到 tempfile.SpooledTemporaryFile() 对象,对该文件进行操作,然后返回到开头并将 env['input.stream'] 设置为指向它。 - Martijn Pieters
感谢@Martijn Pieters!也许这不是最好的选择。我的目标是创建一个规范的片段,以便快速包装Flask应用程序,以便在受控环境中轻松调试。有时候,通过使用这样的中间件来调试与REST API接口交互的代码似乎更容易,但我可能会错过现有的解决方案。 - Arthur Colombini Gusmão

47

使用Flask,您可以访问请求对象,其中包含所有HTTP细节:

from flask import request

@app.route('/')
def index():
    print(request.headers)

32
这只打印了标题,没有像指定的那样 HTTP详细信息 打印。使用请求的其他属性也无法帮助(例如数据,表单,值等)。要完整地打印HTTP请求,建议使用WSGI包装,这是@martinpieters答案中提到的方法。 - snowbound
这不是答案。 - Vishrant

34

假设您想要完整的细节信息,

还有另一种方式

@app.route('/')
def index():
    print request.__dict__
    #this prints all variables in `dict` format including `headers`

1
那不会奏效。 headers根本不是一个键; 有包含不同信息的environ键,其中包括标题,但肯定不是完整的原始请求。 - max
4
实际上这似乎是有效的。在导入pprint后,当我执行pprint.pformat(request.__dict__, depth=5))时,我看到environ作为第一个键被打印出来。 - watsonic

18

为什么不呢?

from flask import Flask, request

app = Flask(__name__)

@app.before_request
def log_request():
    app.logger.debug("Request Headers %s", request.headers)
    return None

# The remaining application code.

我已经使用了标题,但您可以使用相同的方法来打印任何请求属性。文档在这里:http://flask.pocoo.org/docs/0.12/api/#flask.Request

另外,您需要设置FLASK_DEBUG = 1才能使Flask.logger.debug起作用,这很好,因为您可以在生产中禁用它。

此致敬意,


13

你可能想要类似这样的东西

print(request.headers)
print(request.cookies)
print(request.data)
print(request.args)
print(request.form)
print(request.endpoint)
print(request.method)
print(request.remote_addr)

我喜欢这个答案,因为它只使用了 Flask 对象。非常容易理解和使用。 - barrypicker
打印(request.body)并添加try...except可能会有很大帮助。 - Mohamed Jaleel Nazir

4

这不使用flask,但是设置一个套接字回声服务器相当简单。

import socket

host = ''
port = 8888
backlog = 5
size = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host,port))
s.listen(backlog)
while 1:
    client, address = s.accept()
    data = client.recv(size)
    if data:
        client.send(data)
    client.close()

一个注意点:如果客户端发送了“Expect: 110-continue”头部,你将无法看到完整的HTTP通信...你应该向客户端发送“100 Continue”,然后再次读取请求体。 - Kuchara

0

谢谢。我尝试了这个,但是在repr(request.headers)中没有看到任何区别。你能详细说明一下吗? - Jérôme

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