Last active
July 3, 2017 05:03
-
-
Save mironside/09106e7725d45620cad427038903a2d9 to your computer and use it in GitHub Desktop.
minimal http server/router, intended for pasting into single file python scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# A minimal, zero dependency http server/router | |
# | |
# def root(req): | |
# return 'Hello, world' | |
# routes = ( | |
# ('GET', '/', root), | |
# ) | |
# http(routes, 'localhost', 8080) | |
# | |
# Routing with variable capture and default values | |
# def events(req, eventID=None): | |
# return 'eventID %s' % eventID | |
# ('GET', '/events', events), | |
# ('GET', '/events/<int:eventID>/view', events), | |
# ('GET', '/users/<username>', users) | |
# | |
# Response headers inferred from response type | |
# Route handlers return (response, content_type, status) where content_type and status are both optional and can be returned in any order | |
# Content-length is automatically set based on the response size | |
# Content-type is inferred by the response data type | |
# dict or list is encoded to json with Content-type: application/json | |
# string starting with '<html>' set Content-type: text/html | |
# strings set Content-type: text/plain | |
# any value that evaluates to False sends an empty response with Content-type: text/plain | |
# | |
# return '<html><body>Not Found</body></html>', 404 | |
# return open('test.jpg','rb').read(), 'image/jpeg' | |
# return open('test.log','r').read() | |
# return {'a':'b', 'c':[1,2,3]} | |
# | |
# No template engine is provided but the builtin string.Template can be used to avoid additional dependencies | |
# | |
def http(routes, host, port): | |
import itertools, re, urllib.parse, http.server, socketserver, json | |
def route(self): | |
self.path,self.query = (lambda x: (x[0],urllib.parse.parse_qs(x[1])))((self.path.split('?') + [''])[:2]) | |
for method,path,func in routes: | |
if method != self.command: continue | |
kwargs = {} | |
for r,p in itertools.zip_longest([x for x in path.split('/') if x], [x for x in self.path.split('/') if x], fillvalue=None): | |
if r and r[0] == '<' and r[-1] == '>': kwargs.update(dict([(lambda type,name: (name,{'int': lambda x: int(x) if x.isnumeric() else None}.get(type, lambda x: x)(p)))(*([None] + r[1:-1].split(':'))[-2:])])) | |
elif p != r: kwargs = None; break | |
if kwargs == None or None in kwargs.values(): continue | |
body,content_type,status = (lambda x: x+('text/plain',200) if len(x) == 1 else x+(200,) if len(x) == 2 and type(x[1]) == str else (x[0],'text/plain',x[1]) if len(x) == 2 and type(x[1]) == int else (x[0],x[2],x[1]) if type(x[1]) == int and type(x[2]) == str else x)((lambda x: x if type(x) == tuple else (x,))(func(self, **kwargs))) | |
body,content_type = (bytes(json.dumps(body),'utf-8'),'application/json') if type(body) == dict or type(body) == list else (bytes(body, 'utf-8'),'text/html') if type(body) == str and body.startswith('<html>') else (bytes(body, 'utf-8'),content_type) if type(body) == str else (bytes('', 'utf-8'),content_type) if not body else (body,content_type) | |
self.send_response(status),self.send_header('Content-type', content_type),self.send_header('Content-length', len(body)),self.end_headers(),self.wfile.write(body) | |
self.send_error(404) | |
class RequestHandler(http.server.BaseHTTPRequestHandler): pass | |
RequestHandler.do_GET = RequestHandler.do_PUT = RequestHandler.do_POST = RequestHandler.do_DELETE = route | |
class ThreadedHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): pass | |
server = ThreadedHTTPServer((host, port), RequestHandler) | |
server.serve_forever() | |
def root(req): | |
return '<html><b>root</b></html>' | |
def events(req, eventId): | |
return 'events' | |
def about(req): | |
return 'about' | |
routes = ( | |
('GET', '/', root), | |
('GET', '/events/<eventId>', events), | |
('GET', '/events/<int:eventId>/view', events), | |
('GET', '/about', about), | |
) | |
if __name__ == '__main__': | |
http(routes, 'localhost', 8888) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment