diff --git a/client.py b/client.py index e2718ed..9d19d7c 100644 --- a/client.py +++ b/client.py @@ -26,9 +26,9 @@ def main(): try: main() except UnhandledHTTPCode as e: - print(f"[{e.status_code}] {e.cause}:\r\n{e.headers}") + logging.info(f"[{e.status_code}] {e.cause}:\r\n{e.headers}") sys.exit(2) except Exception as e: - print("[ABRT] Internal error: " + str(e), file=sys.stderr) + logging.info("[ABRT] Internal error: %s", e) logging.debug("Internal error", exc_info=e) sys.exit(1) diff --git a/client/command.py b/client/command.py index cc43154..c2f2e46 100644 --- a/client/command.py +++ b/client/command.py @@ -125,17 +125,34 @@ class AbstractCommand(ABC): if not sub_request: client.close() + + + def _get_preamble(self, client): + """ + Returns the preamble (start-line and headers) of the response of this command. + @param client: the client object to retrieve from + @return: A Message object containing the HTTP-version, status code, status message, headers and buffer + """ + retriever = PreambleRetriever(client) + lines = retriever.retrieve() + (version, status, msg) = parser.parse_status_line(next(lines)) + headers = parser.parse_headers(lines) + + buffer = retriever.buffer + logging.debug("---response begin---\r\n%s---response end---", "".join(buffer)) + + return Message(version, status, msg, headers, buffer) + def _await_response(self, client): """ Simple response method. Receives the response and prints to stdout. """ - while True: - line = client.read_line() - print(line, end="") - if line in ("\r\n", "\n", ""): - break + + msg = self._get_preamble(client) + + print("".join(msg.raw)) def _build_message(self, message: str) -> bytes: return (message + "\r\n").encode(FORMAT) @@ -163,25 +180,6 @@ class AbstractCommand(ABC): return host, path -class AbstractWithBodyCommand(AbstractCommand, ABC): - """ - The building block for creating an HTTP message for an HTTP method with a body (POST and PUT). - """ - - def _build_message(self, message: str) -> bytes: - body = input(f"Enter {self.method} data: ").encode(FORMAT) - print() - - message += "Content-Type: text/plain\r\n" - message += f"Content-Length: {len(body)}\r\n" - message += "\r\n" - message = message.encode(FORMAT) - message += body - message += b"\r\n" - - return message - - class HeadCommand(AbstractCommand): """ A Command for sending a `HEAD` request. @@ -207,22 +205,6 @@ class GetCommand(AbstractCommand): def method(self): return "GET" - def _get_preamble(self, client): - """ - Returns the preamble (start-line and headers) of the response of this command. - @param client: the client object to retrieve from - @return: A Message object containing the HTTP-version, status code, status message, headers and buffer - """ - retriever = PreambleRetriever(client) - lines = retriever.retrieve() - (version, status, msg) = parser.parse_status_line(next(lines)) - headers = parser.parse_headers(lines) - - buffer = retriever.buffer - logging.debug("---response begin---\r\n%s---response end---", "".join(buffer)) - - return Message(version, status, msg, headers, buffer) - def _await_response(self, client): """ Handles the response of this command. @@ -233,6 +215,27 @@ class GetCommand(AbstractCommand): self.filename = responsehandler.handle(client, msg, self, self.dir) +class AbstractWithBodyCommand(AbstractCommand, ABC): + """ + The building block for creating an HTTP message for an HTTP method with a body (POST and PUT). + """ + + def _build_message(self, message: str) -> bytes: + input_line = input(f"Enter {self.method} data: ") + input_line += "\r\n" + body = input_line.encode(FORMAT) + print() + + message += "Content-Type: text/plain\r\n" + message += f"Content-Length: {len(body)}\r\n" + message += "\r\n" + message = message.encode(FORMAT) + message += body + message += b"\r\n" + + return message + + class PostCommand(AbstractWithBodyCommand): """ A command for sending a `POST` request. diff --git a/client/htmlparser.py b/client/htmlparser.py deleted file mode 100644 index ebd91da..0000000 --- a/client/htmlparser.py +++ /dev/null @@ -1,6 +0,0 @@ -from bs4 import BeautifulSoup - - -class HTMLParser: - def __init__(self, soup: BeautifulSoup): - pass \ No newline at end of file diff --git a/client/responsehandler.py b/client/responsehandler.py index 719d80a..37863e6 100644 --- a/client/responsehandler.py +++ b/client/responsehandler.py @@ -65,7 +65,7 @@ class ResponseHandler(ABC): class BasicResponseHandler(ResponseHandler): """ - Response handler which skips the body of the message and only shows the headers. + Response handler which will handle redirects and other HTTP status codes. In case of a redirect, it will process it and pass it to the appropriate response handler. """ diff --git a/httplib/exceptions.py b/httplib/exceptions.py index 555d989..b4955cd 100644 --- a/httplib/exceptions.py +++ b/httplib/exceptions.py @@ -66,12 +66,10 @@ class HTTPServerException(HTTPException): """ status_code: str message: str - body: str arg: str - def __init__(self, arg, body=""): + def __init__(self, arg): self.arg = arg - self.body = body class HTTPServerCloseException(HTTPServerException): @@ -160,5 +158,6 @@ class InvalidRequestLine(BadRequest): Request start-line is invalid """ - def __init__(self, line): + def __init__(self, line, arg): + super().__init__(arg) self.request_line = line diff --git a/httplib/parser.py b/httplib/parser.py index d77c88b..88f8025 100644 --- a/httplib/parser.py +++ b/httplib/parser.py @@ -72,7 +72,7 @@ def parse_request_line(line: str): split = list(filter(None, line.rstrip().split(" ", 2))) if len(split) < 3: - raise InvalidRequestLine(line) + raise InvalidRequestLine(line, "missing argument in request-line") method, target, version = split if method not in ("CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT", "TRACE"): diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index cd4ef97..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -lxml~=4.6.2 \ No newline at end of file diff --git a/server/command.py b/server/command.py index fa46580..bc39f83 100644 --- a/server/command.py +++ b/server/command.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from datetime import datetime from httplib import parser -from httplib.exceptions import NotFound, Forbidden, NotModified +from httplib.exceptions import NotFound, Forbidden, NotModified, BadRequest from httplib.httpsocket import FORMAT from httplib.message import RequestMessage as Message @@ -66,8 +66,6 @@ class AbstractCommand(ABC): def _build_message(self, status: int, content_type: str, body: bytes, extra_headers=None): - if extra_headers is None: - extra_headers = {} self._process_conditional_headers() message = f"HTTP/1.1 {status} {status_message[status]}\r\n" @@ -237,3 +235,9 @@ class PutCommand(AbstractModifyCommand): @property def _file_mode(self): return "w" + + def execute(self): + if "content-range" in self.msg.headers: + raise BadRequest("PUT request contains Content-Range header") + + super().execute() diff --git a/server/requesthandler.py b/server/requesthandler.py index d9d9212..7fb40fb 100644 --- a/server/requesthandler.py +++ b/server/requesthandler.py @@ -1,6 +1,4 @@ import logging -import os -import sys from socket import socket from typing import Union from urllib.parse import ParseResultBytes, ParseResult diff --git a/server/worker.py b/server/worker.py index f12b788..28e37ca 100644 --- a/server/worker.py +++ b/server/worker.py @@ -85,6 +85,7 @@ class Worker: RequestHandler.send_error(conn, InternalServerError.status_code, InternalServerError.message) break + logging.info("Closing socket for client %s", addr) conn.shutdown(socket.SHUT_RDWR) conn.close() # Finished, put back into queue