109 lines
3.1 KiB
Python
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
|
|
|
|
|