mcuserv/pyserv.py

67 lines
3.3 KiB
Python

from urllib.parse import parse_qs as upunquote
VERSION = "PyServ Beta 0.1"
class Abort404(Exception):
"""Signals for a 404 error, if listened for"""
def __init__(self,*args):
super().__init__(*args)
# Unquotes query strings.
def unquote(*args):
return upunquote(*args)
# Lazy text sanitizer.
def lazySan(s):
return s.replace("<","&lt;").replace("&","&amp;")
class HTTPServer:
"""HTTP Server base class"""
def __init__(self):
self.methods = dict()
"""Registers function to handle requests to a method"""
def register(self,method,f):
self.methods[method]=f
"""Returns whether a function is set to handle the given method"""
def hasMethod(self,method):
return method in self.methods.keys()
"""Returns a list of supported methods."""
def getMethods(self):
ret = list(self.methods.keys())
ret.sort()
return ret
"""Formats a response."""
def formatResponse(self,respcode,headers,content):
lines = []
lines.append("HTTP/1.1 {!s} {}".format(respcode,DEFAULT_MSGS[respcode]))
for h in headers:
lines.append("{}: {}".format(h,headers[h]))
lines.append("") # of course
for line in content:
lines.append(line)
return "\n".join(lines)+"\n"
# This function creates a stock error page.
# TODO: Replace DEFAULT_MSGS with maintained database
DEFAULT_MSGS = {100: "Continue",101: "Switching Protocols",102: "Processing",200: "Ok",201: "Created",202: "Accepted",203: "Non Authoritative Information",204: "No Content",205: "Reset Content",206: "Partial Content",207: "Multi Status",208: "Already Reported",226: "Im Used",300: "Multiple Choices",301: "Moved Permanently",302: "Found",303: "See Other",304: "Not Modified",305: "Use Proxy",307: "Temporary Redirect",308: "Permanent Redirect",400: "Bad Request",401: "Unauthorized",402: "Payment Required",403: "Forbidden",404: "Not Found",405: "Method Not Allowed",406: "Not Acceptable",407: "Proxy Authentication Required",408: "Request Timeout",409: "Conflict",410: "Gone",411: "Length Required",412: "Precondition Failed",413: "Request Entity Too Large",414: "Request Uri Too Long",415: "Unsupported Media Type",416: "Requested Range Not Satisfiable",417: "Expectation Failed",418: "I'm A Teapot",422: "Unprocessable Entity",423: "Locked",424: "Failed Dependency",426: "Upgrade Required",428: "Precondition Required",429: "Too Many Requests",431: "Request Header Fields Too Large",500: "Internal Server Error",501: "Not Implemented",502: "Bad Gateway",503: "Service Unavailable",504: "Gateway Timeout",505: "Http Version Not Supported",506: "Variant Also Negotiates",507: "Insufficient Storage",508: "Loop Detected",510: "Not Extended",511: "Network Authentication Required"}
def abort(errorcode,message=None,headers=dict()):
lines = []
if message is None:
message = DEFAULT_MSGS.get(errorcode,"Unknown response code")
lines.append("HTTP/1.1 {!s} {}".format(errorcode,message))
headers["Server"] = headers.get("Server",VERSION)
if errorcode!=204 and "Content-Type" not in headers:
headers["Content-Type"] = "text/html"
for h in headers:
lines.append("{}: {}".format(h,headers[h]))
if errorcode==204:
return "\n".join(lines)+"\n" # No content. Duh.
lines.append("")
lines.append("<h1>{!s} {}</h1>".format(errorcode, message))
lines.append("<hr>")
lines.append("<p>{}</p>".format(headers["Server"]))
return "\n".join(lines)+"\n"