importjsonimportthreadingimporthttp.serverimportloggingfromqueueimportQueueq=Queue()EPHEMERAL_PORT=0num_events=0MAX_EVENTS=3classTinyRESTHandler(http.server.BaseHTTPRequestHandler):def__init__(self,service_map,*args):self.service_map=service_map
http.server.BaseHTTPRequestHandler.__init__(self,*args)defrespond(self,code,message):self.send_response(code)self.send_header("Content-Type","text/ascii")self.send_header("Content-Length",str(len(message.encode())))self.end_headers()self.wfile.write(message.encode())defhandle_POST(self,json_handler):"""
Route POST requests to the appropriate handler.
"""payload=self.rfile.read(int(self.headers['Content-Length']))try:json_payload=json.loads(payload)json_handler(self.path,json_payload)exceptjson.decoder.JSONDecodeErrorase:self.respond(400,"Bad Request: Invalid JSON")self.respond(200,"OK")defdo_POST(self):if(self.pathinself.service_map):self.handle_POST(self.service_map[self.path])else:self.respond(404,"Not Found")classEphemeralHTTPServer(http.server.HTTPServer):"""
We cannot know the port used by an Ephemeral HTTP server until
it has tried to bind a port (at which point the OS gives it a
free port.) This adds a callback to the bind function that allows
us to be notified as soon as a port has been obtained.
"""def__init__(self,hostname,port_notify_cb,*args,**kwargs):self.port_notify_cb=port_notify_cb
super().__init__((hostname,EPHEMERAL_PORT),*args,**kwargs)defserver_bind(self):"""
The server will notify port_notify_cb of its address
once it has bound a port.
"""super().server_bind()if(self.port_notify_cb):self.port_notify_cb(self.server_address)classTinyRESTServer():def__init__(self,host,port):self.host=host
self.port=port
self.service_map=dict()defregister_service(self,path,callback):self.service_map[path]=callbackdefget_handler(self,*args):"""
HTTPServer creates a new handler for every request. This ensures
that the TinyRESTHandlers are supplied with the service map.
"""returnTinyRESTHandler(self.service_map,*args)defgetHTTPServer(self):returnhttp.server.HTTPServer((self.host,self.port),self.get_handler)defrun(self):"""
The server_close call forces HTTPServer to relinquish its port.
"""self.server=self.getHTTPServer()try:self.server.serve_forever()finally:self.server.server_close()defshutdown(self):self.server.shutdown()classEphemeralRESTServer(TinyRESTServer):def__init__(self,host,address_cb):self.address_cb=address_cb
super().__init__(host,0)defgetHTTPServer(self):returnEphemeralHTTPServer(self.host,self.address_cb,self.get_handler)classServerEvent:def__init__(self,name):self.name=nameclassPortAcquiredEvent(ServerEvent):def__init__(self,hostname,port):super().__init__("port acquired")self.hostname=hostname
self.port=portdef__str__(self):returnf"{self.name}: (host, port) = ({self.hostname}, {self.port})"classJSONEvent(ServerEvent):def__init__(self,json_content):super().__init__("JSON results")self.json_content=json_contentdef__str__(self):returnf"{self.name}: {self.json_content}"defget_server_address(server_address):"""
When the server binds an ephemeral port, it will call this
function to tell us what port the OS provided. Using a queue
ensures that the main prog doesn't try to get the port before
the HTTP server in the thread has successfully obtained one.
"""q.put(PortAcquiredEvent(server_address[0],server_address[1]))defadd_to_queue(req_type,json_content):"""
Contrived REST service handler.
"""q.put(JSONEvent(json_content))defcheck_if_we_should_stop_the_server(event):"""
Contrived function to test when we should stop the http server
and do something with the received data.
"""globalnum_eventsglobalMAX_EVENTSprint(event)num_events+=1returnnum_events
Do something here to cause rest clients to start hitting this
server (for example invoking the clients via subprocess or
whatevs.)
"""# Block until the queue obtains a value.cur_val=q.get()whilecheck_if_we_should_stop_the_server(cur_val):cur_val=q.get()# Stop the HTTP server.server.shutdown()server_thread.join()# Do normal script stuff with the data...