sf-veritas 0.10.3__cp39-cp39-manylinux_2_28_x86_64.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.
Potentially problematic release.
This version of sf-veritas might be problematic. Click here for more details.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-39-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +556 -0
- sf_veritas/function_span_profiler.py +1174 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +497 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +970 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context-propagation + user-code NetworkHop emission for every aiohttp
|
|
3
|
+
request, while skipping Strawberry GraphQL views.
|
|
4
|
+
|
|
5
|
+
OTEL-STYLE: Emits network hops AFTER handler completes (zero-overhead).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
from ... import _sffuncspan_config, app_config
|
|
11
|
+
from ...constants import (
|
|
12
|
+
FUNCSPAN_OVERRIDE_HEADER_BYTES,
|
|
13
|
+
SAILFISH_TRACING_HEADER,
|
|
14
|
+
SAILFISH_TRACING_HEADER_BYTES,
|
|
15
|
+
)
|
|
16
|
+
from ...custom_excepthook import custom_excepthook
|
|
17
|
+
from ...env_vars import (
|
|
18
|
+
SF_DEBUG,
|
|
19
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_BODY,
|
|
20
|
+
SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS,
|
|
21
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY,
|
|
22
|
+
SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS,
|
|
23
|
+
SF_NETWORKHOP_REQUEST_LIMIT_MB,
|
|
24
|
+
SF_NETWORKHOP_RESPONSE_LIMIT_MB,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# LAZY IMPORT: Import inside function to avoid circular import with _sfnetworkhop
|
|
28
|
+
# from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
|
|
29
|
+
from ...thread_local import (
|
|
30
|
+
clear_c_tls_parent_trace_id,
|
|
31
|
+
clear_current_request_path,
|
|
32
|
+
clear_outbound_header_base,
|
|
33
|
+
clear_trace_id,
|
|
34
|
+
generate_new_trace_id,
|
|
35
|
+
get_or_set_sf_trace_id,
|
|
36
|
+
get_sf_trace_id,
|
|
37
|
+
set_current_request_path,
|
|
38
|
+
set_funcspan_override,
|
|
39
|
+
set_outbound_header_base,
|
|
40
|
+
)
|
|
41
|
+
from .cors_utils import inject_sailfish_headers, should_inject_headers
|
|
42
|
+
|
|
43
|
+
# Size limits in bytes
|
|
44
|
+
_REQUEST_LIMIT_BYTES = SF_NETWORKHOP_REQUEST_LIMIT_MB * 1024 * 1024
|
|
45
|
+
_RESPONSE_LIMIT_BYTES = SF_NETWORKHOP_RESPONSE_LIMIT_MB * 1024 * 1024
|
|
46
|
+
|
|
47
|
+
# Pre-registered endpoint IDs (maps (filename, lineno) tuple -> endpoint_id from C extension)
|
|
48
|
+
_ENDPOINT_REGISTRY: dict[tuple, int] = {}
|
|
49
|
+
|
|
50
|
+
# Track which aiohttp app instances have been registered (to support multiple apps)
|
|
51
|
+
_REGISTERED_APPS: set[int] = set()
|
|
52
|
+
|
|
53
|
+
# Routes to skip (set by patch_aiohttp)
|
|
54
|
+
_ROUTES_TO_SKIP = []
|
|
55
|
+
|
|
56
|
+
# ------------------------------------------------------------------ #
|
|
57
|
+
# shared helpers
|
|
58
|
+
# ------------------------------------------------------------------ #
|
|
59
|
+
from .utils import _is_user_code, _unwrap_user_func, should_skip_route # cached
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _should_trace_endpoint(handler) -> bool:
|
|
63
|
+
"""Check if endpoint should be traced."""
|
|
64
|
+
real_fn = _unwrap_user_func(handler)
|
|
65
|
+
if not callable(real_fn):
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
if getattr(real_fn, "__module__", "").startswith("strawberry"):
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
code = getattr(real_fn, "__code__", None)
|
|
72
|
+
if not code:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
if not _is_user_code(code.co_filename):
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _pre_register_endpoints(app, routes_to_skip: Optional[List[str]] = None):
|
|
82
|
+
"""Pre-register all endpoints at startup."""
|
|
83
|
+
# LAZY IMPORT: Import here to avoid circular import with _sfnetworkhop
|
|
84
|
+
from ...fast_network_hop import register_endpoint
|
|
85
|
+
|
|
86
|
+
routes_to_skip = routes_to_skip or []
|
|
87
|
+
count = 0
|
|
88
|
+
skipped = 0
|
|
89
|
+
|
|
90
|
+
app_id = id(app)
|
|
91
|
+
|
|
92
|
+
# Check if this app has already been registered
|
|
93
|
+
if app_id in _REGISTERED_APPS:
|
|
94
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
95
|
+
print(
|
|
96
|
+
f"[[_pre_register_endpoints]] App {app_id} already registered, skipping",
|
|
97
|
+
log=False,
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
102
|
+
print(
|
|
103
|
+
f"[[_pre_register_endpoints]] Starting registration for app {app_id}",
|
|
104
|
+
log=False,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Iterate through all routes in the router
|
|
108
|
+
if not hasattr(app, "router") or not hasattr(app.router, "_resources"):
|
|
109
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
110
|
+
print(
|
|
111
|
+
f"[[_pre_register_endpoints]] App has no router or resources, skipping",
|
|
112
|
+
log=False,
|
|
113
|
+
)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
for resource in app.router._resources:
|
|
117
|
+
if not hasattr(resource, "_routes"):
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
for route in resource._routes:
|
|
121
|
+
if not hasattr(route, "_handler"):
|
|
122
|
+
skipped += 1
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
handler = route._handler
|
|
126
|
+
if not _should_trace_endpoint(handler):
|
|
127
|
+
skipped += 1
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
unwrapped = _unwrap_user_func(handler)
|
|
131
|
+
code = unwrapped.__code__
|
|
132
|
+
key = (code.co_filename, code.co_firstlineno)
|
|
133
|
+
|
|
134
|
+
# Check if this specific endpoint is already registered
|
|
135
|
+
if key in _ENDPOINT_REGISTRY:
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
# Extract route pattern
|
|
139
|
+
route_pattern = None
|
|
140
|
+
if hasattr(resource, "_path"):
|
|
141
|
+
route_pattern = resource._path
|
|
142
|
+
elif hasattr(resource, "canonical"):
|
|
143
|
+
route_pattern = resource.canonical
|
|
144
|
+
|
|
145
|
+
# Check if route should be skipped based on wildcard patterns
|
|
146
|
+
if route_pattern and should_skip_route(route_pattern, routes_to_skip):
|
|
147
|
+
skipped += 1
|
|
148
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
149
|
+
print(
|
|
150
|
+
f"[[_pre_register_endpoints]] Skipping endpoint (route matches skip pattern): {route_pattern}",
|
|
151
|
+
log=False,
|
|
152
|
+
)
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
endpoint_id = register_endpoint(
|
|
156
|
+
line=str(code.co_firstlineno),
|
|
157
|
+
column="0",
|
|
158
|
+
name=unwrapped.__name__,
|
|
159
|
+
entrypoint=code.co_filename,
|
|
160
|
+
route=route_pattern,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if endpoint_id < 0:
|
|
164
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
165
|
+
print(
|
|
166
|
+
f"[[_pre_register_endpoints]] Failed to register {unwrapped.__name__} (endpoint_id={endpoint_id})",
|
|
167
|
+
log=False,
|
|
168
|
+
)
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
_ENDPOINT_REGISTRY[key] = endpoint_id
|
|
172
|
+
count += 1
|
|
173
|
+
|
|
174
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
175
|
+
print(
|
|
176
|
+
f"[[patch_aiohttp]] Registered: {unwrapped.__name__} @ {code.co_filename}:{code.co_firstlineno} route={route_pattern} (endpoint_id={endpoint_id})",
|
|
177
|
+
log=False,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
181
|
+
print(f"[[patch_aiohttp]] Total endpoints registered: {count}", log=False)
|
|
182
|
+
|
|
183
|
+
# Only mark this app as registered if we actually registered user endpoints
|
|
184
|
+
if count > 0:
|
|
185
|
+
_REGISTERED_APPS.add(app_id)
|
|
186
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
187
|
+
print(f"[[patch_aiohttp]] App {app_id} marked as registered", log=False)
|
|
188
|
+
else:
|
|
189
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
190
|
+
print(
|
|
191
|
+
f"[[patch_aiohttp]] No user endpoints registered yet for app {app_id}",
|
|
192
|
+
log=False,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------------------ #
|
|
197
|
+
# monkey-patch
|
|
198
|
+
# ------------------------------------------------------------------ #
|
|
199
|
+
def patch_aiohttp(routes_to_skip: Optional[List[str]] = None):
|
|
200
|
+
"""
|
|
201
|
+
OTEL-STYLE PURE ASYNC:
|
|
202
|
+
• Prepends middleware that propagates SAILFISH_TRACING_HEADER
|
|
203
|
+
• Captures endpoint metadata before handler
|
|
204
|
+
• Emits NetworkHop AFTER handler completes (zero-overhead)
|
|
205
|
+
• Patches Application.add_route(s) for RouteTableDef support
|
|
206
|
+
Safe no-op if aiohttp isn't installed.
|
|
207
|
+
"""
|
|
208
|
+
global _ROUTES_TO_SKIP
|
|
209
|
+
_ROUTES_TO_SKIP = routes_to_skip or []
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
from aiohttp import web
|
|
213
|
+
except ImportError: # aiohttp missing
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
# ===========================================================
|
|
217
|
+
# 1 | OTEL-STYLE Middleware
|
|
218
|
+
# ===========================================================
|
|
219
|
+
@web.middleware
|
|
220
|
+
async def _sf_tracing_middleware(request: web.Request, handler):
|
|
221
|
+
"""
|
|
222
|
+
OTEL-STYLE aiohttp middleware that:
|
|
223
|
+
1 - Seed ContextVar from the inbound SAILFISH_TRACING_HEADER header.
|
|
224
|
+
2 - Capture request headers/body if enabled.
|
|
225
|
+
3 - Captures endpoint metadata and register endpoint.
|
|
226
|
+
4 - Call handler and capture exceptions.
|
|
227
|
+
5 - Capture response headers/body if enabled.
|
|
228
|
+
6 - Emits NetworkHop AFTER handler completes (OTEL-style zero-overhead).
|
|
229
|
+
"""
|
|
230
|
+
# Set current request path for route-based suppression (SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES)
|
|
231
|
+
set_current_request_path(request.path)
|
|
232
|
+
|
|
233
|
+
# LAZY IMPORT: Import here to avoid circular import with _sfnetworkhop
|
|
234
|
+
from ...fast_network_hop import fast_send_network_hop_fast, register_endpoint
|
|
235
|
+
|
|
236
|
+
# PERFORMANCE: Single-pass bytes-level header scan (no dict allocation until needed)
|
|
237
|
+
# Scan headers once on bytes, only decode what we need, use latin-1 (fast 1:1 byte map)
|
|
238
|
+
# aiohttp uses CIMultiDict for headers, need to iterate raw tuples
|
|
239
|
+
hdr_items = request.headers.items() if hasattr(request.headers, "items") else []
|
|
240
|
+
incoming_trace_raw = None # bytes or str
|
|
241
|
+
funcspan_raw = None # bytes or str
|
|
242
|
+
req_headers = None # dict[str,str] only if capture enabled
|
|
243
|
+
|
|
244
|
+
capture_req_headers = SF_NETWORKHOP_CAPTURE_REQUEST_HEADERS # local cache
|
|
245
|
+
|
|
246
|
+
if capture_req_headers:
|
|
247
|
+
# build the dict while we're scanning
|
|
248
|
+
tmp = {}
|
|
249
|
+
for k, v in hdr_items:
|
|
250
|
+
# aiohttp headers are already strings, check as string first
|
|
251
|
+
if isinstance(k, str):
|
|
252
|
+
kl = k.lower()
|
|
253
|
+
if kl == SAILFISH_TRACING_HEADER.lower():
|
|
254
|
+
incoming_trace_raw = v
|
|
255
|
+
elif kl == "x-sf3-functionspancaptureoverride":
|
|
256
|
+
funcspan_raw = v
|
|
257
|
+
tmp[k] = v
|
|
258
|
+
else:
|
|
259
|
+
# fallback for bytes
|
|
260
|
+
kl = k.lower() if isinstance(k, bytes) else k.encode().lower()
|
|
261
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
262
|
+
incoming_trace_raw = v
|
|
263
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
264
|
+
funcspan_raw = v
|
|
265
|
+
tmp[k.decode("latin-1") if isinstance(k, bytes) else k] = (
|
|
266
|
+
v.decode("latin-1") if isinstance(v, bytes) else v
|
|
267
|
+
)
|
|
268
|
+
req_headers = tmp
|
|
269
|
+
else:
|
|
270
|
+
for k, v in hdr_items:
|
|
271
|
+
if isinstance(k, str):
|
|
272
|
+
kl = k.lower()
|
|
273
|
+
if kl == SAILFISH_TRACING_HEADER.lower():
|
|
274
|
+
incoming_trace_raw = v
|
|
275
|
+
elif kl == "x-sf3-functionspancaptureoverride":
|
|
276
|
+
funcspan_raw = v
|
|
277
|
+
else:
|
|
278
|
+
kl = k.lower() if isinstance(k, bytes) else k.encode().lower()
|
|
279
|
+
if kl == SAILFISH_TRACING_HEADER_BYTES:
|
|
280
|
+
incoming_trace_raw = v
|
|
281
|
+
elif kl == FUNCSPAN_OVERRIDE_HEADER_BYTES:
|
|
282
|
+
funcspan_raw = v
|
|
283
|
+
|
|
284
|
+
# 1. CRITICAL: Seed/ensure trace_id immediately (BEFORE any outbound work)
|
|
285
|
+
if incoming_trace_raw:
|
|
286
|
+
# Incoming X-Sf3-Rid header provided - use it
|
|
287
|
+
incoming_trace = (
|
|
288
|
+
incoming_trace_raw
|
|
289
|
+
if isinstance(incoming_trace_raw, str)
|
|
290
|
+
else incoming_trace_raw.decode("latin-1")
|
|
291
|
+
)
|
|
292
|
+
get_or_set_sf_trace_id(
|
|
293
|
+
incoming_trace, is_associated_with_inbound_request=True
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
# No incoming X-Sf3-Rid header - generate fresh trace_id for this request
|
|
297
|
+
generate_new_trace_id()
|
|
298
|
+
|
|
299
|
+
# Optional funcspan override (decode only if present)
|
|
300
|
+
funcspan_override_header = None
|
|
301
|
+
if funcspan_raw:
|
|
302
|
+
funcspan_override_header = (
|
|
303
|
+
funcspan_raw
|
|
304
|
+
if isinstance(funcspan_raw, str)
|
|
305
|
+
else funcspan_raw.decode("latin-1")
|
|
306
|
+
)
|
|
307
|
+
try:
|
|
308
|
+
set_funcspan_override(funcspan_override_header)
|
|
309
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
310
|
+
print(
|
|
311
|
+
f"[[aiohttp.middleware]] Set function span override from header: {funcspan_override_header}",
|
|
312
|
+
log=False,
|
|
313
|
+
)
|
|
314
|
+
except Exception as e:
|
|
315
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
316
|
+
print(
|
|
317
|
+
f"[[aiohttp.middleware]] Failed to set function span override: {e}",
|
|
318
|
+
log=False,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Initialize outbound base without list/allocs from split()
|
|
322
|
+
try:
|
|
323
|
+
trace_id = get_sf_trace_id()
|
|
324
|
+
if trace_id:
|
|
325
|
+
s = str(trace_id)
|
|
326
|
+
i = s.find("/") # session
|
|
327
|
+
j = s.find("/", i + 1) if i != -1 else -1 # page
|
|
328
|
+
if j != -1:
|
|
329
|
+
base_trace = s[:j] # "session/page"
|
|
330
|
+
set_outbound_header_base(
|
|
331
|
+
base_trace=base_trace,
|
|
332
|
+
parent_trace_id=s, # "session/page/uuid"
|
|
333
|
+
funcspan=funcspan_override_header,
|
|
334
|
+
)
|
|
335
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
336
|
+
print(
|
|
337
|
+
f"[[aiohttp.middleware]] Initialized outbound header base (base={base_trace[:16]}...)",
|
|
338
|
+
log=False,
|
|
339
|
+
)
|
|
340
|
+
except Exception as e:
|
|
341
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
342
|
+
print(
|
|
343
|
+
f"[[aiohttp.middleware]] Failed to initialize outbound header base: {e}",
|
|
344
|
+
log=False,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# 3. Capture request body if enabled
|
|
348
|
+
req_body = None
|
|
349
|
+
if SF_NETWORKHOP_CAPTURE_REQUEST_BODY:
|
|
350
|
+
try:
|
|
351
|
+
# aiohttp Request.read() returns the body
|
|
352
|
+
body = await request.read()
|
|
353
|
+
req_body = body[:_REQUEST_LIMIT_BYTES] if body else None
|
|
354
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
355
|
+
print(
|
|
356
|
+
f"[[aiohttp]] Request body capture: {len(body) if body else 0} bytes (method={request.method})",
|
|
357
|
+
log=False,
|
|
358
|
+
)
|
|
359
|
+
except Exception as e:
|
|
360
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
361
|
+
print(f"[[aiohttp]] Failed to capture request body: {e}", log=False)
|
|
362
|
+
|
|
363
|
+
# 4. OTEL-STYLE: Capture endpoint metadata and register endpoint
|
|
364
|
+
endpoint_id = None
|
|
365
|
+
real_fn = _unwrap_user_func(handler)
|
|
366
|
+
if callable(real_fn) and not real_fn.__module__.startswith("strawberry"):
|
|
367
|
+
code = getattr(real_fn, "__code__", None)
|
|
368
|
+
if code and _is_user_code(code.co_filename):
|
|
369
|
+
key = (code.co_filename, code.co_firstlineno)
|
|
370
|
+
sent = request.setdefault("sf_hops_sent", set())
|
|
371
|
+
if key not in sent:
|
|
372
|
+
fname = code.co_filename
|
|
373
|
+
lno = code.co_firstlineno
|
|
374
|
+
fname_str = real_fn.__name__
|
|
375
|
+
|
|
376
|
+
# Get route pattern if available
|
|
377
|
+
route_pattern = getattr(request.match_info, "route", None)
|
|
378
|
+
route_str = str(
|
|
379
|
+
route_pattern.resource.canonical if route_pattern else None
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Check if route should be skipped
|
|
383
|
+
if route_str and should_skip_route(route_str, _ROUTES_TO_SKIP):
|
|
384
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
385
|
+
print(
|
|
386
|
+
f"[[Aiohttp]] Skipping endpoint (route matches skip pattern): {route_str}",
|
|
387
|
+
log=False,
|
|
388
|
+
)
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
# Get or register endpoint
|
|
392
|
+
endpoint_id = _ENDPOINT_REGISTRY.get(key)
|
|
393
|
+
if endpoint_id is None:
|
|
394
|
+
endpoint_id = register_endpoint(
|
|
395
|
+
line=str(lno),
|
|
396
|
+
column="0",
|
|
397
|
+
name=fname_str,
|
|
398
|
+
entrypoint=fname,
|
|
399
|
+
route=route_str,
|
|
400
|
+
)
|
|
401
|
+
if endpoint_id >= 0:
|
|
402
|
+
_ENDPOINT_REGISTRY[key] = endpoint_id
|
|
403
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
404
|
+
print(
|
|
405
|
+
f"[[aiohttp]] Registered endpoint: {fname_str} @ {fname}:{lno} (id={endpoint_id})",
|
|
406
|
+
log=False,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
sent.add(key)
|
|
410
|
+
request["sf_endpoint_id"] = endpoint_id
|
|
411
|
+
|
|
412
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
413
|
+
print(
|
|
414
|
+
f"[[aiohttp]] Captured endpoint: {fname_str} ({fname}:{lno}) endpoint_id={endpoint_id}",
|
|
415
|
+
log=False,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# 5. Call handler and capture exceptions (with cleanup in finally)
|
|
419
|
+
try:
|
|
420
|
+
try:
|
|
421
|
+
response = await handler(request)
|
|
422
|
+
except Exception as exc: # ← captures *all* errors
|
|
423
|
+
custom_excepthook(type(exc), exc, exc.__traceback__)
|
|
424
|
+
raise # re-raise for aiohttp
|
|
425
|
+
|
|
426
|
+
# 6. Capture response headers if enabled
|
|
427
|
+
resp_headers = None
|
|
428
|
+
if SF_NETWORKHOP_CAPTURE_RESPONSE_HEADERS and endpoint_id is not None:
|
|
429
|
+
try:
|
|
430
|
+
resp_headers = dict(response.headers)
|
|
431
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
432
|
+
print(
|
|
433
|
+
f"[[aiohttp]] Captured response headers: {len(resp_headers)} headers",
|
|
434
|
+
log=False,
|
|
435
|
+
)
|
|
436
|
+
except Exception as e:
|
|
437
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
438
|
+
print(
|
|
439
|
+
f"[[aiohttp]] Failed to capture response headers: {e}",
|
|
440
|
+
log=False,
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# 7. Capture response body if enabled
|
|
444
|
+
resp_body = None
|
|
445
|
+
if SF_NETWORKHOP_CAPTURE_RESPONSE_BODY and endpoint_id is not None:
|
|
446
|
+
try:
|
|
447
|
+
# aiohttp Response has body or text attribute
|
|
448
|
+
if hasattr(response, "body") and response.body:
|
|
449
|
+
if isinstance(response.body, bytes):
|
|
450
|
+
resp_body = response.body[:_RESPONSE_LIMIT_BYTES]
|
|
451
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
452
|
+
print(
|
|
453
|
+
f"[[aiohttp]] Captured from body (bytes): {len(resp_body)} bytes",
|
|
454
|
+
log=False,
|
|
455
|
+
)
|
|
456
|
+
elif isinstance(response.body, str):
|
|
457
|
+
resp_body = response.body.encode("utf-8")[
|
|
458
|
+
:_RESPONSE_LIMIT_BYTES
|
|
459
|
+
]
|
|
460
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
461
|
+
print(
|
|
462
|
+
f"[[aiohttp]] Captured from body (str): {len(resp_body)} bytes",
|
|
463
|
+
log=False,
|
|
464
|
+
)
|
|
465
|
+
elif hasattr(response, "text") and response.text:
|
|
466
|
+
resp_body = response.text.encode("utf-8")[
|
|
467
|
+
:_RESPONSE_LIMIT_BYTES
|
|
468
|
+
]
|
|
469
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
470
|
+
print(
|
|
471
|
+
f"[[aiohttp]] Captured from text: {len(resp_body)} bytes",
|
|
472
|
+
log=False,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if SF_DEBUG and not resp_body:
|
|
476
|
+
print(f"[[aiohttp]] No response body captured", log=False)
|
|
477
|
+
except Exception as e:
|
|
478
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
479
|
+
print(
|
|
480
|
+
f"[[aiohttp]] Failed to capture response body: {e}",
|
|
481
|
+
log=False,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# 8. OTEL-STYLE: Emit network hop AFTER handler completes
|
|
485
|
+
if endpoint_id is not None and endpoint_id >= 0:
|
|
486
|
+
try:
|
|
487
|
+
_, session_id = get_or_set_sf_trace_id()
|
|
488
|
+
|
|
489
|
+
# Extract raw path and query string for C to parse
|
|
490
|
+
raw_path = str(request.path) # e.g., "/log"
|
|
491
|
+
raw_query = (
|
|
492
|
+
request.query_string.encode("utf-8")
|
|
493
|
+
if request.query_string
|
|
494
|
+
else b""
|
|
495
|
+
) # e.g., b"foo=5"
|
|
496
|
+
|
|
497
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
498
|
+
print(
|
|
499
|
+
f"[[aiohttp]] About to emit network hop: endpoint_id={endpoint_id}, "
|
|
500
|
+
f"req_headers={'present' if req_headers else 'None'}, "
|
|
501
|
+
f"req_body={len(req_body) if req_body else 0} bytes, "
|
|
502
|
+
f"resp_headers={'present' if resp_headers else 'None'}, "
|
|
503
|
+
f"resp_body={len(resp_body) if resp_body else 0} bytes",
|
|
504
|
+
log=False,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Direct C call - queues to background worker, returns instantly
|
|
508
|
+
# C will parse route and query_params from raw data
|
|
509
|
+
fast_send_network_hop_fast(
|
|
510
|
+
session_id=session_id,
|
|
511
|
+
endpoint_id=endpoint_id,
|
|
512
|
+
raw_path=raw_path,
|
|
513
|
+
raw_query_string=raw_query,
|
|
514
|
+
request_headers=req_headers,
|
|
515
|
+
request_body=req_body,
|
|
516
|
+
response_headers=resp_headers,
|
|
517
|
+
response_body=resp_body,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
521
|
+
print(
|
|
522
|
+
f"[[aiohttp]] Emitted network hop: endpoint_id={endpoint_id} "
|
|
523
|
+
f"session={session_id}",
|
|
524
|
+
log=False,
|
|
525
|
+
)
|
|
526
|
+
except Exception as e: # noqa: BLE001 S110
|
|
527
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
528
|
+
print(f"[[aiohttp]] Failed to emit network hop: {e}", log=False)
|
|
529
|
+
|
|
530
|
+
return response
|
|
531
|
+
finally:
|
|
532
|
+
# CRITICAL: Clear C TLS to prevent stale data in thread pools
|
|
533
|
+
# This runs even if handler raises exception!
|
|
534
|
+
clear_c_tls_parent_trace_id()
|
|
535
|
+
|
|
536
|
+
# CRITICAL: Clear outbound header base to prevent stale cached headers
|
|
537
|
+
# ContextVar does NOT automatically clean up in thread pools - must clear explicitly
|
|
538
|
+
clear_outbound_header_base()
|
|
539
|
+
|
|
540
|
+
# CRITICAL: Clear trace_id to ensure fresh generation for next request
|
|
541
|
+
# Without this, get_or_set_sf_trace_id() reuses trace_id from previous request
|
|
542
|
+
# causing X-Sf4-Prid to stay constant when no incoming X-Sf3-Rid header
|
|
543
|
+
clear_trace_id()
|
|
544
|
+
|
|
545
|
+
# CRITICAL: Clear current request path to prevent stale data in thread pools
|
|
546
|
+
clear_current_request_path()
|
|
547
|
+
|
|
548
|
+
# Clear function span override for this request (thread-local cleanup)
|
|
549
|
+
try:
|
|
550
|
+
_sffuncspan_config.clear_thread_override()
|
|
551
|
+
except Exception:
|
|
552
|
+
pass
|
|
553
|
+
|
|
554
|
+
# ===========================================================
|
|
555
|
+
# 2 | Patch Application.__init__ to insert middleware
|
|
556
|
+
# ===========================================================
|
|
557
|
+
original_init = web.Application.__init__
|
|
558
|
+
|
|
559
|
+
def patched_init(self, *args, middlewares=None, **kwargs):
|
|
560
|
+
mlist = list(middlewares or [])
|
|
561
|
+
mlist.insert(0, _sf_tracing_middleware) # prepend → runs first
|
|
562
|
+
original_init(self, *args, middlewares=mlist, **kwargs)
|
|
563
|
+
_patch_router(self.router) # apply once per app
|
|
564
|
+
|
|
565
|
+
# Try to register endpoints immediately if routes are already defined
|
|
566
|
+
if (
|
|
567
|
+
hasattr(self, "router")
|
|
568
|
+
and hasattr(self.router, "_resources")
|
|
569
|
+
and self.router._resources
|
|
570
|
+
):
|
|
571
|
+
try:
|
|
572
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
573
|
+
print(
|
|
574
|
+
f"[[patch_aiohttp]] Routes already defined, registering immediately",
|
|
575
|
+
log=False,
|
|
576
|
+
)
|
|
577
|
+
_pre_register_endpoints(self, routes_to_skip)
|
|
578
|
+
except Exception as e:
|
|
579
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
580
|
+
print(
|
|
581
|
+
f"[[patch_aiohttp]] Immediate registration failed: {e}",
|
|
582
|
+
log=False,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# Register on_startup hook as a fallback (for routes added after __init__)
|
|
586
|
+
async def _sf_startup(app):
|
|
587
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
588
|
+
print(
|
|
589
|
+
"[[patch_aiohttp]] Startup event fired, registering endpoints",
|
|
590
|
+
log=False,
|
|
591
|
+
)
|
|
592
|
+
_pre_register_endpoints(app, routes_to_skip)
|
|
593
|
+
|
|
594
|
+
self.on_startup.append(_sf_startup)
|
|
595
|
+
|
|
596
|
+
web.Application.__init__ = patched_init
|
|
597
|
+
|
|
598
|
+
# ===========================================================
|
|
599
|
+
# 3 | Patch router.add_route / add_routes for future calls
|
|
600
|
+
# ===========================================================
|
|
601
|
+
def _patch_router(router):
|
|
602
|
+
if getattr(router, "_sf_tracing_patched", False):
|
|
603
|
+
return # already done
|
|
604
|
+
|
|
605
|
+
orig_add_route = router.add_route
|
|
606
|
+
orig_add_routes = router.add_routes
|
|
607
|
+
|
|
608
|
+
def _wrap_and_add(method, path, handler, *a, **kw): # noqa: ANN001
|
|
609
|
+
return orig_add_route(method, path, _wrap_handler(handler), *a, **kw)
|
|
610
|
+
|
|
611
|
+
def _wrap_handler(h):
|
|
612
|
+
# strawberry skip & user-code check happen in middleware,
|
|
613
|
+
# but wrapping here avoids duplicate stack frames
|
|
614
|
+
return _unwrap_user_func(h) or h
|
|
615
|
+
|
|
616
|
+
def _new_add_routes(routes):
|
|
617
|
+
wrapped = [
|
|
618
|
+
(
|
|
619
|
+
(m, p, _wrap_handler(h), *rest) # route is (method,path,handler,…)
|
|
620
|
+
if len(r) >= 3
|
|
621
|
+
else r
|
|
622
|
+
)
|
|
623
|
+
for r in routes
|
|
624
|
+
for (m, p, h, *rest) in (r,) # unpack safely
|
|
625
|
+
]
|
|
626
|
+
return orig_add_routes(wrapped)
|
|
627
|
+
|
|
628
|
+
router.add_route = _wrap_and_add
|
|
629
|
+
router.add_routes = _new_add_routes
|
|
630
|
+
router._sf_tracing_patched = True
|
|
631
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
632
|
+
print("[[patch_aiohttp]] router hooks installed", log=False)
|
|
633
|
+
|
|
634
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
635
|
+
print("[[patch_aiohttp]] OTEL-style middleware + init patch applied", log=False)
|
|
636
|
+
|
|
637
|
+
# ===========================================================
|
|
638
|
+
# 4 | Patch aiohttp-cors if installed
|
|
639
|
+
# ===========================================================
|
|
640
|
+
def patch_aiohttp_cors():
|
|
641
|
+
"""
|
|
642
|
+
Patch aiohttp-cors to automatically inject Sailfish headers.
|
|
643
|
+
|
|
644
|
+
SAFE: Only modifies CORS if aiohttp-cors is installed and used.
|
|
645
|
+
"""
|
|
646
|
+
try:
|
|
647
|
+
import aiohttp_cors
|
|
648
|
+
except ImportError:
|
|
649
|
+
# aiohttp-cors not installed, skip patching
|
|
650
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
651
|
+
print(
|
|
652
|
+
"[[patch_aiohttp_cors]] aiohttp-cors not installed, skipping",
|
|
653
|
+
log=False,
|
|
654
|
+
)
|
|
655
|
+
return
|
|
656
|
+
|
|
657
|
+
# Check if already patched
|
|
658
|
+
if hasattr(aiohttp_cors.CorsConfig, "_sf_cors_patched"):
|
|
659
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
660
|
+
print("[[patch_aiohttp_cors]] Already patched, skipping", log=False)
|
|
661
|
+
return
|
|
662
|
+
|
|
663
|
+
# Patch CorsConfig.__init__ to intercept defaults parameter
|
|
664
|
+
original_init = aiohttp_cors.CorsConfig.__init__
|
|
665
|
+
|
|
666
|
+
def patched_init(self, app, *, defaults=None, router_adapter=None):
|
|
667
|
+
# Intercept and modify defaults parameter
|
|
668
|
+
if defaults:
|
|
669
|
+
modified_defaults = {}
|
|
670
|
+
for origin, resource_options in defaults.items():
|
|
671
|
+
# Handle both ResourceOptions objects and dicts
|
|
672
|
+
if isinstance(resource_options, aiohttp_cors.ResourceOptions):
|
|
673
|
+
# ResourceOptions object - access allow_headers attribute
|
|
674
|
+
if hasattr(resource_options, "allow_headers"):
|
|
675
|
+
original_headers = resource_options.allow_headers
|
|
676
|
+
if should_inject_headers(original_headers):
|
|
677
|
+
# Create new ResourceOptions with modified headers
|
|
678
|
+
# Convert frozenset to list for allow_methods and expose_headers
|
|
679
|
+
allow_methods = resource_options.allow_methods
|
|
680
|
+
if isinstance(allow_methods, frozenset):
|
|
681
|
+
allow_methods = list(allow_methods)
|
|
682
|
+
|
|
683
|
+
expose_headers = resource_options.expose_headers
|
|
684
|
+
if isinstance(expose_headers, frozenset):
|
|
685
|
+
expose_headers = list(expose_headers)
|
|
686
|
+
|
|
687
|
+
modified_defaults[origin] = aiohttp_cors.ResourceOptions(
|
|
688
|
+
allow_credentials=resource_options.allow_credentials,
|
|
689
|
+
expose_headers=expose_headers,
|
|
690
|
+
allow_headers=inject_sailfish_headers(original_headers),
|
|
691
|
+
allow_methods=allow_methods,
|
|
692
|
+
max_age=resource_options.max_age,
|
|
693
|
+
)
|
|
694
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
695
|
+
print(
|
|
696
|
+
f"[[patch_aiohttp_cors]] Injected Sailfish headers into defaults for origin {origin}",
|
|
697
|
+
log=False,
|
|
698
|
+
)
|
|
699
|
+
else:
|
|
700
|
+
modified_defaults[origin] = resource_options
|
|
701
|
+
else:
|
|
702
|
+
modified_defaults[origin] = resource_options
|
|
703
|
+
elif isinstance(resource_options, dict) and "allow_headers" in resource_options:
|
|
704
|
+
# Dict config - modify directly
|
|
705
|
+
original_headers = resource_options["allow_headers"]
|
|
706
|
+
if should_inject_headers(original_headers):
|
|
707
|
+
modified_config = resource_options.copy()
|
|
708
|
+
modified_config["allow_headers"] = inject_sailfish_headers(original_headers)
|
|
709
|
+
modified_defaults[origin] = modified_config
|
|
710
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
711
|
+
print(
|
|
712
|
+
f"[[patch_aiohttp_cors]] Injected Sailfish headers into defaults dict for origin {origin}",
|
|
713
|
+
log=False,
|
|
714
|
+
)
|
|
715
|
+
else:
|
|
716
|
+
modified_defaults[origin] = resource_options
|
|
717
|
+
else:
|
|
718
|
+
modified_defaults[origin] = resource_options
|
|
719
|
+
|
|
720
|
+
defaults = modified_defaults
|
|
721
|
+
|
|
722
|
+
# Call original init with modified defaults
|
|
723
|
+
original_init(self, app, defaults=defaults, router_adapter=router_adapter)
|
|
724
|
+
|
|
725
|
+
aiohttp_cors.CorsConfig.__init__ = patched_init
|
|
726
|
+
|
|
727
|
+
# Patch CorsConfig.add method (for route-specific overrides)
|
|
728
|
+
original_add = aiohttp_cors.CorsConfig.add
|
|
729
|
+
|
|
730
|
+
def patched_add(self, route, config=None):
|
|
731
|
+
# Intercept the config and modify allow_headers
|
|
732
|
+
if config:
|
|
733
|
+
modified_config = {}
|
|
734
|
+
for origin, resource_config in config.items():
|
|
735
|
+
if isinstance(resource_config, aiohttp_cors.ResourceOptions):
|
|
736
|
+
# ResourceOptions object
|
|
737
|
+
if hasattr(resource_config, "allow_headers"):
|
|
738
|
+
original_headers = resource_config.allow_headers
|
|
739
|
+
if should_inject_headers(original_headers):
|
|
740
|
+
# Convert frozenset to list for allow_methods and expose_headers
|
|
741
|
+
allow_methods = resource_config.allow_methods
|
|
742
|
+
if isinstance(allow_methods, frozenset):
|
|
743
|
+
allow_methods = list(allow_methods)
|
|
744
|
+
|
|
745
|
+
expose_headers = resource_config.expose_headers
|
|
746
|
+
if isinstance(expose_headers, frozenset):
|
|
747
|
+
expose_headers = list(expose_headers)
|
|
748
|
+
|
|
749
|
+
modified_config[origin] = aiohttp_cors.ResourceOptions(
|
|
750
|
+
allow_credentials=resource_config.allow_credentials,
|
|
751
|
+
expose_headers=expose_headers,
|
|
752
|
+
allow_headers=inject_sailfish_headers(original_headers),
|
|
753
|
+
allow_methods=allow_methods,
|
|
754
|
+
max_age=resource_config.max_age,
|
|
755
|
+
)
|
|
756
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
757
|
+
print(
|
|
758
|
+
"[[patch_aiohttp_cors]] Injected Sailfish headers into CorsConfig.add()",
|
|
759
|
+
log=False,
|
|
760
|
+
)
|
|
761
|
+
else:
|
|
762
|
+
modified_config[origin] = resource_config
|
|
763
|
+
else:
|
|
764
|
+
modified_config[origin] = resource_config
|
|
765
|
+
elif isinstance(resource_config, dict) and "allow_headers" in resource_config:
|
|
766
|
+
original_headers = resource_config["allow_headers"]
|
|
767
|
+
if should_inject_headers(original_headers):
|
|
768
|
+
modified = resource_config.copy()
|
|
769
|
+
modified["allow_headers"] = inject_sailfish_headers(original_headers)
|
|
770
|
+
modified_config[origin] = modified
|
|
771
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
772
|
+
print(
|
|
773
|
+
"[[patch_aiohttp_cors]] Injected Sailfish headers into CorsConfig.add()",
|
|
774
|
+
log=False,
|
|
775
|
+
)
|
|
776
|
+
else:
|
|
777
|
+
modified_config[origin] = resource_config
|
|
778
|
+
else:
|
|
779
|
+
modified_config[origin] = resource_config
|
|
780
|
+
|
|
781
|
+
config = modified_config
|
|
782
|
+
|
|
783
|
+
# Call original add
|
|
784
|
+
return original_add(self, route, config)
|
|
785
|
+
|
|
786
|
+
aiohttp_cors.CorsConfig.add = patched_add
|
|
787
|
+
aiohttp_cors.CorsConfig._sf_cors_patched = True
|
|
788
|
+
|
|
789
|
+
if SF_DEBUG and app_config._interceptors_initialized:
|
|
790
|
+
print("[[patch_aiohttp_cors]] Successfully patched aiohttp-cors", log=False)
|
|
791
|
+
|
|
792
|
+
# Call CORS patching
|
|
793
|
+
patch_aiohttp_cors()
|