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 prepare_resource() or prepare_reply() function accordingly, 74 * send the reply back. 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 89 buf = c.recv(_BUF_SIZE) 90 req = parse_request(buf) 91 92 # Prepare our reply. 93 if req['head']['verb'] == 'GET': 94 reply, code = prepare_resource(root, req) 95 else: 96 # Not implemented: we treat only GET calls for now. 97 reply, code = prepare_reply(b"", "", 501) 98 99 # Send the reply back. 100 c.send(reply) 101 102 # Trace the action in the logs. 103 log_reply(addr, req, code) 104 105 # Close the connection. 106 c.close() 107 108 109def prepare_resource(root: str, req: dict): 110 """ 111 Retrieves the content of the resource and sets the status code. 112 113 Parameters 114 ---------- 115 - root: str 116 The path to the local directory to serve. 117 - req: dict[str, dict] 118 The request to proceed. 119 120 Returns 121 ------- 122 tuple 123 The reply for the request, including the data and status code. 124 - data: str 125 The data (header + content) to reply on the socket. 126 - code: int 127 The status code for the reply. 128 """ 129 code = 200 130 content = b"" 131 content_type = "" 132 133 res_path, res_extension = resolve_location(req['head']['resource'], root) 134 if res_path == "": 135 code = 404 136 else: 137 content_type = get_http_content_type(res_extension) 138 content, code = get_resource(res_path) 139 140 return prepare_reply(content, content_type, code) 141 142 143def prepare_reply(content: bytes, content_type: str, code: int): 144 """ 145 Generates the proper answer, including the HTTP headers and content of the 146 webpage, and the status code. 147 148 Headers will look like that: 149 ``` 150 HTTP/1.0 200 OK 151 Content-Type: text/html 152 Date: Thu, 07 Mar 2024 08:29:45 GMT 153 Content-Length: 152 154 Server: RegardeMamanJeFaisUnServeurWeb/0.1 155 ``` 156 157 For more information about: 158 * Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types 159 * Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 160 161 Parameters 162 ---------- 163 - content: bytes 164 The raw data for the resource. 165 - content_type: str 166 The content type for the resource. 167 - code: int 168 The status code. 169 170 Returns 171 ------- 172 tuple 173 The reply for the request, including the data and status code. 174 - data: str 175 The data (header + content) to reply on the socket. 176 - code: int 177 The status code for the reply. 178 """ 179 # Prepare status code 180 http_code_dict = get_http_code(code) 181 if code != 200: 182 content = http_code_dict['html'].encode() 183 content_type = get_http_content_type('html')+"; charset=utf-8" 184 185 # Prepare headers, including content-type, date, content-length, server. 186 header = f"""HTTP/1.0 {http_code_dict['header']} 187Content-Type: {content_type} 188Date: {now_rfc2616()} 189Content-Length: {len(content)} 190Server: RegardeMamanJeFaisUnServeurWeb/0.1 191 192""".encode() 193 194 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 prepare_resource() or prepare_reply() function accordingly, 75 * send the reply back. 76 * optionally write something in the log, 77 * close the connection. 78 79 Parameters 80 ---------- 81 - c: socket.socket 82 The socket to communicate with the client. 83 - addr: tuple[str, int] 84 The IP address and port of the client, as returned by the accept command. 85 - root: str 86 The path to the local directory to serve. 87 88 """ 89 90 buf = c.recv(_BUF_SIZE) 91 req = parse_request(buf) 92 93 # Prepare our reply. 94 if req['head']['verb'] == 'GET': 95 reply, code = prepare_resource(root, req) 96 else: 97 # Not implemented: we treat only GET calls for now. 98 reply, code = prepare_reply(b"", "", 501) 99 100 # Send the reply back. 101 c.send(reply) 102 103 # Trace the action in the logs. 104 log_reply(addr, req, code) 105 106 # Close the connection. 107 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 prepare_resource() or prepare_reply() function accordingly,
- send the reply back.
- 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.
110def prepare_resource(root: str, req: dict): 111 """ 112 Retrieves the content of the resource and sets the status code. 113 114 Parameters 115 ---------- 116 - root: str 117 The path to the local directory to serve. 118 - req: dict[str, dict] 119 The request to proceed. 120 121 Returns 122 ------- 123 tuple 124 The reply for the request, including the data and status code. 125 - data: str 126 The data (header + content) to reply on the socket. 127 - code: int 128 The status code for the reply. 129 """ 130 code = 200 131 content = b"" 132 content_type = "" 133 134 res_path, res_extension = resolve_location(req['head']['resource'], root) 135 if res_path == "": 136 code = 404 137 else: 138 content_type = get_http_content_type(res_extension) 139 content, code = get_resource(res_path) 140 141 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.
144def prepare_reply(content: bytes, content_type: str, code: int): 145 """ 146 Generates the proper answer, including the HTTP headers and content of the 147 webpage, and the status code. 148 149 Headers will look like that: 150 ``` 151 HTTP/1.0 200 OK 152 Content-Type: text/html 153 Date: Thu, 07 Mar 2024 08:29:45 GMT 154 Content-Length: 152 155 Server: RegardeMamanJeFaisUnServeurWeb/0.1 156 ``` 157 158 For more information about: 159 * Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types 160 * Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 161 162 Parameters 163 ---------- 164 - content: bytes 165 The raw data for the resource. 166 - content_type: str 167 The content type for the resource. 168 - code: int 169 The status code. 170 171 Returns 172 ------- 173 tuple 174 The reply for the request, including the data and status code. 175 - data: str 176 The data (header + content) to reply on the socket. 177 - code: int 178 The status code for the reply. 179 """ 180 # Prepare status code 181 http_code_dict = get_http_code(code) 182 if code != 200: 183 content = http_code_dict['html'].encode() 184 content_type = get_http_content_type('html')+"; charset=utf-8" 185 186 # Prepare headers, including content-type, date, content-length, server. 187 header = f"""HTTP/1.0 {http_code_dict['header']} 188Content-Type: {content_type} 189Date: {now_rfc2616()} 190Content-Length: {len(content)} 191Server: RegardeMamanJeFaisUnServeurWeb/0.1 192 193""".encode() 194 195 return header + content, code
Generates the proper answer, including the HTTP headers and content of the webpage, and the status code.
Headers will look like that:
HTTP/1.0 200 OK
Content-Type: text/html
Date: Thu, 07 Mar 2024 08:29:45 GMT
Content-Length: 152
Server: RegardeMamanJeFaisUnServeurWeb/0.1
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.