omserv 0.0.0.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. omserv/__about__.py +28 -0
  2. omserv/__init__.py +0 -0
  3. omserv/apps/__init__.py +0 -0
  4. omserv/apps/base.py +23 -0
  5. omserv/apps/inject.py +89 -0
  6. omserv/apps/markers.py +41 -0
  7. omserv/apps/routes.py +139 -0
  8. omserv/apps/sessions.py +57 -0
  9. omserv/apps/templates.py +90 -0
  10. omserv/dbs.py +24 -0
  11. omserv/node/__init__.py +0 -0
  12. omserv/node/models.py +53 -0
  13. omserv/node/registry.py +124 -0
  14. omserv/node/sql.py +131 -0
  15. omserv/secrets.py +12 -0
  16. omserv/server/__init__.py +18 -0
  17. omserv/server/config.py +51 -0
  18. omserv/server/debug.py +14 -0
  19. omserv/server/events.py +83 -0
  20. omserv/server/headers.py +36 -0
  21. omserv/server/lifespans.py +132 -0
  22. omserv/server/multiprocess.py +157 -0
  23. omserv/server/protocols/__init__.py +1 -0
  24. omserv/server/protocols/h11.py +334 -0
  25. omserv/server/protocols/h2.py +407 -0
  26. omserv/server/protocols/protocols.py +91 -0
  27. omserv/server/protocols/types.py +18 -0
  28. omserv/server/resources/__init__.py +8 -0
  29. omserv/server/sockets.py +111 -0
  30. omserv/server/ssl.py +47 -0
  31. omserv/server/streams/__init__.py +0 -0
  32. omserv/server/streams/httpstream.py +237 -0
  33. omserv/server/streams/utils.py +53 -0
  34. omserv/server/streams/wsstream.py +447 -0
  35. omserv/server/taskspawner.py +111 -0
  36. omserv/server/tcpserver.py +173 -0
  37. omserv/server/types.py +94 -0
  38. omserv/server/workercontext.py +52 -0
  39. omserv/server/workers.py +193 -0
  40. omserv-0.0.0.dev7.dist-info/LICENSE +21 -0
  41. omserv-0.0.0.dev7.dist-info/METADATA +21 -0
  42. omserv-0.0.0.dev7.dist-info/RECORD +44 -0
  43. omserv-0.0.0.dev7.dist-info/WHEEL +5 -0
  44. omserv-0.0.0.dev7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,334 @@
1
+ import itertools
2
+ import typing as ta
3
+
4
+ import h11
5
+
6
+ from ..config import Config
7
+ from ..events import Body
8
+ from ..events import Closed
9
+ from ..events import Data
10
+ from ..events import EndBody
11
+ from ..events import EndData
12
+ from ..events import InformationalResponse
13
+ from ..events import ProtocolEvent
14
+ from ..events import RawData
15
+ from ..events import Request
16
+ from ..events import Response
17
+ from ..events import ServerEvent
18
+ from ..events import StreamClosed
19
+ from ..events import Updated
20
+ from ..headers import response_headers
21
+ from ..streams.httpstream import HttpStream
22
+ from ..streams.wsstream import WsStream
23
+ from ..taskspawner import TaskSpawner
24
+ from ..types import AppWrapper
25
+ from ..workercontext import WorkerContext
26
+ from .types import Protocol
27
+
28
+
29
+ H11SendableEvent: ta.TypeAlias = ta.Union[ # noqa
30
+ h11.Data,
31
+ h11.EndOfMessage,
32
+ h11.InformationalResponse,
33
+ h11.Response,
34
+ ]
35
+
36
+
37
+ STREAM_ID = 1
38
+
39
+
40
+ class H2CProtocolRequiredError(Exception):
41
+ def __init__(self, data: bytes, request: h11.Request) -> None:
42
+ super().__init__()
43
+ settings = ''
44
+ headers = [(b':method', request.method), (b':path', request.target)]
45
+ for name, value in request.headers:
46
+ if name.lower() == b'http2-settings':
47
+ settings = value.decode()
48
+ elif name.lower() == b'host':
49
+ headers.append((b':authority', value))
50
+ headers.append((name, value))
51
+
52
+ self.data = data
53
+ self.headers = headers
54
+ self.settings = settings
55
+
56
+
57
+ class H2ProtocolAssumedError(Exception):
58
+ def __init__(self, data: bytes) -> None:
59
+ super().__init__()
60
+ self.data = data
61
+
62
+
63
+ class H11WsConnection:
64
+ # This class matches the h11 interface, and either passes data
65
+ # through without altering it (for Data, EndData) or sends h11
66
+ # events (Response, Body, EndBody).
67
+ our_state = None # Prevents recycling the connection
68
+ they_are_waiting_for_100_continue = False
69
+ their_state = None
70
+ trailing_data = (b'', False)
71
+
72
+ def __init__(self, h11_connection: h11.Connection) -> None:
73
+ self.buffer = bytearray(h11_connection.trailing_data[0])
74
+ self.h11_connection = h11_connection
75
+
76
+ def receive_data(self, data: bytes) -> None:
77
+ self.buffer.extend(data)
78
+
79
+ def next_event(self) -> Data | type[h11.NEED_DATA]:
80
+ if self.buffer:
81
+ event = Data(stream_id=STREAM_ID, data=bytes(self.buffer))
82
+ self.buffer = bytearray()
83
+ return event
84
+ else:
85
+ return h11.NEED_DATA
86
+
87
+ def send(self, event: H11SendableEvent) -> bytes | None:
88
+ return self.h11_connection.send(event)
89
+
90
+ def start_next_cycle(self) -> None:
91
+ pass
92
+
93
+
94
+ class H11Protocol(Protocol):
95
+ def __init__(
96
+ self,
97
+ app: AppWrapper,
98
+ config: Config,
99
+ context: WorkerContext,
100
+ task_spawner: TaskSpawner,
101
+ client: tuple[str, int] | None,
102
+ server: tuple[str, int] | None,
103
+ send: ta.Callable[[ServerEvent], ta.Awaitable[None]],
104
+ ) -> None:
105
+ self.app = app
106
+ self.can_read = context.event_class()
107
+ self.client = client
108
+ self.config = config
109
+ self.connection: h11.Connection | H11WsConnection = h11.Connection(
110
+ h11.SERVER, max_incomplete_event_size=self.config.h11_max_incomplete_size,
111
+ )
112
+ self.context = context
113
+ self.keep_alive_requests = 0
114
+ self.send = send
115
+ self.server = server
116
+ self.stream: HttpStream | WsStream | None = None
117
+ self.task_spawner = task_spawner
118
+
119
+ async def initiate(self) -> None:
120
+ pass
121
+
122
+ async def handle(self, event: ServerEvent) -> None:
123
+ if isinstance(event, RawData):
124
+ self.connection.receive_data(event.data)
125
+ await self._handle_events()
126
+ elif isinstance(event, Closed):
127
+ if self.stream is not None:
128
+ await self._close_stream()
129
+
130
+ async def stream_send(self, event: ProtocolEvent) -> None:
131
+ if isinstance(event, Response):
132
+ if event.status_code >= 200:
133
+ headers = list(itertools.chain(event.headers, response_headers(self.config, 'h11')))
134
+ if self.keep_alive_requests >= self.config.keep_alive_max_requests:
135
+ headers.append((b'connection', b'close'))
136
+ await self._send_h11_event(h11.Response(
137
+ headers=headers,
138
+ status_code=event.status_code,
139
+ ))
140
+ else:
141
+ await self._send_h11_event(h11.InformationalResponse(
142
+ headers=list(itertools.chain(event.headers, response_headers(self.config, 'h11'))),
143
+ status_code=event.status_code,
144
+ ))
145
+
146
+ elif isinstance(event, InformationalResponse):
147
+ pass # Ignore for HTTP/1
148
+
149
+ elif isinstance(event, Body):
150
+ await self._send_h11_event(h11.Data(data=event.data))
151
+
152
+ elif isinstance(event, EndBody):
153
+ await self._send_h11_event(h11.EndOfMessage())
154
+
155
+ elif isinstance(event, Data):
156
+ await self.send(RawData(data=event.data))
157
+
158
+ elif isinstance(event, EndData):
159
+ pass
160
+
161
+ elif isinstance(event, StreamClosed):
162
+ await self._maybe_recycle()
163
+
164
+ async def _handle_events(self) -> None:
165
+ while True:
166
+ if self.connection.they_are_waiting_for_100_continue:
167
+ await self._send_h11_event(h11.InformationalResponse(
168
+ status_code=100, headers=response_headers(self.config, 'h11'),
169
+ ))
170
+
171
+ try:
172
+ event = self.connection.next_event()
173
+ except h11.RemoteProtocolError as error:
174
+ if self.connection.our_state in {h11.IDLE, h11.SEND_RESPONSE}:
175
+ await self._send_error_response(error.error_status_hint)
176
+ await self.send(Closed())
177
+ break
178
+
179
+ else:
180
+ if isinstance(event, h11.Request):
181
+ await self.send(Updated(idle=False))
182
+ await self._check_protocol(event)
183
+ await self._create_stream(event)
184
+
185
+ elif event is h11.PAUSED:
186
+ await self.can_read.clear()
187
+ await self.can_read.wait()
188
+
189
+ elif isinstance(event, h11.ConnectionClosed) or event is h11.NEED_DATA:
190
+ break
191
+
192
+ elif self.stream is None:
193
+ break
194
+
195
+ elif isinstance(event, h11.Data):
196
+ await self.stream.handle(Body(stream_id=STREAM_ID, data=event.data))
197
+
198
+ elif isinstance(event, h11.EndOfMessage):
199
+ await self.stream.handle(EndBody(stream_id=STREAM_ID))
200
+
201
+ elif isinstance(event, Data):
202
+ # WebSocket pass through
203
+ await self.stream.handle(event)
204
+
205
+ async def _create_stream(self, request: h11.Request) -> None:
206
+ upgrade_value = ''
207
+ connection_value = ''
208
+ for name, value in request.headers:
209
+ sanitised_name = name.decode('latin1').strip().lower()
210
+ if sanitised_name == 'upgrade':
211
+ upgrade_value = value.decode('latin1').strip()
212
+ elif sanitised_name == 'connection':
213
+ connection_value = value.decode('latin1').strip()
214
+
215
+ connection_tokens = connection_value.lower().split(',')
216
+ if (
217
+ any(token.strip() == 'upgrade' for token in connection_tokens)
218
+ and upgrade_value.lower() == 'websocket'
219
+ and request.method.decode('ascii').upper() == 'GET'
220
+ ):
221
+ self.stream = WsStream(
222
+ self.app,
223
+ self.config,
224
+ self.context,
225
+ self.task_spawner,
226
+ self.client,
227
+ self.server,
228
+ self.stream_send,
229
+ STREAM_ID,
230
+ )
231
+ self.connection = H11WsConnection(ta.cast(h11.Connection, self.connection))
232
+ else:
233
+ self.stream = HttpStream(
234
+ self.app,
235
+ self.config,
236
+ self.context,
237
+ self.task_spawner,
238
+ self.client,
239
+ self.server,
240
+ self.stream_send,
241
+ STREAM_ID,
242
+ )
243
+
244
+ if self.config.h11_pass_raw_headers:
245
+ headers = request.headers.raw_items()
246
+ else:
247
+ headers = list(request.headers)
248
+
249
+ await self.stream.handle( # type: ignore
250
+ Request(
251
+ stream_id=STREAM_ID,
252
+ headers=headers,
253
+ http_version=request.http_version.decode(),
254
+ method=request.method.decode('ascii').upper(),
255
+ raw_path=request.target,
256
+ ),
257
+ )
258
+ self.keep_alive_requests += 1
259
+ await self.context.mark_request()
260
+
261
+ async def _send_h11_event(self, event: H11SendableEvent) -> None:
262
+ try:
263
+ data = self.connection.send(event)
264
+ except h11.LocalProtocolError:
265
+ if self.connection.their_state != h11.ERROR:
266
+ raise
267
+ else:
268
+ if data is None:
269
+ raise Exception('closed') # FIXME
270
+ await self.send(RawData(data=data))
271
+
272
+ async def _send_error_response(self, status_code: int) -> None:
273
+ await self._send_h11_event(h11.Response(
274
+ status_code=status_code,
275
+ headers=list(
276
+ itertools.chain(
277
+ [(b'content-length', b'0'), (b'connection', b'close')],
278
+ response_headers(self.config, 'h11'),
279
+ ),
280
+ ),
281
+ ))
282
+ await self._send_h11_event(h11.EndOfMessage())
283
+
284
+ async def _maybe_recycle(self) -> None:
285
+ await self._close_stream()
286
+ if (
287
+ not self.context.terminated.is_set()
288
+ and self.connection.our_state is h11.DONE
289
+ and self.connection.their_state is h11.DONE
290
+ ):
291
+ try:
292
+ self.connection.start_next_cycle()
293
+ except h11.LocalProtocolError:
294
+ await self.send(Closed())
295
+ else:
296
+ self.response = None
297
+ self.scope = None
298
+ await self.can_read.set()
299
+ await self.send(Updated(idle=True))
300
+ else:
301
+ await self.can_read.set()
302
+ await self.send(Closed())
303
+
304
+ async def _close_stream(self) -> None:
305
+ if self.stream is not None:
306
+ await self.stream.handle(StreamClosed(stream_id=STREAM_ID))
307
+ self.stream = None
308
+
309
+ async def _check_protocol(self, event: h11.Request) -> None:
310
+ upgrade_value = ''
311
+ has_body = False
312
+ for name, value in event.headers:
313
+ sanitised_name = name.decode('latin1').strip().lower()
314
+ if sanitised_name == 'upgrade':
315
+ upgrade_value = value.decode('latin1').strip()
316
+ elif sanitised_name in {'content-length', 'transfer-encoding'}:
317
+ has_body = True
318
+
319
+ # h2c Upgrade requests with a body are a pain as the body must be fully recieved in HTTP/1.1 before the upgrade
320
+ # response and HTTP/2 takes over, so Hypercorn ignores the upgrade and responds in HTTP/1.1. Use a preflight
321
+ # OPTIONS request to initiate the upgrade if really required (or just use h2).
322
+ if upgrade_value.lower() == 'h2c' and not has_body:
323
+ await self._send_h11_event(h11.InformationalResponse(
324
+ status_code=101,
325
+ headers=[
326
+ *response_headers(self.config, 'h11'),
327
+ (b'connection', b'upgrade'),
328
+ (b'upgrade', b'h2c'),
329
+ ],
330
+ ))
331
+ raise H2CProtocolRequiredError(self.connection.trailing_data[0], event)
332
+
333
+ elif event.method == b'PRI' and event.target == b'*' and event.http_version == b'2.0':
334
+ raise H2ProtocolAssumedError(b'PRI * HTTP/2.0\r\n\r\n' + self.connection.trailing_data[0])