Added code for a server to maybe run and respond to requests based on handlers registered with the server.
This commit is contained in:
@@ -5,6 +5,7 @@ from msgpack import packb, unpackb
|
||||
from enum import Enum
|
||||
import bz2
|
||||
from typing import Union, Self
|
||||
import datetime
|
||||
|
||||
|
||||
class PacketServerConnection(Connection):
|
||||
@@ -17,6 +18,23 @@ class PacketServerConnection(Connection):
|
||||
# Now perform any initialization of your own that you might need
|
||||
self.data = Unpacker()
|
||||
self.data_lock = Lock()
|
||||
self.connection_created = datetime.datetime.now(datetime.UTC)
|
||||
self.connection_last_activity = datetime.datetime.now(datetime.UTC)
|
||||
|
||||
|
||||
@property
|
||||
def local_callsign(self):
|
||||
if self.incoming:
|
||||
return self.call_to
|
||||
else:
|
||||
return self.call_from
|
||||
|
||||
@property
|
||||
def remote_callsign(self):
|
||||
if self.incoming:
|
||||
return self.call_from
|
||||
else:
|
||||
return self.call_to
|
||||
|
||||
def connected(self):
|
||||
print("connected")
|
||||
@@ -27,11 +45,16 @@ class PacketServerConnection(Connection):
|
||||
pass
|
||||
|
||||
def data_received(self, pid, data):
|
||||
self.connection_last_activity = datetime.datetime.now(datetime.UTC)
|
||||
with self.data_lock:
|
||||
self.data.feed(data)
|
||||
for fn in PacketServerConnection.receive_subscribers:
|
||||
fn(self)
|
||||
|
||||
def send_data(self, data: Union[bytes, bytearray]):
|
||||
self.connection_last_activity = datetime.datetime.now(datetime.UTC)
|
||||
super().send_data(data)
|
||||
|
||||
@classmethod
|
||||
def query_accept(cls, port, call_from, call_to):
|
||||
return True
|
||||
@@ -117,15 +140,8 @@ class Message:
|
||||
self.data['d'] = str(payload)
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, msg_bytes: bytes) -> Self:
|
||||
try:
|
||||
unpacked = unpackb(msg_bytes)
|
||||
except Exception as e:
|
||||
raise ValueError("ERROR: msg_bytes didn't contain a valid msgpack object.\n" + str(e))
|
||||
for i in ('t', 'c', 'd'):
|
||||
if i not in unpacked:
|
||||
raise ValueError("ERROR: unpacked bytes do not contain a valid Message object.")
|
||||
|
||||
def partial_unpack(cls, msg: dict) -> Self:
|
||||
unpacked = msg
|
||||
comp = Message.CompressionType(unpacked['c'])
|
||||
msg_type = Message.MessageType(unpacked['t'])
|
||||
raw_data = unpacked['d']
|
||||
@@ -136,8 +152,22 @@ class Message:
|
||||
data = unpackb(bz2.decompress(raw_data))
|
||||
else:
|
||||
raise NotImplementedError(f"Compression type {comp.name} is not implemented yet.")
|
||||
|
||||
return Message(msg_type, comp, data)
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, msg_bytes: bytes) -> Self:
|
||||
try:
|
||||
unpacked = unpackb(msg_bytes)
|
||||
except Exception as e:
|
||||
raise ValueError("ERROR: msg_bytes didn't contain a valid msgpack object.\n" + str(e))
|
||||
if type(unpacked) is not dict:
|
||||
raise ValueError("ERROR: unpacked message was not a packetserver message.")
|
||||
for i in ('t', 'c', 'd'):
|
||||
if i not in unpacked:
|
||||
raise ValueError("ERROR: unpacked message was not a packetserver message.")
|
||||
return Message.partial_unpack(unpacked)
|
||||
|
||||
class Request(Message):
|
||||
class Method(Enum):
|
||||
GET = 0
|
||||
@@ -162,7 +192,7 @@ class Request(Message):
|
||||
@property
|
||||
def path(self):
|
||||
if 'p' in self.data:
|
||||
return str(self.data['p'])
|
||||
return str(self.data['p']).lower().strip()
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import pe
|
||||
import pe.app
|
||||
from ..common import PacketServerConnection
|
||||
from .constants import default_server_config
|
||||
from copy import deepcopy
|
||||
import ax25
|
||||
from pathlib import Path
|
||||
import ZODB, transaction, ZODB.FileStorage
|
||||
import ZODB, ZODB.FileStorage
|
||||
from BTrees.OOBTree import OOBTree
|
||||
from .requests import process_incoming_data
|
||||
from .requests import standard_handlers
|
||||
|
||||
|
||||
class Server:
|
||||
def __init__(self, pe_server: str, port: int, server_callsign: str, data_dir: str = None):
|
||||
@@ -11,6 +17,7 @@ class Server:
|
||||
self.callsign = server_callsign
|
||||
self.pe_server = pe_server
|
||||
self.pe_port = port
|
||||
self.handlers = deepcopy(standard_handlers)
|
||||
if data_dir:
|
||||
data_path = Path(data_dir)
|
||||
else:
|
||||
@@ -28,13 +35,25 @@ class Server:
|
||||
self.home_dir = data_dir
|
||||
self.storage = ZODB.FileStorage.FileStorage(self.data_file)
|
||||
self.db = ZODB.DB(self.storage)
|
||||
with self.db.transaction() as conn:
|
||||
if 'config' not in conn.root():
|
||||
conn.root.config = deepcopy(default_server_config)
|
||||
if 'users' not in conn.root():
|
||||
conn.root.users = OOBTree()
|
||||
self.app = pe.app.Application()
|
||||
PacketServerConnection.receive_subscribers.append(lambda x: self.server_receiver(x))
|
||||
|
||||
|
||||
@property
|
||||
def data_file(self) -> str:
|
||||
return str(Path(self.home_dir).joinpath('data.zopedb'))
|
||||
|
||||
|
||||
def server_receiver(self, conn: PacketServerConnection):
|
||||
pass
|
||||
pass
|
||||
process_incoming_data(conn, self)
|
||||
|
||||
def start(self):
|
||||
self.app.start(self.pe_server, self.pe_port)
|
||||
self.app.register_callsigns(self.callsign)
|
||||
|
||||
def stop(self):
|
||||
self.app.stop()
|
||||
5
src/packetserver/server/constants.py
Normal file
5
src/packetserver/server/constants.py
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
default_server_config = {
|
||||
"motd": "Welcome to this PacketServer BBS!",
|
||||
"operator": "placeholder",
|
||||
}
|
||||
66
src/packetserver/server/requests.py
Normal file
66
src/packetserver/server/requests.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""Module for handling requests as they arrive to connection objects and servers."""
|
||||
|
||||
from . import PacketServerConnection
|
||||
from . import Server
|
||||
from msgpack.exceptions import OutOfData
|
||||
from ..common import Message, Request, Response
|
||||
|
||||
def handle_root_get(req: Request, conn: PacketServerConnection, server: Server):
|
||||
response = Response.blank()
|
||||
response.compression = Message.CompressionType.BZIP2
|
||||
operator = ""
|
||||
motd = ""
|
||||
with server.db.transaction() as storage:
|
||||
if 'motd' in storage.root.config:
|
||||
motd = storage.root.config['motd']
|
||||
if 'operator' in storage.root.config:
|
||||
operator = storage.root.config['operator']
|
||||
|
||||
response.payload = {
|
||||
'operator': operator,
|
||||
'motd': motd
|
||||
}
|
||||
|
||||
if conn.state.name == "CONNECTED":
|
||||
conn.send_data(response.pack())
|
||||
|
||||
standard_handlers = {
|
||||
"": {
|
||||
"GET": handle_root_get
|
||||
}
|
||||
}
|
||||
|
||||
def handle_request(req: Request, conn: PacketServerConnection, server: Server):
|
||||
"""Handles a proper request by handing off to the appropriate function depending on method and Path."""
|
||||
if req.path in server.handlers:
|
||||
if req.method.name in server.handlers[req.path]:
|
||||
server.handlers[req.path][req.method.name](req, conn, server)
|
||||
return
|
||||
response_404 = Response.blank()
|
||||
response_404.status_code = 404
|
||||
if conn.state.name == "CONNECTED":
|
||||
conn.send_data(response_404.pack())
|
||||
|
||||
def process_incoming_data(connection: PacketServerConnection, server: Server):
|
||||
"""Handles incoming data."""
|
||||
with connection.data_lock:
|
||||
while True:
|
||||
try:
|
||||
msg = Message.partial_unpack(connection.data.unpack())
|
||||
except OutOfData:
|
||||
break
|
||||
except ValueError:
|
||||
r = Response.blank()
|
||||
r.status_code = 400
|
||||
r.payload = "BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE"
|
||||
connection.send_data(r.pack())
|
||||
connection.send_data(b"BAD REQUEST. COULD NOT PARSE INCOMING DATA AS PACKETSERVER MESSAGE")
|
||||
|
||||
try:
|
||||
request = Request(msg)
|
||||
except ValueError:
|
||||
r = Response.blank()
|
||||
r.status_code = 400
|
||||
r.payload = "BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE."
|
||||
connection.send_data(r.pack())
|
||||
connection.send_data(b"BAD REQUEST. DID NOT RECEIVE A REQUEST MESSAGE.")
|
||||
Reference in New Issue
Block a user