#!/usr/bin/python
# World's dumbest HTTPD, but using purely non-blocking I/O and without threads.
import socket, select, cgitb

class FilenameNotAllowed(Exception): pass
class MethodNotUnderstood(Exception): pass

class Connection:
    def fileno(self): return self.sock.fileno()
    def close(self): self.sock.close()

class Server(Connection):
    def __init__(self):
        self.sock = socket.socket()
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind(('', 8000))
        self.sock.listen(5)

    def readable(self): return True
    def handle_read(self):
        add_connection(Client(self.sock.accept()[0]))

    def writable(self): return False

class Client(Connection):
    def __init__(self, sock):
        self.sock = sock
        self.sock.setblocking(False)
        self.write_buf = ''
        self.read_buf = ''
        self.already_read_request = False

    def readable(self): return not self.already_read_request
    def handle_read(self):
        self.read_buf += self.sock.recv(4096)
        if '\n' not in self.read_buf: return

        self.already_read_request = True

        command = 'GET /'
        if self.read_buf.startswith(command):
            filename = self.read_buf[len(command):].split()[0]
            if '/' in filename: raise FilenameNotAllowed(filename)
            self.write_buf += open(filename).read()

        else:
            raise MethodNotUnderstood(self.read_buf)

    def writable(self): return self.write_buf
    def handle_write(self):
        nbytes = self.sock.send(self.write_buf)
        self.write_buf = self.write_buf[nbytes:]
        if not self.write_buf: close_connection(self)

connections = {}
def add_connection(conn): connections[conn] = True
def close_connection(conn):
    del connections[conn]
    conn.close()

def main():
    add_connection(Server())

    while True:
        (r, w, _) = select.select([conn for conn in connections.keys()
                                   if conn.readable()],
                                  [conn for conn in connections.keys()
                                   if conn.writable()],
                                  [])

        for conn in r:
            try:
                conn.handle_read()
            except:
                cgitb.Hook(format='text').handle()
                close_connection(conn)

        for conn in w:
            try:
                conn.handle_write()
            except:
                cgitb.Hook(format='text').handle()
                close_connection(conn)

if __name__ == '__main__': main()

