import socket from http.client import HTTPConnection import selectors from threading import Thread sel = selectors.DefaultSelector() PORT = 8081 def handle_GET(sock, req): sock.sendall( b'HTTP/1.1 200 OK\r\n' b'Connection: keep-alive\r\n\r\n') def handle(sock, mask): req = buf = sock.recv(1024) while True: try: buf = sock.recv(1024) req += buf except BlockingIOError: break first_space = req.find(b' ') method = req[:first_space].decode('ascii') try: globals()['handle_{}'.format(method)](sock, req) except KeyError: # in this proof of concept, i had initially simply done a pass here, # keeping the socket open, effectively being a noop. # because of the logic in HTTPResponse (specifically readline()), # this will cause the client to # hang. the /only/ situation where the client can possibly get an empty # read is, in fact, when the socket has been closed and the client gets # the EOS read. sock.close() def accept(server, mask): con, addr = server.accept() con.setblocking(False) sel.register(con, selectors.EVENT_READ, handle) def start_server(server): server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('localhost', PORT)) server.listen() server.setblocking(False) sel.register(server, selectors.EVENT_READ, accept) while True: try: events = sel.select() for key, mask in events: key.data(key.fileobj, mask) except ValueError: # selector has likely been closed break def main(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Thread(target=start_server, args=(server,)).start() con = HTTPConnection('localhost', PORT) con.connect() con.request('GET', '/') r = con.getresponse() assert r.status == 200 con.request('OPTIONS', '/') r = con.getresponse() assert r.status == 200 con.request('GET', '/') r = con.getresponse() assert r.status == 200 try: server.shutdown(socket.SHUT_RDWR) except OSError: # fine, no connection pass server.close() sel.close() if __name__ == '__main__': main()