为什么Apache/WSGI将HEAD映射到GET?如何加速Flask中的HEAD?
这里有一个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 个回答
正如之前提到的,mod_wsgi把HEAD请求转换成GET请求的问题已经有很好的解释,具体可以参考以下链接:
特别是在那篇博客中提到,如果你设置了Apache的输出过滤器,并且这个过滤器需要从你的WSGI应用中获取相同的输出,无论是对同一个网址的GET请求还是HEAD请求,那么mod_wsgi就不会相信你的应用能正确处理这个请求,它会把HEAD请求转换成GET请求,以确保Apache的输出过滤器能正常工作。
如果你不在乎HEAD请求和GET请求返回的响应头不一样,这样就违反了HTTP标准对HEAD请求的要求,那么只需确保你没有配置任何Apache的输出过滤器,这样mod_wsgi就不会再转换请求类型,你可以随意修改请求。
要从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处理程序的问题,这篇博客提供了一些线索,解释了为什么会出现这种情况。