forked from adafruit/Adafruit_CircuitPython_HTTPServer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequest.py
More file actions
152 lines (117 loc) · 4.12 KB
/
request.py
File metadata and controls
152 lines (117 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`adafruit_httpserver.request.HTTPRequest`
====================================================
* Author(s): Dan Halbert, Michał Pokusa
"""
try:
from typing import Dict, Tuple, Union
from socket import socket
from socketpool import SocketPool
except ImportError:
pass
from .headers import HTTPHeaders
class HTTPRequest:
"""
Incoming request, constructed from raw incoming bytes.
It is passed as first argument to route handlers.
"""
connection: Union["SocketPool.Socket", "socket.socket"]
"""
Socket object usable to send and receive data on the connection.
"""
client_address: Tuple[str, int]
"""
Address and port bound to the socket on the other end of the connection.
Example::
request.client_address
# ('192.168.137.1', 40684)
"""
method: str
"""Request method e.g. "GET" or "POST"."""
path: str
"""Path of the request."""
query_params: Dict[str, str]
"""
Query/GET parameters in the request.
Example::
request = HTTPRequest(raw_request=b"GET /?foo=bar HTTP/1.1...")
request.query_params
# {"foo": "bar"}
"""
http_version: str
"""HTTP version, e.g. "HTTP/1.1"."""
headers: HTTPHeaders
"""
Headers from the request.
"""
raw_request: bytes
"""
Raw 'bytes' passed to the constructor and body 'bytes' received later.
Should **not** be modified directly.
"""
def __init__(
self,
connection: Union["SocketPool.Socket", "socket.socket"],
client_address: Tuple[str, int],
raw_request: bytes = None,
) -> None:
self.connection = connection
self.client_address = client_address
self.raw_request = raw_request
if raw_request is None:
raise ValueError("raw_request cannot be None")
header_bytes = self.header_body_bytes[0]
try:
(
self.method,
self.path,
self.query_params,
self.http_version,
) = self._parse_start_line(header_bytes)
self.headers = self._parse_headers(header_bytes)
except Exception as error:
raise ValueError("Unparseable raw_request: ", raw_request) from error
@property
def body(self) -> bytes:
"""Body of the request, as bytes."""
return self.header_body_bytes[1]
@body.setter
def body(self, body: bytes) -> None:
self.raw_request = self.header_body_bytes[0] + b"\r\n\r\n" + body
@property
def header_body_bytes(self) -> Tuple[bytes, bytes]:
"""Return tuple of header and body bytes."""
empty_line_index = self.raw_request.find(b"\r\n\r\n")
header_bytes = self.raw_request[:empty_line_index]
body_bytes = self.raw_request[empty_line_index + 4 :]
return header_bytes, body_bytes
@staticmethod
def _parse_start_line(header_bytes: bytes) -> Tuple[str, str, Dict[str, str], str]:
"""Parse HTTP Start line to method, path, query_params and http_version."""
start_line = header_bytes.decode("utf8").splitlines()[0]
method, path, http_version = start_line.split()
if "?" not in path:
path += "?"
path, query_string = path.split("?", 1)
query_params = {}
for query_param in query_string.split("&"):
if "=" in query_param:
key, value = query_param.split("=", 1)
query_params[key] = value
elif query_param:
query_params[query_param] = ""
return method, path, query_params, http_version
@staticmethod
def _parse_headers(header_bytes: bytes) -> HTTPHeaders:
"""Parse HTTP headers from raw request."""
header_lines = header_bytes.decode("utf8").splitlines()[1:]
return HTTPHeaders(
{
name: value
for header_line in header_lines
for name, value in [header_line.split(": ", 1)]
}
)