This commit is contained in:
2021-03-26 18:25:03 +01:00
parent 7476870acc
commit fdbd865889
11 changed files with 297 additions and 136 deletions

View File

@@ -1,25 +1,39 @@
import logging
import mimetypes
import os
import sys
from abc import ABC, abstractmethod
from typing import Dict, Tuple
from urllib.parse import urlparse
from datetime import datetime
from time import mktime
from typing import Dict
from wsgiref.handlers import format_date_time
from client.httpclient import FORMAT, HTTPClient
from httplib import parser
from httplib.exceptions import InvalidResponse, InvalidStatusLine, UnsupportedEncoding
from httplib.message import Message
from httplib.retriever import PreambleRetriever
from client.httpclient import FORMAT
from httplib.exceptions import NotFound, Conflict, Forbidden
from httplib.message import ServerMessage as Message
root = os.path.join(os.path.dirname(sys.argv[0]), "public")
status_message = {
200: "OK",
201: "Created",
202: "Accepted",
304: "Not Modified",
400: "Bad Request",
404: "Not Found",
500: "Internal Server Error",
}
def create(method: str, message: Message):
if method == "GET":
return GetCommand(url, port)
elif method == "HEAD":
return HeadCommand(url, port)
elif method == "POST":
return PostCommand(url, port)
elif method == "PUT":
return PutCommand(url, port)
def create(message: Message):
if message.method == "GET":
return GetCommand(message)
elif message.method == "HEAD":
return HeadCommand(message)
elif message.method == "POST":
return PostCommand(message)
elif message.method == "PUT":
return PutCommand(message)
else:
raise ValueError()
@@ -27,8 +41,10 @@ def create(method: str, message: Message):
class AbstractCommand(ABC):
path: str
headers: Dict[str, str]
msg: Message
def __init(self):
def __init__(self, message: Message):
self.msg = message
pass
@property
@@ -36,63 +52,133 @@ class AbstractCommand(ABC):
def command(self):
pass
def _get_date(self):
now = datetime.now()
stamp = mktime(now.timetuple())
return format_date_time(stamp)
class AbstractWithBodyCommand(AbstractCommand, ABC):
@abstractmethod
def execute(self):
pass
def _build_message(self, message: str) -> bytes:
body = input(f"Enter {self.command} data: ").encode(FORMAT)
print()
def _build_message(self, status: int, content_type: str, body: bytes):
message = f"HTTP/1.1 {status} {status_message[status]}\r\n"
message += self._get_date() + "\r\n"
content_length = len(body)
message += f"Content-Length: {content_length}\r\n"
if content_type:
message += f"Content-Type: {content_type}"
if content_type.startswith("text"):
message += "; charset=UTF-8"
message += "\r\n"
elif content_length > 0:
message += f"Content-Type: application/octet-stream"
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"
if content_length > 0:
message += body
message += b"\r\n"
return message
def _get_path(self, check=True):
norm_path = os.path.normpath(self.msg.target.path)
if norm_path == "/":
path = root + "/index.html"
else:
path = root + norm_path
if check and not os.path.exists(path):
raise NotFound()
return path
class AbstractModifyCommand(AbstractCommand, ABC):
@property
@abstractmethod
def _file_mode(self):
pass
def execute(self):
path = self._get_path(False)
dir = os.path.dirname(path)
if not os.path.exists(dir):
raise Forbidden("Target directory does not exists!")
if os.path.exists(dir) and not os.path.isdir(dir):
raise Forbidden("Target directory is an existing file!")
try:
with open(path, mode=f"{self._file_mode}b") as file:
file.write(self.msg.body)
except IsADirectoryError:
raise Forbidden("The target resource is a directory!")
class HeadCommand(AbstractCommand):
def execute(self):
path = self._get_path()
mime = mimetypes.guess_type(path)[0]
return self._build_message(200, mime, b"")
@property
def command(self):
return "HEAD"
class GetCommand(AbstractCommand):
def __init__(self, uri: str, port, dir=None):
super().__init__(uri, port)
self.dir = dir
self.filename = None
@property
def command(self):
return "GET"
def _get_preamble(self, retriever):
lines = retriever.retrieve()
(version, status, msg) = parser.parse_status_line(next(lines))
headers = parser.parse_headers(lines)
def get_mimetype(self, path):
mime = mimetypes.guess_type(path)[0]
logging.debug("---response begin---\r\n%s--- response end---", "".join(retriever.buffer))
if mime:
return mime
return Message(version, status, msg, headers, retriever.buffer)
try:
file = open(path, "r", encoding="utf-8")
file.readline()
file.close()
return "text/plain"
except UnicodeDecodeError:
return "application/octet-stream"
def _await_response(self, client, retriever):
msg = self._get_preamble(retriever)
def execute(self):
path = self._get_path()
mime = self.get_mimetype(path)
from client import response_handler
self.filename = response_handler.handle(client, msg, self, self.dir)
file = open(path, "rb")
buffer = file.read()
file.close()
return self._build_message(200, mime, buffer)
class PostCommand(AbstractWithBodyCommand):
class PostCommand(AbstractModifyCommand):
@property
def command(self):
return "POST"
@property
def _file_mode(self):
return "a"
class PutCommand(AbstractWithBodyCommand):
class PutCommand(AbstractModifyCommand):
@property
def command(self):
return "PUT"
@property
def _file_mode(self):
return "w"