Improve documentation, cleanup duplicated code
This commit is contained in:
@@ -231,7 +231,7 @@ class HTMLDownloadHandler(DownloadHandler):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fp = open(tmp_path, "r", encoding="yeetus")
|
fp = open(tmp_path, "r", encoding=charset)
|
||||||
html = fp.read()
|
html = fp.read()
|
||||||
except UnicodeDecodeError or LookupError:
|
except UnicodeDecodeError or LookupError:
|
||||||
fp = open(tmp_path, "r", encoding=FORMAT, errors="replace")
|
fp = open(tmp_path, "r", encoding=FORMAT, errors="replace")
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
class HTTPException(Exception):
|
class HTTPException(Exception):
|
||||||
""" Base class for HTTP exceptions """
|
"""
|
||||||
|
Base class for HTTP exceptions
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class UnhandledHTTPCode(Exception):
|
class UnhandledHTTPCode(Exception):
|
||||||
|
"""
|
||||||
|
Exception thrown if HTTP codes are not further processed.
|
||||||
|
"""
|
||||||
status_code: str
|
status_code: str
|
||||||
headers: str
|
headers: str
|
||||||
cause: str
|
cause: str
|
||||||
@@ -40,10 +45,12 @@ class UnsupportedEncoding(HTTPException):
|
|||||||
self.enc_type = enc_type
|
self.enc_type = enc_type
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedProtocol(HTTPException):
|
class UnsupportedProtocol(HTTPException):
|
||||||
"""
|
"""
|
||||||
Protocol is not supported
|
Protocol is not supported
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, protocol):
|
def __init__(self, protocol):
|
||||||
self.protocol = protocol
|
self.protocol = protocol
|
||||||
|
|
||||||
@@ -54,7 +61,9 @@ class IncompleteResponse(HTTPException):
|
|||||||
|
|
||||||
|
|
||||||
class HTTPServerException(HTTPException):
|
class HTTPServerException(HTTPException):
|
||||||
""" Base class for HTTP Server exceptions """
|
"""
|
||||||
|
Base class for HTTP Server exceptions
|
||||||
|
"""
|
||||||
status_code: str
|
status_code: str
|
||||||
message: str
|
message: str
|
||||||
body: str
|
body: str
|
||||||
@@ -66,29 +75,39 @@ class HTTPServerException(HTTPException):
|
|||||||
|
|
||||||
|
|
||||||
class HTTPServerCloseException(HTTPServerException):
|
class HTTPServerCloseException(HTTPServerException):
|
||||||
""" When thrown, the connection should be closed """
|
"""
|
||||||
|
When raised, the connection should be closed
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BadRequest(HTTPServerCloseException):
|
class BadRequest(HTTPServerCloseException):
|
||||||
""" Malformed HTTP request"""
|
"""
|
||||||
|
Malformed HTTP request
|
||||||
|
"""
|
||||||
status_code = 400
|
status_code = 400
|
||||||
message = "Bad Request"
|
message = "Bad Request"
|
||||||
|
|
||||||
|
|
||||||
class Forbidden(HTTPServerException):
|
class Forbidden(HTTPServerException):
|
||||||
""" Request not allowed """
|
"""
|
||||||
|
Request not allowed
|
||||||
|
"""
|
||||||
status_code = 403
|
status_code = 403
|
||||||
message = "Forbidden"
|
message = "Forbidden"
|
||||||
|
|
||||||
|
|
||||||
class NotFound(HTTPServerException):
|
class NotFound(HTTPServerException):
|
||||||
""" Resource not found """
|
"""
|
||||||
|
Resource not found
|
||||||
|
"""
|
||||||
status_code = 404
|
status_code = 404
|
||||||
message = "Not Found"
|
message = "Not Found"
|
||||||
|
|
||||||
|
|
||||||
class MethodNotAllowed(HTTPServerException):
|
class MethodNotAllowed(HTTPServerException):
|
||||||
""" Method is not allowed """
|
"""
|
||||||
|
Method is not allowed
|
||||||
|
"""
|
||||||
status_code = 405
|
status_code = 405
|
||||||
message = "Method Not Allowed"
|
message = "Method Not Allowed"
|
||||||
|
|
||||||
@@ -97,37 +116,49 @@ class MethodNotAllowed(HTTPServerException):
|
|||||||
|
|
||||||
|
|
||||||
class InternalServerError(HTTPServerCloseException):
|
class InternalServerError(HTTPServerCloseException):
|
||||||
""" Internal Server Error """
|
"""
|
||||||
|
Internal Server Error
|
||||||
|
"""
|
||||||
status_code = 500
|
status_code = 500
|
||||||
message = "Internal Server Error"
|
message = "Internal Server Error"
|
||||||
|
|
||||||
|
|
||||||
class NotImplemented(HTTPServerException):
|
class NotImplemented(HTTPServerException):
|
||||||
""" Functionality not implemented """
|
"""
|
||||||
|
Functionality not implemented
|
||||||
|
"""
|
||||||
status_code = 501
|
status_code = 501
|
||||||
message = "Not Implemented"
|
message = "Not Implemented"
|
||||||
|
|
||||||
|
|
||||||
class HTTPVersionNotSupported(HTTPServerCloseException):
|
class HTTPVersionNotSupported(HTTPServerCloseException):
|
||||||
""" The server does not support the major version HTTP used in the request message """
|
"""
|
||||||
|
The server does not support the major version HTTP used in the request message
|
||||||
|
"""
|
||||||
status_code = 505
|
status_code = 505
|
||||||
message = "HTTP Version Not Supported"
|
message = "HTTP Version Not Supported"
|
||||||
|
|
||||||
|
|
||||||
class Conflict(HTTPServerException):
|
class Conflict(HTTPServerException):
|
||||||
""" Conflict in the current state of the target resource """
|
"""
|
||||||
|
Conflict in the current state of the target resource
|
||||||
|
"""
|
||||||
status_code = 409
|
status_code = 409
|
||||||
message = "Conflict"
|
message = "Conflict"
|
||||||
|
|
||||||
|
|
||||||
class NotModified(HTTPServerException):
|
class NotModified(HTTPServerException):
|
||||||
""" Requested resource was not modified """
|
"""
|
||||||
|
Requested resource was not modified
|
||||||
|
"""
|
||||||
status_code = 304
|
status_code = 304
|
||||||
message = "Not Modified"
|
message = "Not Modified"
|
||||||
|
|
||||||
|
|
||||||
class InvalidRequestLine(BadRequest):
|
class InvalidRequestLine(BadRequest):
|
||||||
""" Request start-line is invalid """
|
"""
|
||||||
|
Request start-line is invalid
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, line):
|
def __init__(self, line):
|
||||||
self.request_line = line
|
self.request_line = line
|
||||||
|
@@ -3,8 +3,11 @@ import os
|
|||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
import urllib
|
import urllib
|
||||||
|
from datetime import datetime
|
||||||
|
from time import mktime
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from urllib.parse import urlparse, urlsplit
|
from urllib.parse import urlparse, urlsplit
|
||||||
|
from wsgiref.handlers import format_date_time
|
||||||
|
|
||||||
from httplib.exceptions import InvalidStatusLine, InvalidResponse, BadRequest, InvalidRequestLine
|
from httplib.exceptions import InvalidStatusLine, InvalidResponse, BadRequest, InvalidRequestLine
|
||||||
from httplib.httpsocket import FORMAT
|
from httplib.httpsocket import FORMAT
|
||||||
@@ -58,7 +61,7 @@ def parse_status_line(line: str):
|
|||||||
|
|
||||||
def parse_request_line(line: str):
|
def parse_request_line(line: str):
|
||||||
"""
|
"""
|
||||||
Parses the specified line as and HTTP request-line.
|
Parses the specified line as an HTTP request-line.
|
||||||
Returns the method, target as ParseResult and HTTP version from the request-line.
|
Returns the method, target as ParseResult and HTTP version from the request-line.
|
||||||
|
|
||||||
@param line: the request-line to be parsed
|
@param line: the request-line to be parsed
|
||||||
@@ -89,6 +92,7 @@ def parse_request_line(line: str):
|
|||||||
def parse_headers(lines):
|
def parse_headers(lines):
|
||||||
"""
|
"""
|
||||||
Parses the lines from the `lines` iterator as headers.
|
Parses the lines from the `lines` iterator as headers.
|
||||||
|
|
||||||
@param lines: iterator to retrieve the lines from.
|
@param lines: iterator to retrieve the lines from.
|
||||||
@return: A dictionary with header as key and value as value.
|
@return: A dictionary with header as key and value as value.
|
||||||
"""
|
"""
|
||||||
@@ -218,3 +222,12 @@ def get_relative_save_path(path: str):
|
|||||||
root = pathlib.PurePath(os.getcwd())
|
root = pathlib.PurePath(os.getcwd())
|
||||||
rel = path_obj.relative_to(root)
|
rel = path_obj.relative_to(root)
|
||||||
return str(rel)
|
return str(rel)
|
||||||
|
|
||||||
|
|
||||||
|
def get_date():
|
||||||
|
"""
|
||||||
|
Returns a string representation of the current date according to RFC 1123.
|
||||||
|
"""
|
||||||
|
now = datetime.now()
|
||||||
|
stamp = mktime(now.timetuple())
|
||||||
|
return format_date_time(stamp)
|
||||||
|
@@ -3,8 +3,6 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import mktime
|
|
||||||
from wsgiref.handlers import format_date_time
|
|
||||||
|
|
||||||
from httplib import parser
|
from httplib import parser
|
||||||
from httplib.exceptions import NotFound, Forbidden, NotModified
|
from httplib.exceptions import NotFound, Forbidden, NotModified
|
||||||
@@ -62,14 +60,6 @@ class AbstractCommand(ABC):
|
|||||||
def _conditional_headers(self):
|
def _conditional_headers(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _get_date(self):
|
|
||||||
"""
|
|
||||||
Returns a string representation of the current date according to RFC 1123.
|
|
||||||
"""
|
|
||||||
now = datetime.now()
|
|
||||||
stamp = mktime(now.timetuple())
|
|
||||||
return format_date_time(stamp)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self):
|
def execute(self):
|
||||||
pass
|
pass
|
||||||
@@ -81,7 +71,7 @@ class AbstractCommand(ABC):
|
|||||||
self._process_conditional_headers()
|
self._process_conditional_headers()
|
||||||
|
|
||||||
message = f"HTTP/1.1 {status} {status_message[status]}\r\n"
|
message = f"HTTP/1.1 {status} {status_message[status]}\r\n"
|
||||||
message += f"Date: {self._get_date()}\r\n"
|
message += f"Date: {parser.get_date()}\r\n"
|
||||||
|
|
||||||
content_length = len(body)
|
content_length = len(body)
|
||||||
message += f"Content-Length: {content_length}\r\n"
|
message += f"Content-Length: {content_length}\r\n"
|
||||||
|
@@ -1,12 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
|
||||||
from socket import socket
|
from socket import socket
|
||||||
from time import mktime
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from urllib.parse import ParseResultBytes, ParseResult
|
from urllib.parse import ParseResultBytes, ParseResult
|
||||||
from wsgiref.handlers import format_date_time
|
|
||||||
|
|
||||||
from httplib import parser
|
from httplib import parser
|
||||||
from httplib.exceptions import MethodNotAllowed, BadRequest, UnsupportedEncoding, NotImplemented, NotFound, \
|
from httplib.exceptions import MethodNotAllowed, BadRequest, UnsupportedEncoding, NotImplemented, NotFound, \
|
||||||
@@ -21,8 +18,11 @@ METHODS = ("GET", "HEAD", "PUT", "POST")
|
|||||||
|
|
||||||
|
|
||||||
class RequestHandler:
|
class RequestHandler:
|
||||||
|
"""
|
||||||
|
Processes incoming HTTP request messages.
|
||||||
|
"""
|
||||||
|
|
||||||
conn: HTTPSocket
|
conn: HTTPSocket
|
||||||
root = os.path.join(os.path.dirname(sys.argv[0]), "public")
|
|
||||||
|
|
||||||
def __init__(self, conn: socket, host):
|
def __init__(self, conn: socket, host):
|
||||||
self.conn = ServerSocket(conn, host)
|
self.conn = ServerSocket(conn, host)
|
||||||
@@ -42,15 +42,20 @@ class RequestHandler:
|
|||||||
|
|
||||||
def _handle_message(self, retriever, line):
|
def _handle_message(self, retriever, line):
|
||||||
lines = retriever.retrieve()
|
lines = retriever.retrieve()
|
||||||
|
|
||||||
|
# Parse the request-line and headers
|
||||||
(method, target, version) = parser.parse_request_line(line)
|
(method, target, version) = parser.parse_request_line(line)
|
||||||
headers = parser.parse_headers(lines)
|
headers = parser.parse_headers(lines)
|
||||||
|
|
||||||
|
# Create the response message object
|
||||||
message = Message(version, method, target, headers, retriever.buffer)
|
message = Message(version, method, target, headers, retriever.buffer)
|
||||||
|
|
||||||
logging.debug("---request begin---\r\n%s---request end---", "".join(message.raw))
|
logging.debug("---request begin---\r\n%s---request end---", "".join(message.raw))
|
||||||
|
|
||||||
|
# validate if the request is valid
|
||||||
self._validate_request(message)
|
self._validate_request(message)
|
||||||
|
|
||||||
|
# The body (if available) hasn't been retrieved up till now.
|
||||||
body = b""
|
body = b""
|
||||||
if self._has_body(headers):
|
if self._has_body(headers):
|
||||||
try:
|
try:
|
||||||
@@ -64,14 +69,25 @@ class RequestHandler:
|
|||||||
|
|
||||||
message.body = body
|
message.body = body
|
||||||
|
|
||||||
# completed message
|
# message completed
|
||||||
|
|
||||||
cmd = command.create(message)
|
cmd = command.create(message)
|
||||||
msg = cmd.execute()
|
msg = cmd.execute()
|
||||||
|
|
||||||
logging.debug("---response begin---\r\n%s\r\n---response end---", msg.split(b"\r\n\r\n", 1)[0].decode(FORMAT))
|
logging.debug("---response begin---\r\n%s\r\n---response end---", msg.split(b"\r\n\r\n", 1)[0].decode(FORMAT))
|
||||||
|
# Send the response message
|
||||||
self.conn.conn.sendall(msg)
|
self.conn.conn.sendall(msg)
|
||||||
|
|
||||||
def _check_request_line(self, method: str, target: Union[ParseResultBytes, ParseResult], version):
|
def _check_request_line(self, method: str, target: Union[ParseResultBytes, ParseResult], version):
|
||||||
|
"""
|
||||||
|
Checks if the request-line is valid. Throws an appriopriate exception if not.
|
||||||
|
@param method: HTTP request method
|
||||||
|
@param target: The request target
|
||||||
|
@param version: The HTTP version
|
||||||
|
@raise MethodNotAllowed: if the method is not any of the allowed methods in `METHODS`
|
||||||
|
@raise HTTPVersionNotSupported: If the HTTP version is not supported by this server
|
||||||
|
@raise BadRequest: If the scheme of the target is not supported
|
||||||
|
@raise NotFound: If the target is not found on this server
|
||||||
|
"""
|
||||||
|
|
||||||
if method not in METHODS:
|
if method not in METHODS:
|
||||||
raise MethodNotAllowed(METHODS)
|
raise MethodNotAllowed(METHODS)
|
||||||
@@ -91,12 +107,25 @@ class RequestHandler:
|
|||||||
raise NotFound(str(target))
|
raise NotFound(str(target))
|
||||||
|
|
||||||
def _validate_request(self, msg):
|
def _validate_request(self, msg):
|
||||||
|
"""
|
||||||
|
Validates the message request-line and headers. Throws an error if the message is invalid.
|
||||||
|
|
||||||
|
@see: _check_request_line for exceptions raised when validating the request-line.
|
||||||
|
@param msg: the message to validate
|
||||||
|
@raise BadRequest: if HTTP 1.1 and the Host header is missing
|
||||||
|
"""
|
||||||
|
|
||||||
if msg.version == "1.1" and "host" not in msg.headers:
|
if msg.version == "1.1" and "host" not in msg.headers:
|
||||||
raise BadRequest("Missing host header")
|
raise BadRequest("Missing host header")
|
||||||
|
|
||||||
self._check_request_line(msg.method, msg.target, msg.version)
|
self._check_request_line(msg.method, msg.target, msg.version)
|
||||||
|
|
||||||
def _has_body(self, headers):
|
def _has_body(self, headers):
|
||||||
|
"""
|
||||||
|
Check if the headers notify the existing of a message body.
|
||||||
|
@param headers: the headers to check
|
||||||
|
@return: True if the message has a body. False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
if "transfer-encoding" in headers:
|
if "transfer-encoding" in headers:
|
||||||
return True
|
return True
|
||||||
@@ -106,16 +135,10 @@ class RequestHandler:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_date():
|
|
||||||
now = datetime.now()
|
|
||||||
stamp = mktime(now.timetuple())
|
|
||||||
return format_date_time(stamp)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send_error(client: socket, code, message):
|
def send_error(client: socket, code, message):
|
||||||
message = f"HTTP/1.1 {code} {message}\r\n"
|
message = f"HTTP/1.1 {code} {message}\r\n"
|
||||||
message += RequestHandler._get_date() + "\r\n"
|
message += parser.get_date() + "\r\n"
|
||||||
message += "Content-Length: 0\r\n"
|
message += "Content-Length: 0\r\n"
|
||||||
message += "\r\n"
|
message += "\r\n"
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user