Http.server locks up when both reading and writing data (eg. a POST request)

from threading import Thread
import http.server
import requests

class TestRequest(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.do_request()
    def do_PUT(self):
        self.do_request()
    def do_PATCH(self):
        self.do_request()
    def do_POST(self):
        self.do_request()
    def do_request(self):

        if True:
            body = self.rfile.read()  # This correctly gives an empty byte array because the client used GET and did not send any data
            with open('/dev/stderr', 'w') as stderr:
                stderr.write("Body: %s\n" % (body,))

        if True:
            body = self.server.data.encode()
            self.send_response(200, 'OK')
            self.send_header('Content-Length', len(body))
            self.send_header('Content-Type', 'text/plain')
            self.end_headers()
            self.wfile.write(body) # So long as the data is read above this locks up
            self.wfile.flush()


class TestServer(http.server.HTTPServer):
    def __init__(self, address, requast, data):
        self.data = data
        super().__init__(address, requast)

class ServerThread():
    def __init__(self, data):
        self.httpd = TestServer(('', 0), TestRequest, data)

    def url(self):
        return 'http://127.0.0.1:%i' % (self.httpd.server_address[1],)

    def start_server(self):
        def _start_server(self):
            self.httpd.handle_request()
        thread = Thread(target=_start_server, args=(self,))
        thread.daemon = True  # This does not really do anything, when the client throws an exception instead of sending a request the program locks up
        thread.start()

    def stop_server(self):
        self.httpd.server_close()

    def __del__(self):
        self.stop_server()

st = ServerThread(1024 * 'foobar')
st.start_server()
r = requests.get(st.url() + '/foobar')
print(r.text)

It is necessary to use content length to determine how much to read, otherwise the read locks up.

Interestingly, when the server is in the same process as the client it progresses until the write call before it locks up causing a misleading stack trace.

reflector:

from threading import Thread
import http.server
import requests
import sys

class TestRequest(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.do_request()
    def do_PUT(self):
        self.do_request()
    def do_PATCH(self):
        self.do_request()
    def do_POST(self):
        self.do_request()
    def do_request(self):

        body = b''
        if True:
            if 'Content-Length' in self.headers:
                body = self.rfile.read(int(self.headers['Content-Length']))
                sys.stderr.write("Body: %s\n" % (body,))

        if True:
            self.send_response(200, 'OK')
            self.send_header('Content-Length', len(body))
            self.send_header('Content-Type', 'text/plain')
            self.end_headers()
            self.wfile.write(body)
            self.wfile.flush()


class TestServer(http.server.HTTPServer):
    def __init__(self, address, requast, data):
        self.data = data
        super().__init__(address, requast)

class ServerThread():
    def __init__(self, data):
        self.httpd = TestServer(('', 0), TestRequest, data)

    def url(self):
        return 'http://127.0.0.1:%i' % (self.httpd.server_address[1],)

    def start_server(self):
        self.httpd.handle_request()

    def stop_server(self):
        self.httpd.server_close()

    def __del__(self):
        self.stop_server()

st = ServerThread(1024 * 'foobar')
sys.stderr.write(st.url() + '\n')
st.start_server()
> python3 testr.py &
http://127.0.0.1:38887
> > wget -O - --no-show-progress --post-data=foo\ bar\ baz\  http://127.0.0.1:38887
--2025-08-19 18:10:34--  http://127.0.0.1:38887/
Connecting to 127.0.0.1:38887... connected.
HTTP request sent, awaiting response... Body: b'foo bar baz '
127.0.0.1 - - [19/Aug/2025 18:10:34] "POST / HTTP/1.1" 200 -
200 OK
Length: 12 [text/plain]
Saving to: ‘STDOUT’
foo bar baz 2025-08-19 18:10:34 (3.19 MB/s) - written to stdout [12/12]