pyserv.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. from urllib.parse import parse_qs as upunquote
  2. VERSION = "PyServ Beta 0.1"
  3. class Abort404(Exception):
  4. """Signals for a 404 error, if listened for"""
  5. def __init__(self,*args):
  6. super().__init__(*args)
  7. # Unquotes query strings.
  8. def unquote(*args):
  9. return upunquote(*args)
  10. # Lazy text sanitizer.
  11. def lazySan(s):
  12. return s.replace("<","&lt;").replace("&","&amp;")
  13. class HTTPServer:
  14. """HTTP Server base class"""
  15. def __init__(self):
  16. self.methods = dict()
  17. """Registers function to handle requests to a method"""
  18. def register(self,method,f):
  19. self.methods[method]=f
  20. """Returns whether a function is set to handle the given method"""
  21. def hasMethod(self,method):
  22. return method in self.methods.keys()
  23. """Returns a list of supported methods."""
  24. def getMethods(self):
  25. ret = list(self.methods.keys())
  26. ret.sort()
  27. return ret
  28. """Formats a response."""
  29. def formatResponse(self,respcode,headers,content):
  30. lines = []
  31. lines.append("HTTP/1.1 {!s} {}".format(respcode,DEFAULT_MSGS[respcode]))
  32. for h in headers:
  33. lines.append("{}: {}".format(h,headers[h]))
  34. lines.append("") # of course
  35. for line in content:
  36. lines.append(line)
  37. return "\n".join(lines)+"\n"
  38. # This function creates a stock error page.
  39. # TODO: Replace DEFAULT_MSGS with maintained database
  40. 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"}
  41. def abort(errorcode,message=None,headers=dict()):
  42. lines = []
  43. if message is None:
  44. message = DEFAULT_MSGS.get(errorcode,"Unknown response code")
  45. lines.append("HTTP/1.1 {!s} {}".format(errorcode,message))
  46. headers["Server"] = headers.get("Server",VERSION)
  47. if errorcode!=204 and "Content-Type" not in headers:
  48. headers["Content-Type"] = "text/html"
  49. for h in headers:
  50. lines.append("{}: {}".format(h,headers[h]))
  51. if errorcode==204:
  52. return "\n".join(lines)+"\n" # No content. Duh.
  53. lines.append("")
  54. lines.append("<h1>{!s} {}</h1>".format(errorcode, message))
  55. lines.append("<hr>")
  56. lines.append("<p>{}</p>".format(headers["Server"]))
  57. return "\n".join(lines)+"\n"