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]