import os.path, mimetypes from urllib import unquote import hashlib from datetime import datetime, timedelta import sys import asyncserver codes = { 200: "OK", 301: "Moved Permanently", # should this be a 301 304: "Not Modified", 404: "Not Found" } from email.utils import formatdate def httpdate(): return formatdate(timeval=None, localtime=False, usegmt=True) def ETag(data): return hashlib.sha1(data).hexdigest() class HTTPClient(asyncserver.Client): """docstring for HTTPClient""" def __init__(self, parent, sock, addrinfo): super(HTTPClient, self).__init__(parent, sock, addrinfo) self.set_timeout() def on_data(self): self.set_timeout() try: idx = self.read_buf.index("\r\n\r\n") except ValueError: return else: headers = self.read_buf[:idx] self.read_buf = self.read_buf[idx+4:] self.on_request(headers) def set_timeout(self): self.timeout = datetime.today() + timedelta(seconds=10) def on_request(self, req): headers = req.split('\r\n') if not (headers[0].startswith("GET ") and headers[0].endswith("HTTP/1.1")): print(headers[0]) print("malformed request:", headers) return url = headers[0][4:-9] hdr = {} for h in headers[1:]: x = h.split(': ') hdr[x[0]] = x[1] self.on_GET(url, hdr) try: if hdr['Connection'] == 'close': self.close() except KeyError: pass def on_GET(self, url, headers): print("got GET", url, headers) def next_timeout(self): if self.timeout: return (self.timeout - datetime.today()).total_seconds() else: return None def try_timeout(self): if self.timeout < datetime.today(): print("connection timed out") self.timeout = None self.close() def send_file(self, code, headers, fname, orig_etag): (type, encoding) = mimetypes.guess_type(fname, strict = False) if type: headers["Content-Type"] = type with open(fname) as f: data = f.read() etag = ETag(data) if etag == orig_etag: self.send_response(304, {}, "") else: headers["ETag"] = etag self.send_response(code, headers, data) def send_response(self, code, headers, data): headers["Content-Length"] = len(data) headers["Date"] = httpdate() name = codes[code] resp = "HTTP/1.1 %d %s\r\n" % (code, name) for n, val in headers.iteritems(): resp += "%s: %s\r\n" % (n, val) resp += "\r\n" + data self.write(resp) print "%s %s" % (code, codes[code]) class HTTPDirClient(HTTPClient): """docstring for HTTPDirClient""" def __init__(self, parent, sock, addrinfo): super(HTTPDirClient, self).__init__(parent, sock, addrinfo) self.directory = "content" print "Opened connection" def on_GET(self, url, headers): if not url: url = "/" else: url = unquote(url) reqpath = os.path.abspath("./content" + url) # make sure that we can't go outside the content dir if not reqpath.startswith(os.path.abspath("./content/")): self.send_response(404, {}, "Not Found, sorry\n") return print "GET", url if os.path.isdir(reqpath) and not url.endswith('/'): self.send_response(301, {"Location": url + '/'}, "") reqpath += "/index.html" if os.path.isdir(reqpath) and url.endswith('/'): reqpath += "/index.html" if not os.path.isfile(reqpath): self.send_response(404, {}, "Not Found, sorry\n") else: etag = None if "If-None-Match" in headers: etag = headers['If-None-Match'] self.send_file(200, {}, reqpath, etag) if __name__ == '__main__': try: port = int(sys.argv[1]) except (ValueError, IndexError): print "invalid port given" else: s = asyncserver.Server(HTTPDirClient, port = port) try: while 1: s.run() except: s.close() raise