Created
August 19, 2013 08:02
-
-
Save YoukouTenhouin/6266699 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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