split files, add etags

master
Yorick van Pelt 2015-03-01 21:52:24 +01:00
parent 8af1cd2b28
commit 6ec64666a5
2 changed files with 128 additions and 85 deletions

82
webserver/asyncserver.py Normal file
View File

@ -0,0 +1,82 @@
import socket, select
import os.path, mimetypes
class Server(object):
def __init__(self, connection_class, port=8080):
self.port = port
self.connection_class = connection_class
self.listening_socket = socket.socket()
self.listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listening_socket.bind(('127.0.0.1', port))
self.listening_socket.listen(5)
self.listening_socket.setblocking(0)
self.handlers = {self.listening_socket: self}
self.clients = []
def run(self):
reads = [self.listening_socket] + [c.sock for c in self.clients]
writes = [c.sock for c in self.clients if c.should_write()]
others = [c.sock for c in self.clients]
[rlist, wlist, xlist] = select.select(reads, writes, others)
for readable in rlist:
self.handlers[readable].do_read()
for writable in wlist:
self.handlers[writable].do_write()
for otherable in xlist:
self.handlers[otherable].do_other()
def do_read(self):
(sock, addrinfo) = self.listening_socket.accept()
c = self.connection_class(self, sock, addrinfo)
self.clients.append(c)
self.handlers[sock] = c
def client_closed(self, c):
self.clients.remove(c)
del self.handlers[c.sock]
def close(self):
self.listening_socket.close()
class Client(object):
"""docstring for Client"""
def __init__(self, parent, sock, addrinfo):
self.parent = parent
self.sock = sock
self.sock.setblocking(0)
self.addrinfo = addrinfo
self.write_buf = ""
self.read_buf = ""
self.to_close = False
#self.write("test")
# self.close()
def should_write(self):
return len(self.write_buf) > 0 or self.to_close
def do_read(self):
data = self.sock.recv(4096)
if not data: return self.close()
self.read_buf += data
self.on_data()
def do_write(self):
try:
if self.write_buf:
no_written = self.sock.send(self.write_buf)
self.write_buf = self.write_buf[no_written:]
if len(self.write_buf) == 0 and self.to_close:
self.sock.close()
self.parent.client_closed(self)
except socket.error:
self.on_error()
def on_data(self): raise NotImplementedError()
def do_other(self): pass
def on_error(self):
self.parent.client_closed(self)
def write(self, msg):
self.write_buf += msg
def close(self):
self.to_close = True
class EchoClient(Client):
"""docstring for EchoClient"""
def __init__(self, parent, sock, addrinfo):
super(EchoClient, self).__init__(parent, sock, addrinfo)
def on_data(self):
self.write(self.read_buf)
self.read_buf = ""

View File

@ -1,91 +1,24 @@
import socket, select
import os.path
import os.path, mimetypes
from urllib import unquote
import hashlib
class Server(object):
def __init__(self, port=8080):
self.port = port
self.listening_socket = socket.socket()
self.listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listening_socket.bind(('127.0.0.1', port))
self.listening_socket.listen(5)
self.listening_socket.setblocking(0)
self.handlers = {self.listening_socket: self}
self.clients = []
def run(self):
reads = [self.listening_socket] + [c.sock for c in self.clients]
writes = [c.sock for c in self.clients if c.should_write()]
others = [c.sock for c in self.clients]
[rlist, wlist, xlist] = select.select(reads, writes, others)
print(rlist, wlist, xlist)
for readable in rlist:
self.handlers[readable].do_read()
for writable in wlist:
self.handlers[writable].do_write()
for otherable in xlist:
self.handlers[otherable].do_other()
def do_read(self):
(sock, addrinfo) = self.listening_socket.accept()
c = HTTPDirClient(self, sock, addrinfo)
self.clients.append(c)
self.handlers[sock] = c
def client_closed(self, c):
self.clients.remove(c)
del self.handlers[c.sock]
def close(self):
self.listening_socket.close()
import asyncserver
class Client(object):
"""docstring for Client"""
def __init__(self, parent, sock, addrinfo):
self.parent = parent
self.sock = sock
self.sock.setblocking(0)
self.addrinfo = addrinfo
self.write_buf = ""
self.read_buf = ""
self.to_close = False
#self.write("test")
# self.close()
def should_write(self):
return len(self.write_buf) > 0 or self.to_close
def do_read(self):
data = self.sock.recv(4096)
if not data: return self.close()
self.read_buf += data
self.on_data()
def do_write(self):
try:
if self.write_buf:
no_written = self.sock.send(self.write_buf)
self.write_buf = self.write_buf[no_written:]
if len(self.write_buf) == 0 and self.to_close:
self.sock.close()
self.parent.client_closed(self)
except socket.error:
self.on_error()
def on_data(self): raise NotImplementedError()
def do_other(self): pass
def on_error(self):
self.parent.client_closed(self)
def write(self, msg):
self.write_buf += msg
def close(self):
self.to_close = True
class EchoClient(Client):
"""docstring for EchoClient"""
def __init__(self, parent, sock, addrinfo):
super(EchoClient, self).__init__(parent, sock, addrinfo)
def on_data(self):
self.write(self.read_buf)
self.read_buf = ""
codes = {
200: "OK",
301: "Moved Permanently", # should this be a 301
304: "Not Modified",
404: "Not Found"
}
class HTTPClient(Client):
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)
@ -110,16 +43,35 @@ class HTTPClient(Client):
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 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):
@ -127,23 +79,32 @@ class HTTPDirClient(HTTPClient):
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
if url.endswith('/'): # TODO FIX
print url, reqpath
print reqpath, os.path.isdir(reqpath)
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:
with open(reqpath) as f:
self.send_response(200, {}, f.read())
etag = None
if "If-None-Match" in headers:
etag = headers['If-None-Match']
self.send_file(200, {}, reqpath, etag)
if __name__ == '__main__':
s = Server()
s = asyncserver.Server(HTTPDirClient)
try:
while 1:
s.run()