python - 如何在 Python BaseHTTPRequestHandler 中处理分块编码?
问题描述
我有以下简单的网络服务器,利用 Python 的http
模块:
import http.server
import hashlib
class RequestHandler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_PUT(self):
md5 = hashlib.md5()
remaining = int(self.headers['Content-Length'])
while True:
data = self.rfile.read(min(remaining, 16384))
remaining -= len(data)
if not data or not remaining:
break
md5.update(data)
print(md5.hexdigest())
self.send_response(204)
self.send_header('Connection', 'keep-alive')
self.end_headers()
server = http.server.HTTPServer(('', 8000), RequestHandler)
server.serve_forever()
当我使用 curl 上传文件时,效果很好:
curl -vT /tmp/test http://localhost:8000/test
因为文件大小是预先知道的,curl 会发送一个Content-Length: 5
标头,所以我可以知道我应该从套接字读取多少。
但是如果文件大小未知,或者客户端决定使用chunked
Transfer-Encoding,这种方法就会失败。
可以使用以下命令进行模拟:
curl -vT /tmp/test -H "Transfer-Encoding: chunked" http://localhost:8000/test
如果我从self.rfile
块的过去读取,它将永远等待并挂起客户端,直到它断开 TCP 连接,其中self.rfile.read
将返回一个空数据,然后它会跳出循环。
扩展上述示例以支持chunked
传输编码还需要什么?
解决方案
正如您在Transfer-Encoding的描述中看到的那样,分块传输将具有以下形状:
chunk1_length\r\n
chunk1 (binary data)
\r\n
chunk2_length\r\n
chunk2 (binary data)
\r\n
0\r\n
\r\n
您只需要读取一行,获取下一个块的大小,并同时使用二进制块和后续换行符。
此示例将能够处理带有Content-Length
或Transfer-Encoding: chunked
标头的请求。
from http.server import HTTPServer, SimpleHTTPRequestHandler
PORT = 8080
class TestHTTPRequestHandler(SimpleHTTPRequestHandler):
def do_PUT(self):
self.send_response(200)
self.end_headers()
path = self.translate_path(self.path)
if "Content-Length" in self.headers:
content_length = int(self.headers["Content-Length"])
body = self.rfile.read(content_length)
with open(path, "wb") as out_file:
out_file.write(body)
elif "chunked" in self.headers.get("Transfer-Encoding", ""):
with open(path, "wb") as out_file:
while True:
line = self.rfile.readline().strip()
chunk_length = int(line, 16)
if chunk_length != 0:
chunk = self.rfile.read(chunk_length)
out_file.write(chunk)
# Each chunk is followed by an additional empty newline
# that we have to consume.
self.rfile.readline()
# Finally, a chunk size of 0 is an end indication
if chunk_length == 0:
break
httpd = HTTPServer(("", PORT), TestHTTPRequestHandler)
print("Serving at port:", httpd.server_port)
httpd.serve_forever()
注意我选择从SimpleHTTPRequestHandler继承而不是BaseHTTPRequestHandler,因为这样SimpleHTTPRequestHandler.translate_path()
可以使用该方法允许客户端选择目标路径(这可能有用或无用,具体取决于用例;我的示例已经编写为使用它)。
正如您所提到的,您可以使用curl命令测试两种操作模式:
# PUT with "Content-Length":
curl --upload-file "file.txt" \
"http://127.0.0.1:8080/uploaded.txt"
# PUT with "Transfer-Encoding: chunked":
curl --upload-file "file.txt" -H "Transfer-Encoding: chunked" \
"http://127.0.0.1:8080/uploaded.txt"
推荐阅读
- mysql - 特殊字符数据迁移
- php - 使用 bootstrap 的 datetimepicker 从当前日期开始设置下一个 3 个月
- python - 使用 Python 向自己发送电子邮件
- python - django channles channels.exceptions.Channels Full
- loopbackjs - 在 Loopback 版本 LB4 中创建具有嵌套对象和数组的模型
- angular6 - Angular 6 component inside the alertifyjs
- vb.net - 添加文本框线循环
- c# - 将 Gridview 数据保存到数据库
- slack-api - 如何使用 slack bot 让您的机器人用户回复简单的帮助消息
- django - 手动创建多对多关系 - 设置关系时出错