src.myserver.server
A package for learning network programming in Python.
This part manages the socket connections and multi-threading of clients.
1###################################################################### 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari 3# 4# This program and the accompanying materials are made 5# available under the terms of the Eclipse Public License 2.0 6# which is available at https://www.eclipse.org/legal/epl-2.0/ 7# 8# SPDX-License-Identifier: EPL-2.0 9###################################################################### 10 11""" 12A package for learning network programming in Python. 13 14This part manages the socket connections and multi-threading of clients. 15""" 16 17import socket 18from myserver.log import log, log_reply 19from myserver.http_request import parse_request 20from myserver.file import resolve_location, get_resource 21from myserver.date import now_rfc2616 22from myserver.http import get_http_code, get_http_content_type 23 24_BUF_SIZE = 4096 25_SERVER_ADDR = "0.0.0.0" 26 27def serve(port: int, root: str): 28 """ 29 Serves http request connections for clients. 30 31 This function creates the network socket, listens, and as soon as it receives 32 a request (connection), calls the :func:`~myserver.handle_client()`. 33 """ 34 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 35 36 # Allows reusing a socket right after it got closed (after ctrl-c) 37 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 38 39 # Attach the socket to the port and interface provided. 40 s.bind((_SERVER_ADDR, port)) 41 42 ## Start listening on the socket. 43 s.listen() 44 45 log(f"Server started at {_SERVER_ADDR}:{port}.") 46 47 # Try / catch KeyboardInterrupt to close server socket properly if we hit 48 # the Control-C sequence to interrupt the server. 49 try: 50 while True: 51 c, addr = s.accept() 52 # Try / catch KeyboardInterrupt to properly close the client socket if 53 # we hit the Control-C sequence to interrupt the server. 54 try: 55 handle_client(c, addr, root) 56 # If the KeyboardInterrupt is raised, we pass it to the outer loop 57 # to also close the server socket. 58 except KeyboardInterrupt as e: 59 c.close() 60 raise e 61 except KeyboardInterrupt: 62 log("Received KeyboardInterrupt. Closing...") 63 s.close() 64 65 66def handle_client(c: socket.socket, addr: tuple[str, int], root:str): 67 """ 68 Manages a single connection from a client. 69 70 In details, we: 71 * read data from the socket provided, 72 * parse this data to build the request and headers, 73 * call the handle_request() function, 74 [* optionally write something in the log,] 75 * close the connection. 76 77 Parameters 78 ---------- 79 - c: socket.socket 80 The socket to communicate with the client. 81 - addr: tuple[str, int] 82 The IP address and port of the client, as returned by the accept command. 83 - root: str 84 The path to the local directory to serve. 85 86 """ 87 buf = c.recv(_BUF_SIZE) 88 req = parse_request(buf) 89 90 # Prepare our reply. 91 if req['head']['verb'] == 'GET': 92 reply, code = prepare_resource(root, req) 93 else: 94 # Not implemented: we treat only GET calls for now. 95 reply, code = prepare_reply(b"", "", 501) 96 97 # Send the reply back. 98 c.send(reply) 99 100 # Trace the action in the logs. 101 log_reply(addr, req, code) 102 103 # Close the connection. 104 c.close() 105 106 107def prepare_resource(root: str, req: dict): 108 """ 109 Retrieves the content of the resource and sets the status code. 110 111 Parameters 112 ---------- 113 - root: str 114 The path to the local directory to serve. 115 - req: dict[str, dict] 116 The request to proceed. 117 118 Returns 119 ------- 120 tuple 121 The reply for the request, including the data and status code. 122 - data: str 123 The data (header + content) to reply on the socket. 124 - code: int 125 The status code for the reply. 126 """ 127 code = 200 128 content = b"" 129 content_type = "" 130 131 res_path, res_extension = resolve_location(req['head']['resource'], root) 132 if res_path == "": 133 code = 404 134 else: 135 content_type = get_http_content_type(res_extension) 136 content, code = get_resource(res_path) 137 138 return prepare_reply(content, content_type, code) 139 140 141def prepare_reply(content: bytes, content_type: str, code: int): 142 """ 143 Generates the proper answer, including the HTTP headers and content of the 144 webpage, and the status code. 145 146 For more information about: 147 * Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types 148 * Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 149 150 Parameters 151 ---------- 152 - content: bytes 153 The raw data for the resource. 154 - content_type: str 155 The content type for the resource. 156 - code: int 157 The status code. 158 159 Returns 160 ------- 161 tuple 162 The reply for the request, including the data and status code. 163 - data: str 164 The data (header + content) to reply on the socket. 165 - code: int 166 The status code for the reply. 167 """ 168 # Prepare status code 169 http_code_dict = get_http_code(code) 170 if code != 200: 171 content = http_code_dict['html'].encode() 172 content_type = get_http_content_type('html')+"; charset=utf-8" 173 174 # Prepare header 175 header = f"""HTTP/1.0 {http_code_dict['header']} 176Content-Type: {content_type} 177Date: {now_rfc2616()} 178Content-Length: {len(content)} 179Server: RegardeMamanJeFaisUnServeurWeb/0.1 180 181""".encode() 182 183 return header + content, code
28def serve(port: int, root: str): 29 """ 30 Serves http request connections for clients. 31 32 This function creates the network socket, listens, and as soon as it receives 33 a request (connection), calls the :func:`~myserver.handle_client()`. 34 """ 35 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 36 37 # Allows reusing a socket right after it got closed (after ctrl-c) 38 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 39 40 # Attach the socket to the port and interface provided. 41 s.bind((_SERVER_ADDR, port)) 42 43 ## Start listening on the socket. 44 s.listen() 45 46 log(f"Server started at {_SERVER_ADDR}:{port}.") 47 48 # Try / catch KeyboardInterrupt to close server socket properly if we hit 49 # the Control-C sequence to interrupt the server. 50 try: 51 while True: 52 c, addr = s.accept() 53 # Try / catch KeyboardInterrupt to properly close the client socket if 54 # we hit the Control-C sequence to interrupt the server. 55 try: 56 handle_client(c, addr, root) 57 # If the KeyboardInterrupt is raised, we pass it to the outer loop 58 # to also close the server socket. 59 except KeyboardInterrupt as e: 60 c.close() 61 raise e 62 except KeyboardInterrupt: 63 log("Received KeyboardInterrupt. Closing...") 64 s.close()
Serves http request connections for clients.
This function creates the network socket, listens, and as soon as it receives
a request (connection), calls the ~myserver.handle_client()()
.
67def handle_client(c: socket.socket, addr: tuple[str, int], root:str): 68 """ 69 Manages a single connection from a client. 70 71 In details, we: 72 * read data from the socket provided, 73 * parse this data to build the request and headers, 74 * call the handle_request() function, 75 [* optionally write something in the log,] 76 * close the connection. 77 78 Parameters 79 ---------- 80 - c: socket.socket 81 The socket to communicate with the client. 82 - addr: tuple[str, int] 83 The IP address and port of the client, as returned by the accept command. 84 - root: str 85 The path to the local directory to serve. 86 87 """ 88 buf = c.recv(_BUF_SIZE) 89 req = parse_request(buf) 90 91 # Prepare our reply. 92 if req['head']['verb'] == 'GET': 93 reply, code = prepare_resource(root, req) 94 else: 95 # Not implemented: we treat only GET calls for now. 96 reply, code = prepare_reply(b"", "", 501) 97 98 # Send the reply back. 99 c.send(reply) 100 101 # Trace the action in the logs. 102 log_reply(addr, req, code) 103 104 # Close the connection. 105 c.close()
Manages a single connection from a client.
In details, we:
- read data from the socket provided,
- parse this data to build the request and headers,
- call the handle_request() function, [* optionally write something in the log,]
- close the connection.
Parameters
- c: socket.socket The socket to communicate with the client.
- addr: tuple[str, int] The IP address and port of the client, as returned by the accept command.
- root: str The path to the local directory to serve.
108def prepare_resource(root: str, req: dict): 109 """ 110 Retrieves the content of the resource and sets the status code. 111 112 Parameters 113 ---------- 114 - root: str 115 The path to the local directory to serve. 116 - req: dict[str, dict] 117 The request to proceed. 118 119 Returns 120 ------- 121 tuple 122 The reply for the request, including the data and status code. 123 - data: str 124 The data (header + content) to reply on the socket. 125 - code: int 126 The status code for the reply. 127 """ 128 code = 200 129 content = b"" 130 content_type = "" 131 132 res_path, res_extension = resolve_location(req['head']['resource'], root) 133 if res_path == "": 134 code = 404 135 else: 136 content_type = get_http_content_type(res_extension) 137 content, code = get_resource(res_path) 138 139 return prepare_reply(content, content_type, code)
Retrieves the content of the resource and sets the status code.
Parameters
- root: str The path to the local directory to serve.
- req: dict[str, dict] The request to proceed.
Returns
tuple The reply for the request, including the data and status code. - data: str The data (header + content) to reply on the socket. - code: int The status code for the reply.
142def prepare_reply(content: bytes, content_type: str, code: int): 143 """ 144 Generates the proper answer, including the HTTP headers and content of the 145 webpage, and the status code. 146 147 For more information about: 148 * Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types 149 * Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 150 151 Parameters 152 ---------- 153 - content: bytes 154 The raw data for the resource. 155 - content_type: str 156 The content type for the resource. 157 - code: int 158 The status code. 159 160 Returns 161 ------- 162 tuple 163 The reply for the request, including the data and status code. 164 - data: str 165 The data (header + content) to reply on the socket. 166 - code: int 167 The status code for the reply. 168 """ 169 # Prepare status code 170 http_code_dict = get_http_code(code) 171 if code != 200: 172 content = http_code_dict['html'].encode() 173 content_type = get_http_content_type('html')+"; charset=utf-8" 174 175 # Prepare header 176 header = f"""HTTP/1.0 {http_code_dict['header']} 177Content-Type: {content_type} 178Date: {now_rfc2616()} 179Content-Length: {len(content)} 180Server: RegardeMamanJeFaisUnServeurWeb/0.1 181 182""".encode() 183 184 return header + content, code
Generates the proper answer, including the HTTP headers and content of the webpage, and the status code.
For more information about:
- Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
- Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
Parameters
- content: bytes The raw data for the resource.
- content_type: str The content type for the resource.
- code: int The status code.
Returns
tuple The reply for the request, including the data and status code. - data: str The data (header + content) to reply on the socket. - code: int The status code for the reply.