from nbdev import *
from fastcore.utils import *
import time,socket
Python's socketserver classes call handle in a BaseRequestHandler subclass that you pass in to its constructor. You use a BaseRequestHandler with any of the server classes/mixins provided by socketserver.
It's easiest to use allow_reuse_address to avoid having to wait for sockets to close, especially when testing. This class adds that functionality to ThreadingTCPServer.
Here's an example of using Python's standard library features along with ReuseThreadingServer:
host,port = 'localhost',8000
class _TestHandler(StreamRequestHandler):
def handle(self):
print('received', self.rfile.readline())
self.wfile.write(bytes(f'pong {self.client_address[0]}\r\n', 'utf8'))
@startthread
def _f():
with ReuseThreadingServer((host,port), _TestHandler) as srv: srv.handle_request()
time.sleep(0.5) # wait for server to start
c = start_client(port,host)
c.send(b'ping\r\n')
c.recv(1024)
MinimalHTTPHandler parses the HTTP command and headers, and sets command, path, request_version, and headers. It is based on the code in Python's BaseHTTPHandler, but is greatly simplified, and made consistent with the other socketserver servers.
To send a response, call send_response(code), optionally send_header a few times, then end_headers, and finally write to wfile. For instance:
class _TestHandler(MinimalHTTPHandler):
def handle(self):
print(f'Command/path/version: {self.command} {self.path} {self.request_version}')
print(self.headers)
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header('Content-Length', '2')
self.end_headers()
self.wfile.write(b'ok')
@startthread
def _f():
with ReuseThreadingServer(('localhost',8000), _TestHandler) as httpd: httpd.handle_request()
time.sleep(0.5) # wait for server to start
test_eq(urlread("http://localhost:8000"), b'ok')