如何在 Python BaseHTTPRequestHandler 中处理分块编码?

2024-04-02

我有以下简单的 Web 服务器,利用 Pythonhttp module:

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: 5header,这样我就可以知道应该从套接字读取多少内容。

但如果文件大小未知,或者客户端决定使用chunkedTransfer-Encoding,这种方法失败了。

可以使用以下命令进行模拟:

curl -vT /tmp/test -H "Transfer-Encoding: chunked" http://localhost:8000/test

如果我从self.rfile超过该块后,它将永远等待并挂起客户端,直到它断开 TCP 连接,其中self.rfile.read将返回一个空数据,然后跳出循环。

需要什么来扩展上述示例以支持chunked也是传输编码吗?


正如您在描述中看到的传输编码 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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

您只需读取一行,获取下一个块的大小,然后消耗两个二进制块and后续换行符。

这个例子将能够处理请求Content-Length or 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()

注意我选择继承自简单HTTP请求处理程序代替基本HTTP请求处理程序,因为那么该方法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"
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Python BaseHTTPRequestHandler 中处理分块编码? 的相关文章

随机推荐