Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
180 lines (148 sloc) 4.98 KB
#!/usr/bin/env python3
#
# Minimalistic webserver using asyncio.
#
# Copyright (c) 2016 Samuel Groß
#
import asyncio
#
# HTTP Server
#
HTTP_PHRASES = {
100: "Continue",
101: "Switching Protocols",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
307: "Temporary Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Time-out",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Large",
415: "Unsupported Media Type",
416: "Requested range not satisfiable",
417: "Expectation Failed",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Time-out",
505: "HTTP Version not supported",
}
class HTTPServer:
"""Minimalistic, stream-based HTTP server."""
# TODO add some error handling.. :)
class Response:
"""Represents an outgoing HTTP response."""
def __init__(self, writer):
self._writer = writer
def start_header(self, code):
resline = 'HTTP/1.1 {} {}'.format(code, HTTP_PHRASES.get(code, ''))
print(" >", resline)
self.write(resline + '\r\n')
def end_header(self):
self.add_header('Connection', 'close')
self.write('\r\n')
def write(self, data):
if type(data) == str:
data = bytes(data, 'ascii')
self._writer.write(data)
def add_header(self, key, value):
assert('\n' not in key + value)
print(" > {}: {}".format(key, value))
self.write(key + ': ' + value + '\r\n')
def send_header(self, code, headers):
self.start_header(code)
for key, value in headers.items():
self.add_header(key, value)
self.end_header()
async def drain(self):
await self._writer.drain()
class Request:
"""Represents an incoming HTTP request."""
def __init__(self, peer, method, path, version, headers, reader):
self.peer = peer
self.method = method
self.path = path
self.version = version
self.headers = headers
self.reader = reader
def __str__(self):
s = "Request from {}\n".format(self.peer)
s += " < {} {} {}\n".format(self.method, self.path, self.version)
for key, value in self.headers.items():
s += " < {}: {}\n".format(key, value)
return s
def __init__(self, host, port, routes, loop):
self._coro = asyncio.start_server(self.handle_client, host, port, loop=loop)
self._loop = loop
self._routes = routes
async def send_404(self, request, response):
response.send_header(404, {
'Content-Length': '0'
})
async def receive_header(self, reader, writer):
header = await reader.readuntil(b'\r\n\r\n')
headers = header.rstrip().split(b'\r\n')
# Extract request line
# TODO handle GET parameters etc.
reqline = headers[0].decode('ascii').rstrip().split(' ')
method, path, version = reqline
# Extract remaining headers
headers = dict(line.decode('ascii').split(': ') for line in headers[1:])
peer = writer.get_extra_info('peername')
return self.Request(peer, method, path, version, headers, reader)
async def handle_client(self, reader, writer):
response = self.Response(writer)
request = await self.receive_header(reader, writer)
print(request)
handler = self._routes.get(request.path, self.send_404)
await handler(request, response)
await writer.drain()
writer.close()
def run_forever(self):
server = self._loop.run_until_complete(self._coro)
# Serve requests until Ctrl+C is pressed
print("Serving on {}".format(server.sockets[0].getsockname()))
try:
self._loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
self._loop.run_until_complete(server.wait_closed())
self._loop.close()
#
# Request handlers
#
def serve_file(path, ctype):
async def coro(request, response):
with open(path, 'rb') as f:
content = f.read()
response.send_header(200, {
'Content-Length':str(len(content)),
'Content-Type': ctype
})
response.write(content)
return coro
You can’t perform that action at this time.