tigrcorn-protocols 0.3.16.dev5__tar.gz → 0.3.16.dev12__tar.gz
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.
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/PKG-INFO +76 -19
- tigrcorn_protocols-0.3.16.dev12/README.md +101 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/pyproject.toml +9 -7
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/handler.py +217 -21
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/state.py +2 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/handler/core.py +136 -1
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/handler/webtransport.py +104 -17
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols.egg-info/PKG-INFO +76 -19
- tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols.egg-info/requires.txt +5 -0
- tigrcorn_protocols-0.3.16.dev5/README.md +0 -46
- tigrcorn_protocols-0.3.16.dev5/src/tigrcorn_protocols.egg-info/requires.txt +0 -5
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/LICENSE +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/setup.cfg +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/_compression.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/connect.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/content_coding.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/custom/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/custom/adapters.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/custom/registry.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/backpressure.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/buffers.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/credits.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/keepalive.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/timeouts.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/flow/watermarks.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http1/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http1/keepalive.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http1/parser.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http1/serializer.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http1/state.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/codec.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/flow.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/hpack.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/streams.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http2/websocket.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/codec.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/handler/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/handler.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/qpack.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/state.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/streams.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/http3/websocket.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/lifespan/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/lifespan/driver.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/py.typed +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/rawframed/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/rawframed/codec.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/rawframed/frames.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/rawframed/handler.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/rawframed/state.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/registry.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/cancellation.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/dispatch.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/fairness.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/policy.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/priorities.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/quotas.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/runtime.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/scheduler/tasks.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/base.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/connection.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/limits.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/manager.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/metadata.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/sessions/quic.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/streams/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/streams/base.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/streams/ids.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/streams/multiplex.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/streams/registry.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/streams/singleplex.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/__init__.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/codec.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/extensions.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/frames.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/handler.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/handshake.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols/websocket/state.py +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols.egg-info/SOURCES.txt +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols.egg-info/dependency_links.txt +0 -0
- {tigrcorn_protocols-0.3.16.dev5 → tigrcorn_protocols-0.3.16.dev12}/src/tigrcorn_protocols.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrcorn-protocols
|
|
3
|
-
Version: 0.3.16.
|
|
3
|
+
Version: 0.3.16.dev12
|
|
4
4
|
Summary: Protocol handlers for Tigrcorn HTTP/1.1, HTTP/2, HTTP/3, QUIC, WebSocket, WebTransport, lifespan, and ASGI3 traffic.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License: Apache License
|
|
@@ -175,66 +175,123 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
175
175
|
Classifier: Operating System :: OS Independent
|
|
176
176
|
Classifier: Programming Language :: Python :: 3
|
|
177
177
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
178
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
178
179
|
Classifier: Programming Language :: Python :: 3.11
|
|
179
180
|
Classifier: Programming Language :: Python :: 3.12
|
|
180
181
|
Classifier: Programming Language :: Python :: 3.13
|
|
182
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
181
183
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
182
184
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
183
185
|
Classifier: Topic :: System :: Networking
|
|
184
186
|
Classifier: Typing :: Typed
|
|
185
|
-
Requires-Python:
|
|
187
|
+
Requires-Python: <3.15,>=3.10
|
|
186
188
|
Description-Content-Type: text/markdown
|
|
187
189
|
License-File: LICENSE
|
|
188
|
-
Requires-Dist: tigrcorn-core==0.3.16.
|
|
189
|
-
Requires-Dist: tigrcorn-config==0.3.16.
|
|
190
|
-
Requires-Dist: tigrcorn-asgi==0.3.16.
|
|
191
|
-
Requires-Dist: tigrcorn-http==0.3.16.
|
|
192
|
-
Requires-Dist: tigrcorn-transports==0.3.16.
|
|
190
|
+
Requires-Dist: tigrcorn-core==0.3.16.dev12
|
|
191
|
+
Requires-Dist: tigrcorn-config==0.3.16.dev12
|
|
192
|
+
Requires-Dist: tigrcorn-asgi==0.3.16.dev12
|
|
193
|
+
Requires-Dist: tigrcorn-http==0.3.16.dev12
|
|
194
|
+
Requires-Dist: tigrcorn-transports==0.3.16.dev12
|
|
193
195
|
Dynamic: license-file
|
|
194
196
|
|
|
195
197
|
<div align="center">
|
|
196
198
|
<h1>tigrcorn-protocols</h1>
|
|
199
|
+
<img
|
|
200
|
+
src="https://raw.githubusercontent.com/Tigrbl/tigrcorn/master/assets/tigrcorn_logo.png"
|
|
201
|
+
alt="Tigrcorn tiger-unicorn logo"
|
|
202
|
+
width="140"
|
|
203
|
+
/>
|
|
197
204
|
|
|
198
205
|
<p><strong>Protocol handlers for Tigrcorn HTTP/1.1, HTTP/2, HTTP/3, QUIC, WebSocket, WebTransport, lifespan, and ASGI3 traffic.</strong></p>
|
|
199
206
|
|
|
200
207
|
<a href="https://pypi.org/project/tigrcorn-protocols/"><img alt="PyPI version for tigrcorn-protocols" src="https://img.shields.io/pypi/v/tigrcorn-protocols?label=PyPI"></a>
|
|
201
208
|
<a href="https://pypi.org/project/tigrcorn-protocols/"><img alt="tigrcorn-protocols package on PyPI" src="https://img.shields.io/badge/package-PyPI-blue"></a>
|
|
209
|
+
<a href="https://pepy.tech/project/tigrcorn-protocols"><img alt="Downloads for tigrcorn-protocols" src="https://static.pepy.tech/badge/tigrcorn-protocols"></a>
|
|
210
|
+
<a href="https://github.com/tigrbl/tigrcorn/blob/master/pkgs/tigrcorn-protocols/README.md"><img alt="Hits for tigrcorn-protocols README" src="https://hits.sh/github.com/tigrbl/tigrcorn/blob/master/pkgs/tigrcorn-protocols/README.md.svg?label=hits"></a>
|
|
202
211
|
<a href="LICENSE"><img alt="Apache 2.0 license" src="https://img.shields.io/badge/license-Apache%202.0-525252"></a>
|
|
203
|
-
<a href="pyproject.toml"><img alt="Python 3.11 supported" src="https://img.shields.io/badge/python-3.11-3776ab"></a>
|
|
204
|
-
<a href="pyproject.toml"><img alt="Python 3.12 supported" src="https://img.shields.io/badge/python-3.12-3776ab"></a>
|
|
205
|
-
<a href="pyproject.toml"><img alt="Python 3.13 supported" src="https://img.shields.io/badge/python-3.13-3776ab"></a>
|
|
206
|
-
<a href="src/tigrcorn_protocols/py.typed"><img alt="typed package" src="https://img.shields.io/badge/typed-py.typed-2f7ed8"></a>
|
|
212
|
+
<a href="pyproject.toml"><img alt="Python 3.10 | 3.11 | 3.12 | 3.13 | 3.14 supported" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab"></a>
|
|
207
213
|
<a href="https://pypi.org/project/tigrcorn-protocols/"><img alt="protocols role package" src="https://img.shields.io/badge/role-protocols-0a7f5a"></a>
|
|
208
214
|
</div>
|
|
209
215
|
|
|
216
|
+
<p align="center"><a href="https://github.com/Tigrbl/tigrcorn/blob/master/.ssot/registry.json"><img alt="SSOT governed" src="https://img.shields.io/badge/SSOT-governed-2f6f4e.svg"></a> <a href="https://discord.gg/jzvrbEtTtt"><img alt="Discord" src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white"></a></p>
|
|
217
|
+
|
|
210
218
|
## Install
|
|
211
219
|
|
|
212
|
-
|
|
220
|
+
```bash
|
|
221
|
+
uv add tigrcorn-protocols
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```bash
|
|
213
225
|
pip install tigrcorn-protocols
|
|
214
|
-
|
|
226
|
+
```
|
|
215
227
|
|
|
216
228
|
Use the aggregate [tigrcorn](https://pypi.org/project/tigrcorn/) distribution when you want the full ASGI3 Python web server stack. Install <code>tigrcorn-protocols</code> directly when you want only this package boundary and its declared dependencies.
|
|
217
229
|
|
|
218
230
|
## What It Owns
|
|
219
231
|
|
|
220
|
-
<code>tigrcorn-protocols</code> owns
|
|
232
|
+
<code>tigrcorn-protocols</code> owns http1, http2, http3, websocket, lifespan, rawframed, custom protocols, flow control, scheduler primitives, sessions, and streams. Its import package is <code>tigrcorn_protocols</code>, and its declared package dependencies are: tigrcorn-core, tigrcorn-config, tigrcorn-asgi, tigrcorn-http, tigrcorn-transports.
|
|
233
|
+
|
|
234
|
+
This package page is written for developers searching for Tigrcorn ASGI3 server components, Python web server packages, HTTP/3 and QUIC support, WebSocket and WebTransport-adjacent surfaces, and Apache 2.0 licensed infrastructure.
|
|
235
|
+
|
|
236
|
+
## Why Use This?
|
|
237
|
+
|
|
238
|
+
Use <code>tigrcorn-protocols</code> when you want the protocols layer as a direct install target instead of the full server bundle. It lets application, operator, or certification workflows depend on this boundary explicitly while keeping the broader Tigrcorn runtime assembled from smaller repo-owned package surfaces.
|
|
239
|
+
|
|
240
|
+
## FAQ
|
|
241
|
+
|
|
242
|
+
### What does this package export?
|
|
243
|
+
|
|
244
|
+
The package exports through the <code>tigrcorn_protocols</code> namespace and keeps the root <code>tigrcorn</code> package as the compatibility umbrella.
|
|
245
|
+
|
|
246
|
+
### Which boundary does this package own?
|
|
247
|
+
|
|
248
|
+
It is the package boundary for http1, http2, http3, websocket, lifespan, rawframed, custom protocols, flow control, scheduler primitives, sessions, and streams in the Tigrcorn package graph.
|
|
221
249
|
|
|
222
|
-
|
|
250
|
+
### What protocol families are implemented here?
|
|
251
|
+
|
|
252
|
+
This package is the main protocol plane for HTTP/1.1, HTTP/2, HTTP/3, WebSocket, WebTransport-adjacent protocol logic, lifespan, rawframed, scheduler primitives, sessions, and streams.
|
|
253
|
+
|
|
254
|
+
## Features
|
|
255
|
+
|
|
256
|
+
- Owns http1, http2, http3, websocket, lifespan, rawframed, custom protocols, flow control, scheduler primitives, sessions, and streams inside the Tigrcorn split-package architecture.
|
|
257
|
+
- Publishes the <code>tigrcorn_protocols</code> import surface for module-oriented public surfaces.
|
|
258
|
+
- Declared runtime dependencies: tigrcorn-core, tigrcorn-config, tigrcorn-asgi, tigrcorn-http, tigrcorn-transports.
|
|
259
|
+
- Optional dependency surface: none.
|
|
260
|
+
- Supports Python 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
223
261
|
|
|
224
262
|
## Use It When
|
|
225
263
|
|
|
226
|
-
Use <code>tigrcorn-protocols</code> when you need
|
|
264
|
+
Use <code>tigrcorn-protocols</code> when you need protocols-level behavior without pulling the entire server stack into the import surface. It is part of Tigrcorn's split-package architecture, so it can be installed independently while remaining linked to the rest of the Tigrcorn package family on PyPI.
|
|
227
265
|
|
|
228
266
|
## Import Surface
|
|
229
267
|
|
|
230
|
-
|
|
268
|
+
```python
|
|
231
269
|
import tigrcorn_protocols
|
|
232
270
|
|
|
233
271
|
print(tigrcorn_protocols.__name__)
|
|
234
|
-
|
|
272
|
+
```
|
|
235
273
|
|
|
236
274
|
The package exposes its supported public surface through the <code>tigrcorn_protocols</code> namespace. The root [tigrcorn](https://pypi.org/project/tigrcorn/) package keeps compatibility shims for users who install the full server distribution.
|
|
237
275
|
|
|
276
|
+
## Related Packages
|
|
277
|
+
|
|
278
|
+
- [tigrcorn-core](https://pypi.org/project/tigrcorn-core/)
|
|
279
|
+
- [tigrcorn-config](https://pypi.org/project/tigrcorn-config/)
|
|
280
|
+
- [tigrcorn-asgi](https://pypi.org/project/tigrcorn-asgi/)
|
|
281
|
+
- [tigrcorn-http](https://pypi.org/project/tigrcorn-http/)
|
|
282
|
+
- [tigrcorn-transports](https://pypi.org/project/tigrcorn-transports/)
|
|
283
|
+
- [tigrcorn](https://pypi.org/project/tigrcorn/)
|
|
284
|
+
|
|
238
285
|
## Package Graph
|
|
239
286
|
|
|
240
|
-
[tigrcorn](https://pypi.org/project/tigrcorn/) | [tigrcorn-
|
|
287
|
+
[tigrcorn-core](https://pypi.org/project/tigrcorn-core/) | [tigrcorn-config](https://pypi.org/project/tigrcorn-config/) | [tigrcorn-http](https://pypi.org/project/tigrcorn-http/) | [tigrcorn-asgi](https://pypi.org/project/tigrcorn-asgi/) | [tigrcorn-contract](https://pypi.org/project/tigrcorn-contract/) | [tigrcorn-transports](https://pypi.org/project/tigrcorn-transports/) | [tigrcorn-security](https://pypi.org/project/tigrcorn-security/) | [tigrcorn-protocols](https://pypi.org/project/tigrcorn-protocols/) | [tigrcorn-static](https://pypi.org/project/tigrcorn-static/) | [tigrcorn-observability](https://pypi.org/project/tigrcorn-observability/) | [tigrcorn-runtime](https://pypi.org/project/tigrcorn-runtime/) | [tigrcorn-compat](https://pypi.org/project/tigrcorn-compat/) | [tigrcorn-certification](https://pypi.org/project/tigrcorn-certification/)
|
|
288
|
+
|
|
289
|
+
## Best Practices
|
|
290
|
+
|
|
291
|
+
- Use this package for protocol-state machines and stream/session behavior, not for transport bootstrapping.
|
|
292
|
+
- Keep HTTP, WebSocket, and HTTP/3 behavior aligned with the certified protocol surface.
|
|
293
|
+
- Validate new protocol work against shared ASGI and HTTP helper layers before widening runtime claims.
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
Apache-2.0
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>tigrcorn-protocols</h1>
|
|
3
|
+
<img
|
|
4
|
+
src="https://raw.githubusercontent.com/Tigrbl/tigrcorn/master/assets/tigrcorn_logo.png"
|
|
5
|
+
alt="Tigrcorn tiger-unicorn logo"
|
|
6
|
+
width="140"
|
|
7
|
+
/>
|
|
8
|
+
|
|
9
|
+
<p><strong>Protocol handlers for Tigrcorn HTTP/1.1, HTTP/2, HTTP/3, QUIC, WebSocket, WebTransport, lifespan, and ASGI3 traffic.</strong></p>
|
|
10
|
+
|
|
11
|
+
<a href="https://pypi.org/project/tigrcorn-protocols/"><img alt="PyPI version for tigrcorn-protocols" src="https://img.shields.io/pypi/v/tigrcorn-protocols?label=PyPI"></a>
|
|
12
|
+
<a href="https://pypi.org/project/tigrcorn-protocols/"><img alt="tigrcorn-protocols package on PyPI" src="https://img.shields.io/badge/package-PyPI-blue"></a>
|
|
13
|
+
<a href="https://pepy.tech/project/tigrcorn-protocols"><img alt="Downloads for tigrcorn-protocols" src="https://static.pepy.tech/badge/tigrcorn-protocols"></a>
|
|
14
|
+
<a href="https://github.com/tigrbl/tigrcorn/blob/master/pkgs/tigrcorn-protocols/README.md"><img alt="Hits for tigrcorn-protocols README" src="https://hits.sh/github.com/tigrbl/tigrcorn/blob/master/pkgs/tigrcorn-protocols/README.md.svg?label=hits"></a>
|
|
15
|
+
<a href="LICENSE"><img alt="Apache 2.0 license" src="https://img.shields.io/badge/license-Apache%202.0-525252"></a>
|
|
16
|
+
<a href="pyproject.toml"><img alt="Python 3.10 | 3.11 | 3.12 | 3.13 | 3.14 supported" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab"></a>
|
|
17
|
+
<a href="https://pypi.org/project/tigrcorn-protocols/"><img alt="protocols role package" src="https://img.shields.io/badge/role-protocols-0a7f5a"></a>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<p align="center"><a href="https://github.com/Tigrbl/tigrcorn/blob/master/.ssot/registry.json"><img alt="SSOT governed" src="https://img.shields.io/badge/SSOT-governed-2f6f4e.svg"></a> <a href="https://discord.gg/jzvrbEtTtt"><img alt="Discord" src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white"></a></p>
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv add tigrcorn-protocols
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install tigrcorn-protocols
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use the aggregate [tigrcorn](https://pypi.org/project/tigrcorn/) distribution when you want the full ASGI3 Python web server stack. Install <code>tigrcorn-protocols</code> directly when you want only this package boundary and its declared dependencies.
|
|
33
|
+
|
|
34
|
+
## What It Owns
|
|
35
|
+
|
|
36
|
+
<code>tigrcorn-protocols</code> owns http1, http2, http3, websocket, lifespan, rawframed, custom protocols, flow control, scheduler primitives, sessions, and streams. Its import package is <code>tigrcorn_protocols</code>, and its declared package dependencies are: tigrcorn-core, tigrcorn-config, tigrcorn-asgi, tigrcorn-http, tigrcorn-transports.
|
|
37
|
+
|
|
38
|
+
This package page is written for developers searching for Tigrcorn ASGI3 server components, Python web server packages, HTTP/3 and QUIC support, WebSocket and WebTransport-adjacent surfaces, and Apache 2.0 licensed infrastructure.
|
|
39
|
+
|
|
40
|
+
## Why Use This?
|
|
41
|
+
|
|
42
|
+
Use <code>tigrcorn-protocols</code> when you want the protocols layer as a direct install target instead of the full server bundle. It lets application, operator, or certification workflows depend on this boundary explicitly while keeping the broader Tigrcorn runtime assembled from smaller repo-owned package surfaces.
|
|
43
|
+
|
|
44
|
+
## FAQ
|
|
45
|
+
|
|
46
|
+
### What does this package export?
|
|
47
|
+
|
|
48
|
+
The package exports through the <code>tigrcorn_protocols</code> namespace and keeps the root <code>tigrcorn</code> package as the compatibility umbrella.
|
|
49
|
+
|
|
50
|
+
### Which boundary does this package own?
|
|
51
|
+
|
|
52
|
+
It is the package boundary for http1, http2, http3, websocket, lifespan, rawframed, custom protocols, flow control, scheduler primitives, sessions, and streams in the Tigrcorn package graph.
|
|
53
|
+
|
|
54
|
+
### What protocol families are implemented here?
|
|
55
|
+
|
|
56
|
+
This package is the main protocol plane for HTTP/1.1, HTTP/2, HTTP/3, WebSocket, WebTransport-adjacent protocol logic, lifespan, rawframed, scheduler primitives, sessions, and streams.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- Owns http1, http2, http3, websocket, lifespan, rawframed, custom protocols, flow control, scheduler primitives, sessions, and streams inside the Tigrcorn split-package architecture.
|
|
61
|
+
- Publishes the <code>tigrcorn_protocols</code> import surface for module-oriented public surfaces.
|
|
62
|
+
- Declared runtime dependencies: tigrcorn-core, tigrcorn-config, tigrcorn-asgi, tigrcorn-http, tigrcorn-transports.
|
|
63
|
+
- Optional dependency surface: none.
|
|
64
|
+
- Supports Python 3.10, 3.11, 3.12, 3.13, and 3.14.
|
|
65
|
+
|
|
66
|
+
## Use It When
|
|
67
|
+
|
|
68
|
+
Use <code>tigrcorn-protocols</code> when you need protocols-level behavior without pulling the entire server stack into the import surface. It is part of Tigrcorn's split-package architecture, so it can be installed independently while remaining linked to the rest of the Tigrcorn package family on PyPI.
|
|
69
|
+
|
|
70
|
+
## Import Surface
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import tigrcorn_protocols
|
|
74
|
+
|
|
75
|
+
print(tigrcorn_protocols.__name__)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The package exposes its supported public surface through the <code>tigrcorn_protocols</code> namespace. The root [tigrcorn](https://pypi.org/project/tigrcorn/) package keeps compatibility shims for users who install the full server distribution.
|
|
79
|
+
|
|
80
|
+
## Related Packages
|
|
81
|
+
|
|
82
|
+
- [tigrcorn-core](https://pypi.org/project/tigrcorn-core/)
|
|
83
|
+
- [tigrcorn-config](https://pypi.org/project/tigrcorn-config/)
|
|
84
|
+
- [tigrcorn-asgi](https://pypi.org/project/tigrcorn-asgi/)
|
|
85
|
+
- [tigrcorn-http](https://pypi.org/project/tigrcorn-http/)
|
|
86
|
+
- [tigrcorn-transports](https://pypi.org/project/tigrcorn-transports/)
|
|
87
|
+
- [tigrcorn](https://pypi.org/project/tigrcorn/)
|
|
88
|
+
|
|
89
|
+
## Package Graph
|
|
90
|
+
|
|
91
|
+
[tigrcorn-core](https://pypi.org/project/tigrcorn-core/) | [tigrcorn-config](https://pypi.org/project/tigrcorn-config/) | [tigrcorn-http](https://pypi.org/project/tigrcorn-http/) | [tigrcorn-asgi](https://pypi.org/project/tigrcorn-asgi/) | [tigrcorn-contract](https://pypi.org/project/tigrcorn-contract/) | [tigrcorn-transports](https://pypi.org/project/tigrcorn-transports/) | [tigrcorn-security](https://pypi.org/project/tigrcorn-security/) | [tigrcorn-protocols](https://pypi.org/project/tigrcorn-protocols/) | [tigrcorn-static](https://pypi.org/project/tigrcorn-static/) | [tigrcorn-observability](https://pypi.org/project/tigrcorn-observability/) | [tigrcorn-runtime](https://pypi.org/project/tigrcorn-runtime/) | [tigrcorn-compat](https://pypi.org/project/tigrcorn-compat/) | [tigrcorn-certification](https://pypi.org/project/tigrcorn-certification/)
|
|
92
|
+
|
|
93
|
+
## Best Practices
|
|
94
|
+
|
|
95
|
+
- Use this package for protocol-state machines and stream/session behavior, not for transport bootstrapping.
|
|
96
|
+
- Keep HTTP, WebSocket, and HTTP/3 behavior aligned with the certified protocol surface.
|
|
97
|
+
- Validate new protocol work against shared ASGI and HTTP helper layers before widening runtime claims.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
Apache-2.0
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tigrcorn-protocols"
|
|
7
|
-
version = "0.3.16.
|
|
7
|
+
version = "0.3.16.dev12"
|
|
8
8
|
description = "Protocol handlers for Tigrcorn HTTP/1.1, HTTP/2, HTTP/3, QUIC, WebSocket, WebTransport, lifespan, and ASGI3 traffic."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10,<3.15"
|
|
11
11
|
license = {file = "LICENSE"}
|
|
12
12
|
authors = [{name = "Jacob Stewart", email = "jacob@swarmauri.com"}]
|
|
13
13
|
keywords = ["tigrcorn", "http1", "http2", "http3", "websocket", "webtransport", "quic", "asgi3", "protocol-handlers"]
|
|
@@ -19,20 +19,22 @@ classifiers = [
|
|
|
19
19
|
"Operating System :: OS Independent",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
21
|
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
22
23
|
"Programming Language :: Python :: 3.11",
|
|
23
24
|
"Programming Language :: Python :: 3.12",
|
|
24
25
|
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
25
27
|
"Topic :: Internet :: WWW/HTTP",
|
|
26
28
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
29
|
"Topic :: System :: Networking",
|
|
28
30
|
"Typing :: Typed",
|
|
29
31
|
]
|
|
30
32
|
dependencies = [
|
|
31
|
-
"tigrcorn-core==0.3.16.
|
|
32
|
-
"tigrcorn-config==0.3.16.
|
|
33
|
-
"tigrcorn-asgi==0.3.16.
|
|
34
|
-
"tigrcorn-http==0.3.16.
|
|
35
|
-
"tigrcorn-transports==0.3.16.
|
|
33
|
+
"tigrcorn-core==0.3.16.dev12",
|
|
34
|
+
"tigrcorn-config==0.3.16.dev12",
|
|
35
|
+
"tigrcorn-asgi==0.3.16.dev12",
|
|
36
|
+
"tigrcorn-http==0.3.16.dev12",
|
|
37
|
+
"tigrcorn-transports==0.3.16.dev12",
|
|
36
38
|
]
|
|
37
39
|
|
|
38
40
|
[tool.setuptools]
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
from contextlib import suppress
|
|
5
5
|
from urllib.parse import urlsplit
|
|
6
6
|
|
|
7
|
-
from tigrcorn_asgi.receive import HTTPRequestReceive, apply_request_trailer_policy
|
|
7
|
+
from tigrcorn_asgi.receive import HTTP2QueuedRequestReceive, HTTPRequestReceive, apply_request_trailer_policy
|
|
8
8
|
from tigrcorn_asgi.scopes.http import build_http_scope
|
|
9
9
|
from tigrcorn_asgi.send import HTTPResponseCollector, iter_response_body_segments, response_body_segments_have_bytes
|
|
10
10
|
from tigrcorn_config.model import ServerConfig
|
|
@@ -384,6 +384,7 @@ class HTTP2ConnectionHandler:
|
|
|
384
384
|
for name, value in headers:
|
|
385
385
|
if any(65 <= byte <= 90 for byte in name):
|
|
386
386
|
raise ProtocolError("uppercase header field name forbidden in HTTP/2")
|
|
387
|
+
self._validate_field_value(value)
|
|
387
388
|
if name.startswith(b":"):
|
|
388
389
|
raise ProtocolError("trailer pseudo-header forbidden in HTTP/2")
|
|
389
390
|
if name in {b"connection", b"upgrade", b"proxy-connection", b"transfer-encoding"}:
|
|
@@ -391,6 +392,36 @@ class HTTP2ConnectionHandler:
|
|
|
391
392
|
if name == b"te" and value.lower() != b"trailers":
|
|
392
393
|
raise ProtocolError("invalid TE header for HTTP/2")
|
|
393
394
|
|
|
395
|
+
def _validate_field_value(self, value: bytes) -> None:
|
|
396
|
+
if any(byte in {0x00, 0x0A, 0x0D} for byte in value):
|
|
397
|
+
raise ProtocolError("invalid HTTP/2 header field value")
|
|
398
|
+
if value[:1] in {b" ", b"\t"} or value[-1:] in {b" ", b"\t"}:
|
|
399
|
+
raise ProtocolError("invalid HTTP/2 header field value")
|
|
400
|
+
|
|
401
|
+
def _parse_content_length(self, headers: list[tuple[bytes, bytes]]) -> int | None:
|
|
402
|
+
values: list[bytes] = []
|
|
403
|
+
for name, value in headers:
|
|
404
|
+
if name.lower() != b"content-length":
|
|
405
|
+
continue
|
|
406
|
+
for part in value.split(b","):
|
|
407
|
+
parsed = part.strip()
|
|
408
|
+
if not parsed:
|
|
409
|
+
raise ProtocolError("invalid content-length header")
|
|
410
|
+
values.append(parsed)
|
|
411
|
+
if not values:
|
|
412
|
+
return None
|
|
413
|
+
parsed_value: int | None = None
|
|
414
|
+
for value in values:
|
|
415
|
+
if not value.isdigit():
|
|
416
|
+
raise ProtocolError("invalid content-length header")
|
|
417
|
+
current = int(value)
|
|
418
|
+
if parsed_value is None:
|
|
419
|
+
parsed_value = current
|
|
420
|
+
continue
|
|
421
|
+
if parsed_value != current:
|
|
422
|
+
raise ProtocolError("conflicting content-length values")
|
|
423
|
+
return parsed_value
|
|
424
|
+
|
|
394
425
|
async def _handle_headers(self, frame: HTTP2Frame) -> None:
|
|
395
426
|
if frame.stream_id == 0:
|
|
396
427
|
raise ProtocolError("HEADERS must use a stream id")
|
|
@@ -540,10 +571,19 @@ class HTTP2ConnectionHandler:
|
|
|
540
571
|
elif payload:
|
|
541
572
|
if state.buffered_body_size + len(payload) > self.config.max_body_size:
|
|
542
573
|
raise ProtocolError("request body exceeds configured max_body_size")
|
|
543
|
-
state.
|
|
544
|
-
|
|
574
|
+
if state.request_receive is not None:
|
|
575
|
+
await state.request_receive.put_body(payload, more_body=not bool(frame.flags & FLAG_END_STREAM))
|
|
576
|
+
else:
|
|
577
|
+
state.append_body(payload)
|
|
578
|
+
if (
|
|
579
|
+
state.expected_content_length is not None
|
|
580
|
+
and state.buffered_body_size > state.expected_content_length
|
|
581
|
+
):
|
|
582
|
+
raise ProtocolError("request body exceeds content-length")
|
|
545
583
|
if frame.flags & FLAG_END_STREAM:
|
|
546
584
|
state.receive_end_stream()
|
|
585
|
+
if state.request_receive is not None and not payload:
|
|
586
|
+
await state.request_receive.finish_body()
|
|
547
587
|
await self._maybe_dispatch(frame.stream_id)
|
|
548
588
|
self._finalize_stream_if_complete(frame.stream_id)
|
|
549
589
|
|
|
@@ -558,6 +598,7 @@ class HTTP2ConnectionHandler:
|
|
|
558
598
|
else:
|
|
559
599
|
state.headers = headers
|
|
560
600
|
state.headers_complete = True
|
|
601
|
+
state.expected_content_length = self._parse_content_length(headers)
|
|
561
602
|
state.header_fragments.clear()
|
|
562
603
|
state.header_block_bytes = 0
|
|
563
604
|
state.awaiting_continuation = False
|
|
@@ -567,9 +608,31 @@ class HTTP2ConnectionHandler:
|
|
|
567
608
|
state = self.streams.find(stream_id)
|
|
568
609
|
if state is None or state.dispatched or not state.headers_complete:
|
|
569
610
|
return
|
|
611
|
+
protocol = self._extended_connect_protocol(state.headers)
|
|
570
612
|
is_ws = self._is_extended_connect_websocket(state.headers)
|
|
571
613
|
is_connect = self._is_generic_connect_tunnel(state.headers)
|
|
572
|
-
if not
|
|
614
|
+
if protocol is not None and (protocol != b"websocket" or not self.config.websocket.enabled):
|
|
615
|
+
request = self._build_request(state)
|
|
616
|
+
if not self._admit_stream_work(stream_id):
|
|
617
|
+
await self._send_response(stream_id, 503, [(b"content-type", b"text/plain")], b"scheduler overloaded")
|
|
618
|
+
self.access_logger.log_http(self.client, request.method, request.path, 503, "HTTP/2")
|
|
619
|
+
self._release_stream_work_lease(stream_id)
|
|
620
|
+
self._cancel_stream(stream_id)
|
|
621
|
+
self.streams.close(stream_id)
|
|
622
|
+
self._maybe_finish_after_goaway()
|
|
623
|
+
return
|
|
624
|
+
state.dispatched = True
|
|
625
|
+
await self._send_response(
|
|
626
|
+
stream_id,
|
|
627
|
+
501,
|
|
628
|
+
[(b"content-type", b"text/plain")],
|
|
629
|
+
b"unsupported extended connect protocol",
|
|
630
|
+
)
|
|
631
|
+
self.access_logger.log_http(self.client, request.method, request.path, 501, "HTTP/2")
|
|
632
|
+
self._release_stream_work_lease(stream_id)
|
|
633
|
+
self._cancel_stream(stream_id)
|
|
634
|
+
self.streams.close(stream_id)
|
|
635
|
+
self._maybe_finish_after_goaway()
|
|
573
636
|
return
|
|
574
637
|
if not self._admit_stream_work(stream_id):
|
|
575
638
|
request = self._build_request(state)
|
|
@@ -673,13 +736,18 @@ class HTTP2ConnectionHandler:
|
|
|
673
736
|
def _pseudo_headers(self, headers: list[tuple[bytes, bytes]]) -> dict[bytes, bytes]:
|
|
674
737
|
return {k: v for k, v in headers if k.startswith(b":")}
|
|
675
738
|
|
|
676
|
-
def
|
|
739
|
+
def _extended_connect_protocol(self, headers: list[tuple[bytes, bytes]]) -> bytes | None:
|
|
677
740
|
pseudo = self._pseudo_headers(headers)
|
|
678
|
-
|
|
741
|
+
if pseudo.get(b":method") != b"CONNECT":
|
|
742
|
+
return None
|
|
743
|
+
return pseudo.get(b":protocol")
|
|
744
|
+
|
|
745
|
+
def _is_extended_connect_websocket(self, headers: list[tuple[bytes, bytes]]) -> bool:
|
|
746
|
+
return self._extended_connect_protocol(headers) == b"websocket"
|
|
679
747
|
|
|
680
748
|
def _is_generic_connect_tunnel(self, headers: list[tuple[bytes, bytes]]) -> bool:
|
|
681
749
|
pseudo = self._pseudo_headers(headers)
|
|
682
|
-
return pseudo.get(b":method") == b"CONNECT" and
|
|
750
|
+
return pseudo.get(b":method") == b"CONNECT" and self._extended_connect_protocol(headers) is None
|
|
683
751
|
def _release_stream_work_lease(self, stream_id: int) -> None:
|
|
684
752
|
lease = self.stream_work_leases.pop(stream_id, None)
|
|
685
753
|
if lease is not None:
|
|
@@ -735,6 +803,8 @@ class HTTP2ConnectionHandler:
|
|
|
735
803
|
method_text = method.decode("ascii", "strict").upper()
|
|
736
804
|
else:
|
|
737
805
|
method_text = str(method).upper()
|
|
806
|
+
if method_text not in {"GET", "HEAD"}:
|
|
807
|
+
raise ProtocolError("HTTP/2 server push requires a safe cacheable method")
|
|
738
808
|
authority = message.get("authority")
|
|
739
809
|
if authority is None:
|
|
740
810
|
authority_bytes = pseudo.get(b":authority", b"")
|
|
@@ -875,6 +945,105 @@ class HTTP2ConnectionHandler:
|
|
|
875
945
|
)
|
|
876
946
|
return processed.status, processed.headers, processed.body, ([] if processed.head_response else trailers), informational, None, cleanup
|
|
877
947
|
|
|
948
|
+
async def _run_http_app_live(self, stream_id: int, request: ParsedRequest, *, allow_push: bool) -> int:
|
|
949
|
+
extensions = dict(self.scope_extensions)
|
|
950
|
+
state = self.streams.find(stream_id)
|
|
951
|
+
if state is None:
|
|
952
|
+
return 500
|
|
953
|
+
raw_request_trailers = list(state.trailers)
|
|
954
|
+
try:
|
|
955
|
+
request_trailers = apply_request_trailer_policy(raw_request_trailers, self.config.http.trailer_policy)
|
|
956
|
+
except ProtocolError:
|
|
957
|
+
await self._send_response(stream_id, 400, [(b"content-type", b"text/plain")], b"bad request trailers", [], body_segments=None)
|
|
958
|
+
return 400
|
|
959
|
+
if request.method.upper() == "CONNECT":
|
|
960
|
+
extensions["tigrcorn.http.connect"] = {"authority": request.target}
|
|
961
|
+
if request_trailers and self.config.http.trailer_policy != 'drop':
|
|
962
|
+
extensions["tigrcorn.http.request_trailers"] = {}
|
|
963
|
+
if allow_push and self.state.client_allows_push:
|
|
964
|
+
extensions["http.response.push"] = {}
|
|
965
|
+
extensions['tigrcorn.http.response.file'] = {'protocol': 'http/2', 'streaming': True, 'sendfile': False}
|
|
966
|
+
extensions['http.response.pathsend'] = {}
|
|
967
|
+
scope = build_http_scope(
|
|
968
|
+
request,
|
|
969
|
+
client=self.client,
|
|
970
|
+
server=self.server,
|
|
971
|
+
scheme=self.scheme,
|
|
972
|
+
extensions=extensions,
|
|
973
|
+
root_path=self.config.proxy.root_path,
|
|
974
|
+
proxy=self.config.proxy,
|
|
975
|
+
)
|
|
976
|
+
receive = HTTP2QueuedRequestReceive(
|
|
977
|
+
trailers=request_trailers,
|
|
978
|
+
trailer_policy=self.config.http.trailer_policy,
|
|
979
|
+
on_body_consumed=lambda amount: self._maybe_replenish_receive_credit(stream_id, amount),
|
|
980
|
+
)
|
|
981
|
+
state.request_receive = receive
|
|
982
|
+
for part in state.body_parts:
|
|
983
|
+
await receive.put_body(part, more_body=True)
|
|
984
|
+
state.body_parts.clear()
|
|
985
|
+
if state.end_stream_received:
|
|
986
|
+
await receive.finish_body()
|
|
987
|
+
|
|
988
|
+
response_started = False
|
|
989
|
+
response_complete = False
|
|
990
|
+
final_status = 500
|
|
991
|
+
|
|
992
|
+
async def send(message: dict) -> None:
|
|
993
|
+
nonlocal response_started, response_complete, final_status
|
|
994
|
+
message_type = message.get("type")
|
|
995
|
+
if message_type == "http.response.push":
|
|
996
|
+
if not allow_push or not self.state.client_allows_push:
|
|
997
|
+
raise ProtocolError("HTTP/2 server push is not available on this stream")
|
|
998
|
+
await self._send_push_promise(stream_id, message)
|
|
999
|
+
return
|
|
1000
|
+
if message_type == "http.response.start":
|
|
1001
|
+
status = int(message["status"])
|
|
1002
|
+
headers = list(message.get("headers", []))
|
|
1003
|
+
if status < 200:
|
|
1004
|
+
await self._send_stream_headers(stream_id, status, sanitize_early_hints_headers(headers), end_stream=False)
|
|
1005
|
+
return
|
|
1006
|
+
if response_started:
|
|
1007
|
+
raise ProtocolError("http.response.start sent more than once")
|
|
1008
|
+
response_started = True
|
|
1009
|
+
final_status = status
|
|
1010
|
+
await self._send_stream_headers(stream_id, status, headers, end_stream=False)
|
|
1011
|
+
return
|
|
1012
|
+
if message_type == "http.response.body":
|
|
1013
|
+
if response_complete:
|
|
1014
|
+
raise ProtocolError("http.response.body sent after response completion")
|
|
1015
|
+
if not response_started:
|
|
1016
|
+
response_started = True
|
|
1017
|
+
final_status = 200
|
|
1018
|
+
await self._send_stream_headers(stream_id, 200, [], end_stream=False)
|
|
1019
|
+
body = bytes(message.get("body", b""))
|
|
1020
|
+
more_body = bool(message.get("more_body", False))
|
|
1021
|
+
await self._send_stream_data(stream_id, body, end_stream=not more_body)
|
|
1022
|
+
if not more_body:
|
|
1023
|
+
response_complete = True
|
|
1024
|
+
return
|
|
1025
|
+
raise ProtocolError(f"unsupported HTTP/2 ASGI send message: {message_type!r}")
|
|
1026
|
+
|
|
1027
|
+
try:
|
|
1028
|
+
await self.app(scope, receive, send)
|
|
1029
|
+
if not response_complete:
|
|
1030
|
+
if not response_started:
|
|
1031
|
+
response_started = True
|
|
1032
|
+
final_status = 200
|
|
1033
|
+
await self._send_stream_headers(stream_id, 200, [], end_stream=False)
|
|
1034
|
+
await self._send_stream_data(stream_id, b"", end_stream=True)
|
|
1035
|
+
response_complete = True
|
|
1036
|
+
return final_status
|
|
1037
|
+
except Exception:
|
|
1038
|
+
if not response_started and self.streams.find(stream_id) is not None:
|
|
1039
|
+
await self._send_response(stream_id, 500, [(b"content-type", b"text/plain")], b"internal server error")
|
|
1040
|
+
elif response_started and not response_complete and self.streams.find(stream_id) is not None:
|
|
1041
|
+
await self._send_stream_data(stream_id, b"", end_stream=True)
|
|
1042
|
+
return 500
|
|
1043
|
+
finally:
|
|
1044
|
+
if state.request_receive is receive:
|
|
1045
|
+
state.request_receive = None
|
|
1046
|
+
|
|
878
1047
|
async def _send_push_promise(self, parent_stream_id: int, message: dict) -> None:
|
|
879
1048
|
if not self.state.client_allows_push:
|
|
880
1049
|
return
|
|
@@ -1078,9 +1247,11 @@ class HTTP2ConnectionHandler:
|
|
|
1078
1247
|
pseudo_seen: set[bytes] = set()
|
|
1079
1248
|
regular_seen = False
|
|
1080
1249
|
allowed_pseudo = {b":method", b":scheme", b":authority", b":path", b":protocol"}
|
|
1250
|
+
host_values: list[bytes] = []
|
|
1081
1251
|
for name, value in headers:
|
|
1082
1252
|
if any(65 <= byte <= 90 for byte in name):
|
|
1083
1253
|
raise ProtocolError("uppercase header field name forbidden in HTTP/2")
|
|
1254
|
+
self._validate_field_value(value)
|
|
1084
1255
|
if name.startswith(b":"):
|
|
1085
1256
|
if regular_seen:
|
|
1086
1257
|
raise ProtocolError("pseudo-header after regular header")
|
|
@@ -1095,10 +1266,22 @@ class HTTP2ConnectionHandler:
|
|
|
1095
1266
|
raise ProtocolError("connection-specific header forbidden in HTTP/2")
|
|
1096
1267
|
if name == b"te" and value.lower() != b"trailers":
|
|
1097
1268
|
raise ProtocolError("invalid TE header for HTTP/2")
|
|
1269
|
+
if name == b"host":
|
|
1270
|
+
host_values.append(value)
|
|
1098
1271
|
if b":method" not in pseudo_seen:
|
|
1099
1272
|
raise ProtocolError("missing :method pseudo-header")
|
|
1100
|
-
|
|
1101
|
-
|
|
1273
|
+
pseudo_headers = {name: value for name, value in headers if name.startswith(b":")}
|
|
1274
|
+
method = pseudo_headers.get(b":method", b"GET")
|
|
1275
|
+
protocol = pseudo_headers.get(b":protocol")
|
|
1276
|
+
authority = pseudo_headers.get(b":authority")
|
|
1277
|
+
if authority is not None and b"@" in authority:
|
|
1278
|
+
raise ProtocolError("userinfo is forbidden in :authority")
|
|
1279
|
+
if host_values:
|
|
1280
|
+
normalized_hosts = {value.lower() for value in host_values}
|
|
1281
|
+
if len(normalized_hosts) != 1:
|
|
1282
|
+
raise ProtocolError("conflicting host header values")
|
|
1283
|
+
if authority is not None and next(iter(normalized_hosts)) != authority.lower():
|
|
1284
|
+
raise ProtocolError("host header must match :authority")
|
|
1102
1285
|
if protocol is not None:
|
|
1103
1286
|
if method != b"CONNECT":
|
|
1104
1287
|
raise ProtocolError("extended CONNECT requires CONNECT method")
|
|
@@ -1121,15 +1304,26 @@ class HTTP2ConnectionHandler:
|
|
|
1121
1304
|
pseudo = {k: v for k, v in state.headers if k.startswith(b":")}
|
|
1122
1305
|
headers = [(k, v) for k, v in state.headers if not k.startswith(b":")]
|
|
1123
1306
|
method = pseudo.get(b":method", b"GET").decode("ascii", "strict")
|
|
1307
|
+
if state.expected_content_length is not None:
|
|
1308
|
+
observed = len(state.body)
|
|
1309
|
+
if observed > state.expected_content_length:
|
|
1310
|
+
raise ProtocolError("request body exceeds content-length")
|
|
1311
|
+
if state.end_stream_received and observed != state.expected_content_length:
|
|
1312
|
+
raise ProtocolError("request body does not match content-length")
|
|
1124
1313
|
if method.upper() == "CONNECT" and pseudo.get(b":protocol") != b"websocket":
|
|
1125
1314
|
target = pseudo.get(b":authority", b"").decode("ascii", "strict")
|
|
1126
1315
|
path = target
|
|
1127
1316
|
raw_path = target.encode("ascii", "strict")
|
|
1128
1317
|
query_string = b""
|
|
1129
1318
|
else:
|
|
1130
|
-
|
|
1319
|
+
target_bytes = pseudo.get(b":path", b"")
|
|
1320
|
+
if not target_bytes:
|
|
1321
|
+
raise ProtocolError("empty :path pseudo-header")
|
|
1322
|
+
target = target_bytes.decode("ascii", "strict")
|
|
1131
1323
|
split = urlsplit(target)
|
|
1132
|
-
|
|
1324
|
+
if not split.path:
|
|
1325
|
+
raise ProtocolError("malformed request target")
|
|
1326
|
+
path = split.path
|
|
1133
1327
|
raw_path = path.encode("utf-8")
|
|
1134
1328
|
query_string = split.query.encode("ascii")
|
|
1135
1329
|
return ParsedRequest(
|
|
@@ -1162,18 +1356,20 @@ class HTTP2ConnectionHandler:
|
|
|
1162
1356
|
self.streams.close(stream_id)
|
|
1163
1357
|
self._maybe_finish_after_goaway()
|
|
1164
1358
|
return
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
cleanup
|
|
1359
|
+
if state.end_stream_received:
|
|
1360
|
+
status, headers, body, trailers, informational, body_segments, cleanup = await self._run_http_app(stream_id, request, allow_push=True)
|
|
1361
|
+
for interim_status, interim_headers in informational:
|
|
1362
|
+
await self._send_stream_headers(stream_id, interim_status, sanitize_early_hints_headers(interim_headers), end_stream=False)
|
|
1363
|
+
try:
|
|
1364
|
+
await self._send_response(stream_id, status, headers, body, trailers, body_segments=body_segments)
|
|
1365
|
+
finally:
|
|
1366
|
+
if cleanup is not None:
|
|
1367
|
+
cleanup()
|
|
1368
|
+
else:
|
|
1369
|
+
status = await self._run_http_app_live(stream_id, request, allow_push=True)
|
|
1173
1370
|
self.access_logger.log_http(self.client, request.method, request.path, status, "HTTP/2")
|
|
1174
1371
|
if self.streams.find(stream_id) is not None:
|
|
1175
|
-
self.
|
|
1176
|
-
self.streams.close(stream_id)
|
|
1372
|
+
self._finalize_stream_if_complete(stream_id)
|
|
1177
1373
|
self._maybe_finish_after_goaway()
|
|
1178
1374
|
finally:
|
|
1179
1375
|
self._release_stream_work_lease(stream_id)
|