wsbuilder 1.2.9__tar.gz → 1.2.11__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.
- {wsbuilder-1.2.9/src/wsbuilder.egg-info → wsbuilder-1.2.11}/PKG-INFO +1 -1
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/pyproject.toml +1 -1
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/__init__.py +1 -1
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/http3_server.py +7 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11/src/wsbuilder.egg-info}/PKG-INFO +1 -1
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http3_streaming.py +59 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/LICENSE +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/MANIFEST.in +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/README.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/404.html +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/CNAME +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/advanced.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/ai-predict.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/api-map.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/app-http.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/architecture.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/assets/wsbuilder-mark.svg +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/beginners.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/cache-security.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/cache.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/capabilities.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/data-orm.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/dns.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/full-project.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/getting-started.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/help/index.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/http-versions.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/http.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/ia.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/index.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/install.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/intermediate.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/mdns.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/network-edge.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/observability-tasks.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/observability.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/persistence.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/proxy.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/reference/index.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/security.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/stylesheets/extra.css +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/tasks.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/tls-certificates.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/docs/websocket.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/examples/full-project/.gitignore +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/examples/full-project/README.md +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/examples/full-project/app.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/examples/full-project/pyproject.toml +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/examples/full-project/upstream.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/mkdocs.yml +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/setup.cfg +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/__main__.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/_coerce.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/app.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/cache.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/caches.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/constants.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/cookies.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/db_replicas.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/dns.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/framework.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/headers.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/hpack.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/http.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/http1.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/http2.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/http3.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/http3_websocket.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/ia.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/logs.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/mdns.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/metrics.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/orm.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/pki.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/predicts.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/proxyi.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/qpack.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/__init__.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/address.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/connection.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/crypto.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/frames.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/packet.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/recovery.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/tls.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/quic/varint.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/security.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/server.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/tasks.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder/ws.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder.egg-info/SOURCES.txt +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder.egg-info/dependency_links.txt +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder.egg-info/entry_points.txt +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder.egg-info/requires.txt +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/src/wsbuilder.egg-info/top_level.txt +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_app_threads.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_cache.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_caches.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_contributing_policy.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_demo_monitor.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_dns.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_docs.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_documentation_files.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_headers_cookies.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_hpack.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http1.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http2.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http3.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http3_server.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_http3_streaming_udp.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_ia.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_logs.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_mdns.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_metrics.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_orm.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_orm_replicas.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_pki.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_pr_identity_policy.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_predicts.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_proxyi.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_proxyi_routing.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_quic_address.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_quic_crypto.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_quic_protocol.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_quic_recovery.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_quic_tls.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_security.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_server.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_server_errors.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_tasks.py +0 -0
- {wsbuilder-1.2.9 → wsbuilder-1.2.11}/tests/test_ws.py +0 -0
|
@@ -367,6 +367,13 @@ class Http3Server:
|
|
|
367
367
|
get_log(self.app).exception("http3", f"dispatch failed on stream {stream_id}: {e}", e)
|
|
368
368
|
response = Response.text("Internal Server Error", status=500)
|
|
369
369
|
if connection.closed:
|
|
370
|
+
if getattr(response, "is_stream", False):
|
|
371
|
+
try:
|
|
372
|
+
close = getattr(response.stream, "close", None)
|
|
373
|
+
if callable(close):
|
|
374
|
+
close()
|
|
375
|
+
except Exception as e:
|
|
376
|
+
get_log(self.app).exception("http3", f"streaming cleanup on stream {stream_id} failed: {e}", e)
|
|
370
377
|
return
|
|
371
378
|
try:
|
|
372
379
|
self._send_response(connection, stream_id, response, send_body=request.method != "HEAD")
|
|
@@ -4,6 +4,7 @@ import socket
|
|
|
4
4
|
import threading
|
|
5
5
|
import unittest
|
|
6
6
|
from types import SimpleNamespace
|
|
7
|
+
from unittest.mock import patch
|
|
7
8
|
|
|
8
9
|
from wsbuilder import App, Response, http3
|
|
9
10
|
from wsbuilder.http3_server import Http3Server
|
|
@@ -207,6 +208,64 @@ class TestHttp3BodyLimit(unittest.TestCase):
|
|
|
207
208
|
|
|
208
209
|
|
|
209
210
|
class TestHttp3StreamCleanup(unittest.TestCase):
|
|
211
|
+
def test_closing_a_connection_during_dispatch_closes_the_unused_producer(self):
|
|
212
|
+
class Producer:
|
|
213
|
+
iterations = 0
|
|
214
|
+
close_calls = 0
|
|
215
|
+
|
|
216
|
+
def __iter__(self):
|
|
217
|
+
self.iterations += 1
|
|
218
|
+
return iter([b"chunk"])
|
|
219
|
+
|
|
220
|
+
class ClosingProducer(Producer):
|
|
221
|
+
def __init__(self, fail_close=False):
|
|
222
|
+
self.error = OSError("cleanup failed") if fail_close else None
|
|
223
|
+
|
|
224
|
+
def close(self):
|
|
225
|
+
self.close_calls += 1
|
|
226
|
+
if self.error is not None:
|
|
227
|
+
raise self.error
|
|
228
|
+
|
|
229
|
+
for producer in (ClosingProducer(), ClosingProducer(fail_close=True), Producer()):
|
|
230
|
+
with self.subTest(producer=type(producer).__name__, error=getattr(producer, "error", None)):
|
|
231
|
+
entered = threading.Event()
|
|
232
|
+
release = threading.Event()
|
|
233
|
+
queued = []
|
|
234
|
+
|
|
235
|
+
def dispatch(request):
|
|
236
|
+
entered.set()
|
|
237
|
+
if not release.wait(2.0):
|
|
238
|
+
raise TimeoutError("dispatch was never released")
|
|
239
|
+
return Response.stream(producer)
|
|
240
|
+
|
|
241
|
+
app = SimpleNamespace(dispatch=dispatch)
|
|
242
|
+
server = Http3Server("127.0.0.1", 0, app, None)
|
|
243
|
+
self.addCleanup(server._dispatch_pool.shutdown, wait=True)
|
|
244
|
+
connection = SimpleNamespace(
|
|
245
|
+
closed=False,
|
|
246
|
+
queue_stream_data=lambda *args, **kwargs: queued.append((args, kwargs)),
|
|
247
|
+
)
|
|
248
|
+
request = SimpleNamespace(method="GET")
|
|
249
|
+
with patch("wsbuilder.http3_server.get_log") as get_log:
|
|
250
|
+
future = server._dispatch_pool.submit(server._dispatch, connection, 12, request)
|
|
251
|
+
try:
|
|
252
|
+
self.assertTrue(entered.wait(2.0), "dispatch never started")
|
|
253
|
+
server._drop(connection)
|
|
254
|
+
finally:
|
|
255
|
+
release.set()
|
|
256
|
+
future.result(timeout=2.0)
|
|
257
|
+
|
|
258
|
+
self.assertEqual(producer.iterations, 0)
|
|
259
|
+
self.assertEqual(queued, [])
|
|
260
|
+
self.assertEqual(producer.close_calls, int(isinstance(producer, ClosingProducer)))
|
|
261
|
+
if getattr(producer, "error", None) is not None:
|
|
262
|
+
get_log.assert_called_once_with(app)
|
|
263
|
+
get_log.return_value.exception.assert_called_once_with(
|
|
264
|
+
"http3", "streaming cleanup on stream 12 failed: cleanup failed", producer.error,
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
get_log.assert_not_called()
|
|
268
|
+
|
|
210
269
|
def test_dropping_a_connection_wakes_all_websocket_readers(self):
|
|
211
270
|
server = Http3Server("127.0.0.1", 0, None, None)
|
|
212
271
|
connection = SimpleNamespace(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|