|
import socket |
|
import threading |
|
from datetime import datetime |
|
import sys |
|
import os |
|
import mimetypes |
|
import urllib.parse |
|
import subprocess |
|
|
|
respTemplate = """HTTP/1.1 {statusNum} {statusCode} |
|
Date: {dateSent} |
|
Server: {server} |
|
Last-Modified: {modified} |
|
Content-Length: {length} |
|
Content-Type: {contentType} |
|
Connection: {connectionType} |
|
|
|
{body} |
|
""" |
|
DOC_ROOT = "DocRoot" |
|
|
|
CODES = {"200": "OK", |
|
"304": "NOT MODIFIED", |
|
"400": "BAD REQUEST", "401": "UNAUTHORIZED", "403": "FORBIDDEN", "404": "NOT FOUND", |
|
"500": "INTERNAL SERVER ERROR"} |
|
|
|
MIMES = {"txt": "text/plain", "css":"text/css", "html":"text/html", "png": "image/png", "jpg":"image/jpg", |
|
"ttf":"application/octet-stream","otf":"application/octet-stream", "woff":"font/woff", "woff2": "font/woff2", |
|
"js":"application/javascript","gz":"application/zip", "py":"text/plain", "map": "application/octet-stream"} |
|
|
|
|
|
class Response: |
|
def __init__(self, **kwargs): |
|
self.__dict__.update(kwargs) |
|
now = datetime.now() |
|
self.dateSent = self.modified = now.strftime("%a, %d %b %Y %H:%M:%S") |
|
def stringResponse(self): |
|
return respTemplate.format(**self.__dict__) |
|
|
|
class Request: |
|
def __init__(self, request): |
|
self.good = True |
|
try: |
|
request = self.parseRequest(request) |
|
self.method = request["method"] |
|
self.doc = request["doc"] |
|
self.vers = request["vers"] |
|
self.header = request["header"] |
|
self.body = request["body"] |
|
except: |
|
self.good = False |
|
|
|
def parseRequest(self, request): |
|
req = request.strip("\r").split("\n") |
|
method,doc,vers = req[0].split(" ") |
|
header = req[1:-3] |
|
body = req[-1] |
|
headerDict = {} |
|
for param in header: |
|
pos = param.find(": ") |
|
key, val = param[:pos], param[pos+2:] |
|
headerDict.update({key: val}) |
|
return {"method": method, "doc": doc, "vers": vers, "header": headerDict, "body": body} |
|
|
|
|
|
class Server: |
|
def __init__(self, host, port): |
|
self.host = host |
|
self.port = port |
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
self.sock.bind((self.host, self.port)) |
|
|
|
def listen(self): |
|
self.sock.listen(5) |
|
while True: |
|
client, address = self.sock.accept() |
|
client.settimeout(60) |
|
threading.Thread(target = self.listenToClient,args = (client,address)).start() |
|
|
|
def listenToClient(self, client, address): |
|
size = 1024 |
|
while True: |
|
try: |
|
data = client.recv(size) |
|
if data: |
|
# Set the response to echo back the recieved data |
|
req = Request(data.decode()) |
|
self.handleRequest(req, client, address) |
|
client.shutdown() |
|
client.close() |
|
else: |
|
raise error('Client disconnected') |
|
except: |
|
client.close() |
|
return False |
|
|
|
def handleRequest(self, request, conn, address): |
|
if request.good: |
|
# try: |
|
# print(str(request.method) + " " + str(request.doc), end=' ') |
|
# print("from {0}".format(address[0])) |
|
# except Exception as e: |
|
# print(e) |
|
document = self.serveDoc(request.doc, DOC_ROOT) |
|
statusNum=document["status"] |
|
else: |
|
document = self.serveDoc("/errors/400.html", DOC_ROOT) |
|
statusNum="400" |
|
body = document["body"] |
|
|
|
statusCode=CODES[statusNum] |
|
dateSent = "" |
|
server = "BadHTTPServer" |
|
modified = "" |
|
length = len(body) |
|
contentType = document["mime"] # Try and identify MIME type from string |
|
connectionType = "Closed" |
|
|
|
|
|
resp = Response( |
|
statusNum=statusNum, statusCode=statusCode, |
|
dateSent = dateSent, server = server, |
|
modified = modified, length = length, |
|
contentType = contentType, connectionType = connectionType, |
|
body = body |
|
) |
|
|
|
data = resp.stringResponse() |
|
if not data: |
|
return -1 |
|
conn.send(data.encode()) |
|
return 0 |
|
|
|
def serveDoc(self, path, docRoot): |
|
path = urllib.parse.unquote(path) |
|
try: |
|
info = "output = 'Document: {}'" # Keep the output for later debug |
|
exec(info.format(path)) # This is how you do string formatting, right? |
|
print(info.format(path)) |
|
cwd = os.path.dirname(os.path.realpath(__file__)) |
|
docRoot = os.path.join(cwd, docRoot) |
|
if path == "/": |
|
path = "/index.html" |
|
requested = os.path.join(docRoot, path[1:]) |
|
if os.path.isfile(requested): |
|
mime = mimetypes.guess_type(requested) |
|
mime = (mime if mime[0] != None else "text/html") |
|
mime = MIMES[requested.split(".")[-1]] |
|
try: |
|
with open(requested, "r") as f: |
|
data = f.read() |
|
except: |
|
with open(requested, "rb") as f: |
|
data = f.read() |
|
status = "200" |
|
else: |
|
errorPage = os.path.join(docRoot, "errors", "404.html") |
|
mime = "text/html" |
|
with open(errorPage, "r") as f: |
|
data = f.read().format(path) |
|
status = "404" |
|
except Exception as e: |
|
print(e) |
|
errorPage = os.path.join(docRoot, "errors", "500.html") |
|
mime = "text/html" |
|
with open(errorPage, "r") as f: |
|
data = f.read() |
|
status = "500" |
|
return {"body": data, "mime": mime, "status": status} |
|
|
|
serv = Server('127.0.0.1', 7777) |
|
serv.listen() |