为什么Apache/WSGI将HEAD映射到GET?如何加速Flask中的HEAD?

3 投票
2 回答
3130 浏览
提问于 2025-04-17 22:34

这里有一个Flask应用,可以通过命令行或Apache/WSGI来运行:

import flask
app = flask.Flask(__name__)

LENGTH = 1000000                # one million

@app.route('/', methods=['HEAD'])
def head():
    return 'x' * LENGTH         # response body isn't actually sent

@app.route('/', methods=['GET'])
def get():
    import random
    return ''.join(str(random.randint(0,9)) for x in range(LENGTH))

if __name__ == '__main__':
    app.run()                   # from command-line
else:
    application = app           # via Apache and WSGI

这个应用会返回一百万个随机数字。GET请求需要花费不少时间来处理,但HEAD请求应该能几乎立即返回。这只是一个示例;真正的应用可能会涉及到生成较大且处理较慢的GET请求响应,但这些响应的大小是预先确定的,可以通过HEAD请求快速查询到。(还有一种情况:我想把请求重定向到预签名的Amazon S3网址,而HEAD和GET方法的签名方式是不同的。)

问题#1)当我从命令行运行Flask应用时,HEAD请求会触发head函数,正如预期的那样;但当我通过Apache/WSGI运行时,它却触发了get函数。这是为什么呢?我该如何解决这个问题,以实现我想要的行为?

问题#2)为什么我不能直接返回app.make_response('', 200, {'Content-Length':LENGTH}),而是要为HEAD请求创建一个虚假的响应(分配一大堆内存)呢?

我猜测这些问题是由于一个出于好意的尝试,确保HEAD请求总是与相应的GET请求一致。所以:

猜测#1)可能是Apache或WSGI内部把HEAD请求重写成了GET请求。

猜测#2)Flask可能不信任我手动设置Content-Length头,而是用实际的响应体长度来重写它……即使对于HEAD请求,这个长度实际上应该是空的。

我是不是理解错了什么?有没有什么建议可以让我更快地处理HEAD请求,最好是不用慢慢生成一个大响应体,仅仅是为了设置Content-Length头?

2 个回答

3

正如之前提到的,mod_wsgi把HEAD请求转换成GET请求的问题已经有很好的解释,具体可以参考以下链接:

特别是在那篇博客中提到,如果你设置了Apache的输出过滤器,并且这个过滤器需要从你的WSGI应用中获取相同的输出,无论是对同一个网址的GET请求还是HEAD请求,那么mod_wsgi就不会相信你的应用能正确处理这个请求,它会把HEAD请求转换成GET请求,以确保Apache的输出过滤器能正常工作。

如果你不在乎HEAD请求和GET请求返回的响应头不一样,这样就违反了HTTP标准对HEAD请求的要求,那么只需确保你没有配置任何Apache的输出过滤器,这样mod_wsgi就不会再转换请求类型,你可以随意修改请求。

2

要从Flask创建一个完整的响应,你可以这样做:

@app.route('/', methods=['HEAD'])
def head():
    response = Response()
    response.headers.add('content-length', LENGTH)
    return response

这样做之后,你会得到类似这样的结果:

Connected to localhost.
Escape character is '^]'.
HEAD / HTTP/1.1
Host: localhost

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
content-length: 1000000
Server: Werkzeug/0.9.4 Python/2.7.6
Date: Sun, 16 Mar 2014 22:59:16 GMT

这个测试是用标准的运行方式进行的,而不是通过wsgi,但这应该没有太大区别。

关于Apache/WSGI强制使用get处理程序的问题,这篇博客提供了一些线索,解释了为什么会出现这种情况。

可以参考:Flask/Werkzeug 如何将HTTP内容长度头附加到文件下载中

撰写回答