ue_pe_web/scripts/03_server/src/myserver/server.py

109 lines
3.1 KiB
Python

######################################################################
# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
#
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
######################################################################
"""
A package for learning network programming in Python.
This module (file) manages the socket connections and multi-threading of clients.
"""
import socket
from myserver.log import log, log_reply
from myserver.http_request import parse_request
from myserver.file import resolve_location, get_resource
from myserver.date import now_rfc2616
from myserver.http import get_http_code, get_http_content_type
_BUF_SIZE = 1024
_SERVER_ADDR = "0.0.0.0"
def serve(port: int, root: str):
"""
Serves http request connections for clients.
This function creates the network socket, listens, and calls
:func:`~myserver.handle_client()` when a request comes in.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Allows reusing a socket right after it got closed (after ctrl-c)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((_SERVER_ADDR, port))
s.listen()
log(f"Server started at {_SERVER_ADDR}:{port}.")
try: # Catch KeyboardInterrupt to close server socket
while True:
c, addr = s.accept()
try: # Catch KeyboardInterrupt to close client handling socket
handle_client(c, addr, root)
except KeyboardInterrupt as e:
c.close()
raise e
except KeyboardInterrupt:
log("Received KeyboardInterrupt. Closing...")
s.close()
def handle_client(c: socket.socket, addr: tuple[str, int], root:str):
buf = c.recv(_BUF_SIZE)
req = parse_request(buf)
reply, code = handle_request(root, req)
c.send(reply)
log_reply(addr, req, code)
c.close()
def handle_request(root:str, req: dict):
if req['head']['verb'] == 'GET':
return prepare_resource(root, req)
else:
# Not implemented
return prepare_reply(b"", "", 501)
def prepare_resource(root: str, req: dict):
code = 200
content = b""
content_type = ""
res_path, res_extension = resolve_location(req['head']['resource'], root)
if res_path == "":
code = 404
else:
content_type = get_http_content_type(res_extension)
content, code = get_resource(res_path)
return prepare_reply(content, content_type, code)
def prepare_reply(content: bytes, content_type: str, code: int):
# Prepare status code
http_code_dict = get_http_code(code)
if code != 200:
content = http_code_dict['html'].encode()
content_type = get_http_content_type('html')+"; charset=utf-8"
# Prepare header
header = f"""HTTP/1.0 {http_code_dict['header']}
Content-Type: {content_type}
Date: {now_rfc2616()}
Content-Length: {len(content)}
Server: RegardeMamanJeFaisUnServeurWeb/0.1
""".encode()
return header + content, code