Skip to content

Instantly share code, notes, and snippets.

@MosakujiHokuto
Created August 19, 2013 08:02
Show Gist options
  • Save MosakujiHokuto/6266699 to your computer and use it in GitHub Desktop.
Save MosakujiHokuto/6266699 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
from os.path import *
import socket,select
import mimetypes
import argparse
import os
class httpBadRequestError(Exception):
pass
class httpFileNotFoundError(Exception):
pass
class httpForbiddenError(Exception):
pass
class sockClosedError(Exception):
pass
path_cache = {}
def get_path(path):
global path_cache
if path in path_cache:
return path_cache[path]
else:
with open(path,'rb') as f:
filecont = f.read()
if len(path_cache) > 100:
path_cache = {}
path_cache[path] = filecont
return filecont
def gen_headers(headers):
ret = ""
for i in headers:
ret += ("%s: %s\r\n"%(i,headers[i]))
return ret
class Response:
def __init__(self,request=None):
self.request = request
self.keep_alive = False
self.init()
try:
self.create_response()
except FileNotFoundError:
raise httpFileNotFoundError
except:
raise
def init(self):
self.status = "200 OK"
self.file = None
self.content = None
def _handle_content(self):
path = self.request.path
if path == "/":
path = "index.html"
else:
path = path.replace("../","/")[1:]
try:
if isdir(path):
raise httpForbiddenError()
except FileNotFoundError:
raise httpNotFoundError()
self.cont_size = getsize(path)
if self.cont_size < 65535:
self.content = get_path(path)
else:
self.file = open(path,'rb')
self.cont_type = mimetypes.guess_type(path)[0]
def _check_keep_alive(self):
if 'Connection' in self.request.headers and self.request.headers['Connection'] == "Keep-Alive":
self.keep_alive = True
def create_response(self):
self._handle_content()
self._check_keep_alive()
connection = "Keep-Alive" if self.keep_alive else 'close'
headers = {
'Server':'Syameimaru',
'Connection':connection,
'Content-Type':self.cont_type,
'Content-Length':self.cont_size
}
status_line = 'HTTP/1.1 ' + self.status + "\r\n"
self.header = status_line + gen_headers(headers) + '\r\n'
self.header = self.header.encode('utf8')
if isinstance(self.content,str):
self.content = self.content.encode('utf8')
if self.content != None:
self.header += self.content
def write(self,sock):
if self.header != None:
sendb = sock.send(self.header)
if sendb < len(self.header):
raise sockClosedError()
self.header = None
elif self.file != None:
buf = self.file.read(65535)
if len(buf) == 0:
return
sendb = sock.send(buf)
if sendb < 65535:
raise EOFError()
else:
raise EOFError()
class ErrorResponse(Response):
def __init__(self,*args,**kwargs):
Response.__init__(self,*args,**kwargs)
def _handle_content(self):
self.cont_size = len(self.content)
self.cont_type = 'text/html'
def _check_keep_alive(self):
self.keep_alive = False
class BadReqResponse(ErrorResponse):
def __init__(self,*args,**kwargs):
ErrorResponse.__init__(self,*args,**kwargs)
def init(self):
self.file = None
self.status = '400 Bad Request'
self.content = self.status
class FileNotFoundResponse(ErrorResponse):
def __init__(self,*args,**kwargs):
ErrorResponse.__init__(self,*args,**kwargs)
def init(self):
self.file = None
self.status = '404 File Not Found'
self.content = self.status
class ForbiddenResponse(ErrorResponse):
def __init__(self,*args,**kwargs):
ErrorResponse.__init__(self,*args,**kwargs)
def init(self):
self.file = None
self.status = '403 Forbidden'
self.content = self.status
class InternalErrorResponse(ErrorResponse):
def __init__(self,*args,**kwargs):
ErrorResponse.__init__(self,*args,**kwargs)
def init(self):
self.file = None
self.status = '500 Internal Server Error'
self.content = self.status
class Request:
def __init__(self,content):
self.content = content
self.parse_request()
def parse_request(self):
try:
status_line,*rest = self.content.split("\r\n")
method,path,httpver = status_line.split()
headers = dict()
for i in rest:
key,value = [j.strip() for j in i.split(":",1)]
headers[key] = value
self.method = method
self.path = path
self.httpver = httpver
self.headers = headers
except ValueError:
raise httpBadRequestError()
except:
raise
class Client:
def __init__(self,server,sock,ioloop):
self.server = server
self.response = None
self.sock = sock
self.buf = bytes()
self.ioloop = ioloop
self.ioloop.add(self)
def close(self):
self.server.remove(self)
self.ioloop.remove(self)
self.sock.close()
def new_request(self,reqcont):
try:
reqcont = reqcont.decode('utf8')
request = Request(reqcont)
self.response = Response(request)
except UnicodeDecodeError:
self.response = BadReqResponse()
except httpForbiddenError:
self.response = ForbiddenResponse()
except httpBadRequestError:
self.response = BadReqResponse()
except httpFileNotFoundError:
self.response = FileNotFoundResponse()
except:
self.response = InternalErrorResponse()
def read_callback(self):
try:
recv = self.sock.recv(8192)
if recv == '':
self.close()
else:
self.buf += recv
except ConnectionError:
self.close()
except:
raise
if b"\r\n\r\n" in self.buf:
request,self.buf = self.buf.split(b'\r\n\r\n',1)
self.new_request(request)
def write_callback(self):
if self.response != None:
try:
self.response.write(self.sock)
except EOFError:
if not self.response.keep_alive:
self.close()
except sockClosedError:
self.close()
except:
raise
class Server:
def __init__(self,host,port):
self.sock = None
self.clients = []
try:
addr = (host,port)
self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.sock.bind(addr)
self.sock.listen(128)
self.ioloop = EPollIOLoop()
self.ioloop.add(self)
self.ioloop.loop()
except:
for i in self.clients:
i.close()
self.sock.close()
raise
def read_callback(self):
self.callback()
def write_callback(self):
self.callback()
def callback(self):
while True:
try:
fd,addr = self.sock.accept()
self.clients.append(Client(self,fd,self.ioloop))
except:
return
def remove(self,client):
self.clients.remove(client)
class IOLoop:
def __init__(self):
self.init()
def add(self,client):
raise NotImplemented()
def remove(self,client):
raise NotImplemented()
def loop(self):
raise NotImplemented()
if hasattr(select,"epoll"):
class EPollIOLoop(IOLoop):
def init(self):
self.epoll_obj = select.epoll()
self.clients = {}
def add(self,client):
client.sock.setblocking(False)
self.clients[client.sock.fileno()] = client
self.epoll_obj.register(client.sock.fileno())
def remove(self,client):
del self.clients[client.sock.fileno()]
self.epoll_obj.unregister(client.sock.fileno())
def loop(self):
while True:
ret = self.epoll_obj.poll()
for sockfd,event in ret:
if event & select.EPOLLIN:
self.clients[sockfd].read_callback()
elif event & select.EPOLLOUT:
self.clients[sockfd].write_callback()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Static file http server')
parser.add_argument('--port',dest='port',type=int,help='port to listen on',default=8080)
parser.add_argument('--host',dest='host',default='localhost',help='host to bind')
parser.add_argument('--root',dest='root',default='./',help='set root path of this server')
args = parser.parse_args()
os.chdir(args.root)
server = Server(args.host,args.port)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment