Skip to content

Commit

Permalink
Initial build, add basic HTTP, server, and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenDude101 committed Sep 12, 2023
1 parent a59ebb0 commit c4bd482
Show file tree
Hide file tree
Showing 26 changed files with 1,195 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .exampleEnv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#
# An example .env file.
# With all module used keys.
#

HANDLER = MirrorHandler
SERVER = SocketServer

HOSTNAME = localhost
PORT = 80
4 changes: 4 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import protoserver

server = protoserver.ProtoServer()
server.run()
6 changes: 6 additions & 0 deletions protoserver/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from protoserver.config import *
from protoserver.protoServer import *

from protoserver import handlers
from protoserver import http
from protoserver import servers
67 changes: 67 additions & 0 deletions protoserver/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from __future__ import annotations
import dotenv
from typing import Mapping, TypeVar, TYPE_CHECKING

if TYPE_CHECKING:
from protoserver.handlers.iHandler import IHandler
from protoserver.servers.iServer import IServer


_T = TypeVar("_T")

class Config(object):

MINIMAL_VALID_CONFIG_OPTIONS = {
"HANDLER": "MirrorHandler",
"SERVER": "FunctionServer",
}

_registeredHandlers: dict[str, type[IHandler]] = {}
_registeredServers: dict[str, type[IServer]] = {}

def __init__(self, data: Mapping[str, None | str]) -> None:
self._config = data

possibleHandlers = self._registeredHandlers.keys()
if self._config["HANDLER"] not in possibleHandlers:
raise ValueError(f"HANDLER not correctly set in .env.\nMust be one of:\n\t{', '.join(possibleHandlers)}")

possibleServers = self._registeredServers.keys()
if self._config["SERVER"] not in possibleServers:
raise ValueError(f"SERVER not correctly set in .env.\nMust be one of:\n\t{', '.join(possibleServers)}")

@classmethod
def loadFromFile(cls, path = ".env") -> Config:
return Config(dotenv.dotenv_values(path))

@classmethod
def getMinimalValidConfig(cls, data: None | Mapping[str, None | str] = None) -> Config:
if data is None: data = {}
return Config(Config.MINIMAL_VALID_CONFIG_OPTIONS | data)


def get(self, key: str, type: type[_T] = str) -> _T:
if key not in self._config:
raise KeyError(f"Unknown config option {key}.\nMaybe it wasn't in .exampleEnv?")

value = self._config[key]
if value is None:
raise ValueError(f"{key} has no default value, and wasn't set in .env.")

return type(value)

def getHandler(self) -> type[IHandler]:
handler = self.get("HANDLER")
return self._registeredHandlers[handler]

def getServer(self) -> type[IServer]:
server = self.get("SERVER")
return self._registeredServers[server]

@classmethod
def registerHandler(cls, name: str, handler: type[IHandler]) -> None:
cls._registeredHandlers[name] = handler

@classmethod
def registerServer(cls, name: str, server: type[IServer]) -> None:
cls._registeredServers[name] = server
2 changes: 2 additions & 0 deletions protoserver/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from protoserver.handlers.iHandler import IHandler
from protoserver.handlers.mirrorHandler import MirrorHandler
21 changes: 21 additions & 0 deletions protoserver/handlers/iHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from protoserver import Config
from protoserver.http import Request, Response, StatusCode


class IHandler(ABC):

def __init__(self, config: Config) -> None:
self.config = config

@abstractmethod
def handleRequest(self, request: Request) -> None | Response:
raise NotImplementedError()

@abstractmethod
def handleError(self, statusCode: StatusCode) -> None | Response:
raise NotImplementedError()
25 changes: 25 additions & 0 deletions protoserver/handlers/mirrorHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import annotations
from typing import TYPE_CHECKING

from protoserver.config import Config
from protoserver.handlers.iHandler import IHandler
from protoserver.http.response import Response

if TYPE_CHECKING:
from protoserver.http.request import Request
from protoserver.http.statusCodes import StatusCode


class MirrorHandler(IHandler):

def handleRequest(self, request: Request) -> Response | None:
response = Response()

response.setBody(request.raw.decode("iso-8859-1"))

return response

def handleError(self, statusCode: StatusCode) -> Response | None:
return super().handleError(statusCode)

Config.registerHandler("MirrorHandler", MirrorHandler)
4 changes: 4 additions & 0 deletions protoserver/http/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from protoserver.http.client import Client
from protoserver.http.request import Request
from protoserver.http.response import Response
from protoserver.http.statusCodes import StatusCode
63 changes: 63 additions & 0 deletions protoserver/http/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations
from threading import Thread
from typing import TYPE_CHECKING

from protoserver.http.request import Request
from protoserver.http.response import Response
from protoserver.http.statusCodes import StatusCode

if TYPE_CHECKING:
from protoserver import Config
from protoserver.handlers import IHandler
from protoserver.servers import IConnection


class Client(object):

def __init__(self, config: Config, connection: IConnection, handler: IHandler) -> None:
self.config = config
self.connection = connection
self.handler = handler

self.isRunning = True
self.thread = Thread(target = self.loop)
self.thread.start()

def loop(self) -> None:

while self.isRunning:
request = self.recv()
if request is None:
continue

response = self.handler.handleRequest(request)
if response is None:
continue

self.send(response)

self.close()

def recv(self) -> None | Request:
requestBytes = self.connection.recv()
if requestBytes == b"":
self.isRunning = False
return None

request = Request(requestBytes)

match (request.status):
case "OK":
return request
case "BAD_REQUEST":
response = self.handler.handleError(StatusCode.BAD_REQUEST)
if response is not None:
self.send(response)
return None

def send(self, response: Response) -> None:
self.connection.send(response.build())

def close(self) -> None:
self.connection.close()

Loading

0 comments on commit c4bd482

Please sign in to comment.