httpdbg 1.1.1__tar.gz → 1.2.0__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.
- {httpdbg-1.1.1 → httpdbg-1.2.0}/PKG-INFO +6 -6
- {httpdbg-1.1.1 → httpdbg-1.2.0}/README.md +5 -5
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/__init__.py +1 -1
- httpdbg-1.2.0/httpdbg/env.py +4 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/all.py +19 -10
- httpdbg-1.2.0/httpdbg/hooks/fastapi.py +156 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/flask.py +29 -9
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/pytest.py +4 -2
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/socket.py +66 -40
- httpdbg-1.2.0/httpdbg/hooks/starlette.py +58 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/unittest.py +3 -3
- httpdbg-1.2.0/httpdbg/hooks/uvicorn.py +129 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/initiator.py +45 -72
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/records.py +30 -11
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/api.py +1 -1
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg.egg-info/PKG-INFO +6 -6
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg.egg-info/SOURCES.txt +6 -2
- httpdbg-1.2.0/tests/test_server_hook_fastapi.py +115 -0
- httpdbg-1.1.1/tests/test_hook_flask.py → httpdbg-1.2.0/tests/test_server_hook_flask.py +8 -2
- httpdbg-1.1.1/httpdbg/env.py +0 -7
- {httpdbg-1.1.1 → httpdbg-1.2.0}/LICENSE +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/__main__.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/args.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/exception.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/aiohttp.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/external.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/http.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/httpx.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/requests.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/template_sitecustomize.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/urllib3.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/hooks/utils.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/log.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/mode_console.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/mode_module.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/mode_script.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/preview.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/server.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/utils.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/app.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/api.js +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/index.htm +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/render.js +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/style.css +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg/webapp/static/table.css +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/pyproject.toml +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/setup.cfg +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_api.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_cli.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_context_manager.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_hook_httpx.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_hook_pytest.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_hook_requests.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_hook_unittest.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_initiator.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_mode_console.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_mode_script.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_multiprocess.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_preview.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_pyhttpdbg_server.py +0 -0
- {httpdbg-1.1.1 → httpdbg-1.2.0}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A very simple tool to debug HTTP(S) client and server requests.
|
|
5
5
|
Author-email: cle-b <cle@tictac.pm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -114,7 +114,6 @@ Press CTRL+C to quit
|
|
|
114
114
|
|
|
115
115
|

|
|
116
116
|
|
|
117
|
-
This feature is new and not fully implemented. See [limitations](#limitations) for more details.
|
|
118
117
|
|
|
119
118
|
## initiators and groups
|
|
120
119
|
|
|
@@ -146,7 +145,8 @@ The initiator is the low-level socket method that receives the data. The group i
|
|
|
146
145
|
|
|
147
146
|
| packages | status | initiator | group |
|
|
148
147
|
|----------------|-------------------------------------|-----------|-----------|
|
|
149
|
-
| flask | supported |
|
|
148
|
+
| flask | supported | - | yes |
|
|
149
|
+
| fastapi | supported | - | yes |
|
|
150
150
|
|
|
151
151
|
### supported test framework
|
|
152
152
|
|
|
@@ -154,8 +154,8 @@ The requests are grouped by tests.
|
|
|
154
154
|
|
|
155
155
|
| packages | status | initiator | group |
|
|
156
156
|
|----------------|-------------------------------------|-----------|-----------|
|
|
157
|
-
| pytest | supported |
|
|
158
|
-
| unittest | supported |
|
|
157
|
+
| pytest | supported | - | yes |
|
|
158
|
+
| unittest | supported | - | yes |
|
|
159
159
|
|
|
160
160
|
## configuration
|
|
161
161
|
|
|
@@ -218,7 +218,7 @@ The requests:
|
|
|
218
218
|
|
|
219
219
|
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
220
220
|
|
|
221
|
-
Support for recording requests on the server side is new in `httpdbg 1.0.0
|
|
221
|
+
Support for recording requests on the server side is new in `httpdbg 1.0.0`. In some cases, like with |FastAPI| using |uvloop|, we had to implement a new mechanism to make request recording possible. This is still a work in progress as we aim to support more frameworks.
|
|
222
222
|
|
|
223
223
|
## documentation
|
|
224
224
|
|
|
@@ -87,7 +87,6 @@ Press CTRL+C to quit
|
|
|
87
87
|
|
|
88
88
|

|
|
89
89
|
|
|
90
|
-
This feature is new and not fully implemented. See [limitations](#limitations) for more details.
|
|
91
90
|
|
|
92
91
|
## initiators and groups
|
|
93
92
|
|
|
@@ -119,7 +118,8 @@ The initiator is the low-level socket method that receives the data. The group i
|
|
|
119
118
|
|
|
120
119
|
| packages | status | initiator | group |
|
|
121
120
|
|----------------|-------------------------------------|-----------|-----------|
|
|
122
|
-
| flask | supported |
|
|
121
|
+
| flask | supported | - | yes |
|
|
122
|
+
| fastapi | supported | - | yes |
|
|
123
123
|
|
|
124
124
|
### supported test framework
|
|
125
125
|
|
|
@@ -127,8 +127,8 @@ The requests are grouped by tests.
|
|
|
127
127
|
|
|
128
128
|
| packages | status | initiator | group |
|
|
129
129
|
|----------------|-------------------------------------|-----------|-----------|
|
|
130
|
-
| pytest | supported |
|
|
131
|
-
| unittest | supported |
|
|
130
|
+
| pytest | supported | - | yes |
|
|
131
|
+
| unittest | supported | - | yes |
|
|
132
132
|
|
|
133
133
|
## configuration
|
|
134
134
|
|
|
@@ -191,7 +191,7 @@ The requests:
|
|
|
191
191
|
|
|
192
192
|
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
193
193
|
|
|
194
|
-
Support for recording requests on the server side is new in `httpdbg 1.0.0
|
|
194
|
+
Support for recording requests on the server side is new in `httpdbg 1.0.0`. In some cases, like with |FastAPI| using |uvloop|, we had to implement a new mechanism to make request recording possible. This is still a work in progress as we aim to support more frameworks.
|
|
195
195
|
|
|
196
196
|
## documentation
|
|
197
197
|
|
|
@@ -7,15 +7,19 @@ from typing import Union
|
|
|
7
7
|
|
|
8
8
|
from httpdbg.hooks.aiohttp import hook_aiohttp
|
|
9
9
|
from httpdbg.hooks.external import watcher_external
|
|
10
|
+
from httpdbg.hooks.fastapi import hook_fastapi
|
|
11
|
+
from httpdbg.hooks.flask import hook_flask
|
|
10
12
|
from httpdbg.hooks.generic import hook_generic
|
|
11
13
|
from httpdbg.hooks.http import hook_http
|
|
12
14
|
from httpdbg.hooks.httpx import hook_httpx
|
|
13
15
|
from httpdbg.hooks.pytest import hook_pytest
|
|
14
16
|
from httpdbg.hooks.requests import hook_requests
|
|
15
17
|
from httpdbg.hooks.socket import hook_socket
|
|
18
|
+
from httpdbg.hooks.starlette import hook_starlette
|
|
16
19
|
from httpdbg.hooks.unittest import hook_unittest
|
|
17
20
|
from httpdbg.hooks.urllib3 import hook_urllib3
|
|
18
|
-
from httpdbg.hooks.
|
|
21
|
+
from httpdbg.hooks.uvicorn import hook_uvicorn
|
|
22
|
+
|
|
19
23
|
from httpdbg.records import HTTPRecords
|
|
20
24
|
|
|
21
25
|
|
|
@@ -33,12 +37,17 @@ def httprecord(
|
|
|
33
37
|
with watcher_external(records, initiators, server):
|
|
34
38
|
with hook_flask(records):
|
|
35
39
|
with hook_socket(records):
|
|
36
|
-
with
|
|
37
|
-
with
|
|
38
|
-
with
|
|
39
|
-
with
|
|
40
|
-
with
|
|
41
|
-
with
|
|
42
|
-
with
|
|
43
|
-
with
|
|
44
|
-
|
|
40
|
+
with hook_fastapi(records):
|
|
41
|
+
with hook_starlette(records):
|
|
42
|
+
with hook_uvicorn(records):
|
|
43
|
+
with hook_http(records):
|
|
44
|
+
with hook_httpx(records):
|
|
45
|
+
with hook_requests(records):
|
|
46
|
+
with hook_urllib3(records):
|
|
47
|
+
with hook_aiohttp(records):
|
|
48
|
+
with hook_pytest(records):
|
|
49
|
+
with hook_unittest(records):
|
|
50
|
+
with hook_generic(
|
|
51
|
+
records, initiators
|
|
52
|
+
):
|
|
53
|
+
yield records
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
import functools
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from typing import Dict
|
|
7
|
+
from typing import Generator
|
|
8
|
+
from typing import Union
|
|
9
|
+
|
|
10
|
+
import datetime
|
|
11
|
+
|
|
12
|
+
from httpdbg.hooks.utils import decorate
|
|
13
|
+
from httpdbg.hooks.utils import getcallargs
|
|
14
|
+
from httpdbg.hooks.utils import undecorate
|
|
15
|
+
from httpdbg.initiator import httpdbg_endpoint
|
|
16
|
+
from httpdbg.records import HTTPRecords
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# decorate the function decorated by @app.get; @app.post etc.
|
|
20
|
+
def set_hook_fastapi_endpoint(records: HTTPRecords, method: Callable):
|
|
21
|
+
|
|
22
|
+
@wraps(method)
|
|
23
|
+
def hook(*args, **kwargs):
|
|
24
|
+
|
|
25
|
+
with httpdbg_endpoint(
|
|
26
|
+
records,
|
|
27
|
+
method,
|
|
28
|
+
*args,
|
|
29
|
+
**kwargs,
|
|
30
|
+
):
|
|
31
|
+
ret = method(*args, **kwargs)
|
|
32
|
+
return ret
|
|
33
|
+
|
|
34
|
+
return hook
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_hook_fastapi_apirouter_add_api_route(
|
|
38
|
+
records: HTTPRecords,
|
|
39
|
+
method: Callable,
|
|
40
|
+
already_mapped: Union[Dict[Callable, Callable], None] = None,
|
|
41
|
+
):
|
|
42
|
+
|
|
43
|
+
@wraps(method)
|
|
44
|
+
def hook(*args, **kwargs):
|
|
45
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
46
|
+
|
|
47
|
+
param = "endpoint"
|
|
48
|
+
|
|
49
|
+
if param in callargs:
|
|
50
|
+
original_view_func = callargs[param]
|
|
51
|
+
if original_view_func not in already_mapped:
|
|
52
|
+
callargs[param] = decorate(
|
|
53
|
+
records, callargs[param], set_hook_fastapi_endpoint
|
|
54
|
+
)
|
|
55
|
+
already_mapped[original_view_func] = callargs[param]
|
|
56
|
+
else:
|
|
57
|
+
callargs[param] = already_mapped[original_view_func]
|
|
58
|
+
|
|
59
|
+
args = [callargs[param] if x == original_view_func else x for x in args]
|
|
60
|
+
if param in kwargs:
|
|
61
|
+
kwargs[param] = callargs[param]
|
|
62
|
+
|
|
63
|
+
return method(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
return hook
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# decorate the "app" returned by fastapi.routing.get_request_handler to link the socketdata and the endpoint group
|
|
69
|
+
def set_hook_fastapi_app(records: HTTPRecords, method: Callable):
|
|
70
|
+
|
|
71
|
+
@wraps(method)
|
|
72
|
+
async def hook(*args, **kwargs):
|
|
73
|
+
|
|
74
|
+
socketdata_id = None
|
|
75
|
+
|
|
76
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
77
|
+
|
|
78
|
+
if "request" in callargs:
|
|
79
|
+
if hasattr(callargs["request"], "__httpdbg_socketdata_id"):
|
|
80
|
+
socketdata_id = callargs["request"].__httpdbg_socketdata_id
|
|
81
|
+
|
|
82
|
+
with httpdbg_endpoint(
|
|
83
|
+
records,
|
|
84
|
+
method,
|
|
85
|
+
*args,
|
|
86
|
+
**kwargs,
|
|
87
|
+
) as group:
|
|
88
|
+
if group and (group.id in records.groups):
|
|
89
|
+
if socketdata_id and (socketdata_id in records._sockets):
|
|
90
|
+
records.groups[group.id].tbegin = records._sockets[
|
|
91
|
+
socketdata_id
|
|
92
|
+
].record.tbegin - datetime.timedelta(milliseconds=1)
|
|
93
|
+
records._sockets[socketdata_id].record.group_id = group.id
|
|
94
|
+
|
|
95
|
+
ret = await method(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
return ret
|
|
98
|
+
|
|
99
|
+
return hook
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def set_hook_fastapi_routing_get_request_handler(
|
|
103
|
+
records: HTTPRecords, method: Callable
|
|
104
|
+
):
|
|
105
|
+
|
|
106
|
+
@wraps(method)
|
|
107
|
+
def hook(*args, **kwargs):
|
|
108
|
+
|
|
109
|
+
app = method(*args, **kwargs)
|
|
110
|
+
|
|
111
|
+
app = decorate(records, app, set_hook_fastapi_app)
|
|
112
|
+
|
|
113
|
+
return app
|
|
114
|
+
|
|
115
|
+
return hook
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@contextmanager
|
|
119
|
+
def hook_fastapi(records: HTTPRecords) -> Generator[None, None, None]:
|
|
120
|
+
hooks = False
|
|
121
|
+
try:
|
|
122
|
+
import fastapi.routing
|
|
123
|
+
|
|
124
|
+
already_mapped: Dict[Callable, Callable] = {}
|
|
125
|
+
|
|
126
|
+
set_hook_fastapi_apirouter_add_api_route_with_already_mapped = (
|
|
127
|
+
functools.partial(
|
|
128
|
+
set_hook_fastapi_apirouter_add_api_route, already_mapped=already_mapped
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
fastapi.routing.APIRouter.add_api_route = decorate(
|
|
133
|
+
records,
|
|
134
|
+
fastapi.routing.APIRouter.add_api_route,
|
|
135
|
+
set_hook_fastapi_apirouter_add_api_route_with_already_mapped,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
fastapi.routing.get_request_handler = decorate(
|
|
139
|
+
records,
|
|
140
|
+
fastapi.routing.get_request_handler,
|
|
141
|
+
set_hook_fastapi_routing_get_request_handler,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
hooks = True
|
|
145
|
+
except ImportError:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
yield
|
|
149
|
+
|
|
150
|
+
if hooks:
|
|
151
|
+
fastapi.routing.APIRouter.add_api_route = undecorate(
|
|
152
|
+
fastapi.routing.APIRouter.add_api_route
|
|
153
|
+
)
|
|
154
|
+
fastapi.routing.get_request_handler = undecorate(
|
|
155
|
+
fastapi.routing.get_request_handler
|
|
156
|
+
)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from contextlib import contextmanager
|
|
4
|
+
import functools
|
|
4
5
|
from functools import wraps
|
|
6
|
+
from typing import Dict
|
|
5
7
|
from typing import Generator
|
|
8
|
+
from typing import Union
|
|
6
9
|
|
|
7
10
|
from httpdbg.hooks.utils import getcallargs
|
|
8
11
|
from httpdbg.hooks.utils import decorate
|
|
@@ -30,12 +33,11 @@ def set_hook_flask_endpoint(records: HTTPRecords, method: Callable):
|
|
|
30
33
|
return hook
|
|
31
34
|
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def set_hook_flask_add_url_rule(records: HTTPRecords, method: Callable):
|
|
36
|
+
def set_hook_flask_add_url_rule(
|
|
37
|
+
records: HTTPRecords,
|
|
38
|
+
method: Callable,
|
|
39
|
+
already_mapped: Union[Dict[Callable, Callable], None] = None,
|
|
40
|
+
):
|
|
39
41
|
|
|
40
42
|
def hook(*args, **kwargs):
|
|
41
43
|
|
|
@@ -62,7 +64,11 @@ def set_hook_flask_add_url_rule(records: HTTPRecords, method: Callable):
|
|
|
62
64
|
return hook
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
def set_hook_flask_register_error_handler(
|
|
67
|
+
def set_hook_flask_register_error_handler(
|
|
68
|
+
records: HTTPRecords,
|
|
69
|
+
method: Callable,
|
|
70
|
+
already_mapped: Union[Dict[Callable, Callable], None] = None,
|
|
71
|
+
):
|
|
66
72
|
|
|
67
73
|
def hook(*args, **kwargs):
|
|
68
74
|
|
|
@@ -95,14 +101,28 @@ def hook_flask(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
95
101
|
try:
|
|
96
102
|
import flask
|
|
97
103
|
|
|
104
|
+
# we must not apply the hook more than once on a mapped endpoint function
|
|
105
|
+
# AssertionError: View function mapping is overwriting an existing endpoint function: xxxx
|
|
106
|
+
already_mapped: Dict[Callable, Callable] = {}
|
|
107
|
+
|
|
108
|
+
set_hook_flask_add_url_rule_with_already_mapped = functools.partial(
|
|
109
|
+
set_hook_flask_add_url_rule, already_mapped=already_mapped
|
|
110
|
+
)
|
|
111
|
+
|
|
98
112
|
flask.Flask.add_url_rule = decorate(
|
|
99
|
-
records,
|
|
113
|
+
records,
|
|
114
|
+
flask.Flask.add_url_rule,
|
|
115
|
+
set_hook_flask_add_url_rule_with_already_mapped,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
set_hook_flask_register_error_handler_with_already_mapped = functools.partial(
|
|
119
|
+
set_hook_flask_register_error_handler, already_mapped=already_mapped
|
|
100
120
|
)
|
|
101
121
|
|
|
102
122
|
flask.Flask.register_error_handler = decorate(
|
|
103
123
|
records,
|
|
104
124
|
flask.Flask.register_error_handler,
|
|
105
|
-
|
|
125
|
+
set_hook_flask_register_error_handler_with_already_mapped,
|
|
106
126
|
)
|
|
107
127
|
flask.Flask.handle_exception = decorate(
|
|
108
128
|
records, flask.Flask.handle_exception, set_hook_flask_endpoint
|
|
@@ -12,13 +12,15 @@ from httpdbg.log import logger
|
|
|
12
12
|
from httpdbg.records import HTTPRecords
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def set_hook_for_pytest_fixture(
|
|
15
|
+
def set_hook_for_pytest_fixture(records: HTTPRecords, method: Callable):
|
|
16
16
|
def hook(*args, **kwargs):
|
|
17
17
|
logger().info("SET_HOOK_FOR_PYTEST_FIXTURE")
|
|
18
18
|
callargs = getcallargs(method, *args, **kwargs)
|
|
19
19
|
|
|
20
20
|
if "fixturefunc" in callargs:
|
|
21
|
-
with httpdbg_tag(
|
|
21
|
+
with httpdbg_tag(
|
|
22
|
+
records, getattr(callargs["fixturefunc"], "__name__", "fixture")
|
|
23
|
+
):
|
|
22
24
|
return method(*args, **kwargs)
|
|
23
25
|
else:
|
|
24
26
|
return method(*args, **kwargs)
|
|
@@ -186,46 +186,53 @@ def set_hook_for_socket_recv_into(records: HTTPRecords, method: Callable):
|
|
|
186
186
|
|
|
187
187
|
nbytes = method(self, buffer, *args, **kwargs)
|
|
188
188
|
|
|
189
|
-
if
|
|
190
|
-
if socketdata
|
|
191
|
-
|
|
192
|
-
f"RECV_INTO (after) - id={id(self)} buffer={(b''+buffer)[:20]}"
|
|
193
|
-
)
|
|
194
|
-
socketdata.record.receive_data(buffer[:nbytes])
|
|
195
|
-
else:
|
|
196
|
-
socketdata.rawdata += buffer[:nbytes]
|
|
197
|
-
http_detected = socketdata.http_detected()
|
|
198
|
-
if http_detected:
|
|
199
|
-
logger().info("RECV_INTO - http detected")
|
|
189
|
+
if buffer: # it appears that the buffer may be None (observed on Windows).
|
|
190
|
+
if socketdata:
|
|
191
|
+
if socketdata.record:
|
|
200
192
|
logger().info(
|
|
201
193
|
f"RECV_INTO (after) - id={id(self)} buffer={(b''+buffer)[:20]}"
|
|
202
194
|
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
) as initiator_and_group:
|
|
212
|
-
initiator, group, is_new = initiator_and_group
|
|
213
|
-
if is_new:
|
|
214
|
-
tbegin = socketdata.tbegin - datetime.timedelta(
|
|
215
|
-
milliseconds=1
|
|
216
|
-
)
|
|
217
|
-
initiator.tbegin = tbegin
|
|
218
|
-
group.tbegin = tbegin
|
|
219
|
-
socketdata.record = HTTPRecord(
|
|
220
|
-
tbegin=socketdata.tbegin, is_client=False
|
|
195
|
+
socketdata.record.receive_data(buffer[:nbytes])
|
|
196
|
+
else:
|
|
197
|
+
socketdata.rawdata += buffer[:nbytes]
|
|
198
|
+
http_detected = socketdata.http_detected()
|
|
199
|
+
if http_detected:
|
|
200
|
+
logger().info("RECV_INTO - http detected")
|
|
201
|
+
logger().info(
|
|
202
|
+
f"RECV_INTO (after) - id={id(self)} buffer={(b''+buffer)[:20]}"
|
|
221
203
|
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
204
|
+
with httpdbg_initiator(
|
|
205
|
+
records,
|
|
206
|
+
traceback.extract_stack(),
|
|
207
|
+
method,
|
|
208
|
+
self,
|
|
209
|
+
buffer,
|
|
210
|
+
*args,
|
|
211
|
+
**kwargs,
|
|
212
|
+
) as initiator_and_group:
|
|
213
|
+
initiator, group, is_new = initiator_and_group
|
|
214
|
+
if is_new:
|
|
215
|
+
tbegin = socketdata.tbegin - datetime.timedelta(
|
|
216
|
+
milliseconds=1
|
|
217
|
+
)
|
|
218
|
+
initiator.tbegin = tbegin
|
|
219
|
+
group.tbegin = tbegin
|
|
220
|
+
socketdata.record = HTTPRecord(
|
|
221
|
+
records.current_initiator,
|
|
222
|
+
records.current_group,
|
|
223
|
+
records.current_tag,
|
|
224
|
+
tbegin=socketdata.tbegin,
|
|
225
|
+
is_client=False,
|
|
226
|
+
)
|
|
227
|
+
socketdata.record.address = socketdata.address
|
|
228
|
+
socketdata.record.ssl = socketdata.ssl
|
|
229
|
+
socketdata.record.receive_data(socketdata.rawdata)
|
|
230
|
+
if records.server:
|
|
231
|
+
records.requests[socketdata.record.id] = (
|
|
232
|
+
socketdata.record
|
|
233
|
+
)
|
|
234
|
+
elif http_detected is False: # if None, there is nothing to do
|
|
235
|
+
records._sockets[id(self)] = None
|
|
229
236
|
|
|
230
237
|
return nbytes
|
|
231
238
|
|
|
@@ -270,7 +277,11 @@ def set_hook_for_socket_recv(records: HTTPRecords, method: Callable):
|
|
|
270
277
|
initiator.tbegin = tbegin
|
|
271
278
|
group.tbegin = tbegin
|
|
272
279
|
socketdata.record = HTTPRecord(
|
|
273
|
-
|
|
280
|
+
records.current_initiator,
|
|
281
|
+
records.current_group,
|
|
282
|
+
records.current_tag,
|
|
283
|
+
tbegin=socketdata.tbegin,
|
|
284
|
+
is_client=False,
|
|
274
285
|
)
|
|
275
286
|
socketdata.record.address = socketdata.address
|
|
276
287
|
socketdata.record.ssl = socketdata.ssl
|
|
@@ -320,7 +331,12 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
|
|
|
320
331
|
)
|
|
321
332
|
initiator.tbegin = tbegin
|
|
322
333
|
group.tbegin = tbegin
|
|
323
|
-
socketdata.record = HTTPRecord(
|
|
334
|
+
socketdata.record = HTTPRecord(
|
|
335
|
+
records.current_initiator,
|
|
336
|
+
records.current_group,
|
|
337
|
+
records.current_tag,
|
|
338
|
+
tbegin=socketdata.tbegin,
|
|
339
|
+
)
|
|
324
340
|
socketdata.record.address = socketdata.address
|
|
325
341
|
socketdata.record.ssl = socketdata.ssl
|
|
326
342
|
socketdata.record.send_data(socketdata.rawdata)
|
|
@@ -371,7 +387,12 @@ def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
|
|
|
371
387
|
)
|
|
372
388
|
initiator.tbegin = tbegin
|
|
373
389
|
group.tbegin = tbegin
|
|
374
|
-
socketdata.record = HTTPRecord(
|
|
390
|
+
socketdata.record = HTTPRecord(
|
|
391
|
+
records.current_initiator,
|
|
392
|
+
records.current_group,
|
|
393
|
+
records.current_tag,
|
|
394
|
+
tbegin=socketdata.tbegin,
|
|
395
|
+
)
|
|
375
396
|
socketdata.record.address = socketdata.address
|
|
376
397
|
socketdata.record.ssl = socketdata.ssl
|
|
377
398
|
socketdata.record.send_data(socketdata.rawdata)
|
|
@@ -446,7 +467,12 @@ def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
|
|
|
446
467
|
)
|
|
447
468
|
initiator.tbegin = tbegin
|
|
448
469
|
group.tbegin = tbegin
|
|
449
|
-
socketdata.record = HTTPRecord(
|
|
470
|
+
socketdata.record = HTTPRecord(
|
|
471
|
+
records.current_initiator,
|
|
472
|
+
records.current_group,
|
|
473
|
+
records.current_tag,
|
|
474
|
+
tbegin=socketdata.tbegin,
|
|
475
|
+
)
|
|
450
476
|
socketdata.record.address = socketdata.address
|
|
451
477
|
socketdata.record.ssl = socketdata.ssl
|
|
452
478
|
socketdata.record.send_data(socketdata.rawdata)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Generator
|
|
6
|
+
|
|
7
|
+
from httpdbg.hooks.utils import decorate
|
|
8
|
+
from httpdbg.hooks.utils import getcallargs
|
|
9
|
+
from httpdbg.hooks.utils import undecorate
|
|
10
|
+
from httpdbg.records import HTTPRecords
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def set_hook_starlette_request_init(records: HTTPRecords, method: Callable):
|
|
14
|
+
|
|
15
|
+
@wraps(method)
|
|
16
|
+
def hook(self, *args, **kwargs):
|
|
17
|
+
|
|
18
|
+
callargs = getcallargs(method, self, *args, **kwargs)
|
|
19
|
+
|
|
20
|
+
if "receive" in callargs:
|
|
21
|
+
if hasattr(callargs["receive"], "__self__"):
|
|
22
|
+
if hasattr(callargs["receive"].__self__, "transport"):
|
|
23
|
+
if hasattr(
|
|
24
|
+
callargs["receive"].__self__.transport,
|
|
25
|
+
"_TCPTransport__httpdbg_socketdata",
|
|
26
|
+
):
|
|
27
|
+
# make the link between starlette.requests.Request (self) and uvicorn.protocols.http.httptools_impl.HttpToolsProtocol (socketdata)
|
|
28
|
+
self.__httpdbg_socketdata_id = callargs[
|
|
29
|
+
"receive"
|
|
30
|
+
].__self__.transport._TCPTransport__httpdbg_socketdata.id
|
|
31
|
+
|
|
32
|
+
return method(self, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
return hook
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def hook_starlette(records: HTTPRecords) -> Generator[None, None, None]:
|
|
39
|
+
hooks = False
|
|
40
|
+
try:
|
|
41
|
+
import starlette.requests
|
|
42
|
+
|
|
43
|
+
starlette.requests.Request.__init__ = decorate(
|
|
44
|
+
records,
|
|
45
|
+
starlette.requests.Request.__init__,
|
|
46
|
+
set_hook_starlette_request_init,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
hooks = True
|
|
50
|
+
except ImportError:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
yield
|
|
54
|
+
|
|
55
|
+
if hooks:
|
|
56
|
+
starlette.requests.Request.__init__ = undecorate(
|
|
57
|
+
starlette.requests.Request.__init__
|
|
58
|
+
)
|
|
@@ -45,15 +45,15 @@ def set_hook_for_unittest_run(records: HTTPRecords, method: Callable):
|
|
|
45
45
|
return hook
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def set_hook_for_unittest_fixture(
|
|
48
|
+
def set_hook_for_unittest_fixture(records: HTTPRecords, method: Callable):
|
|
49
49
|
|
|
50
50
|
def hook(*args, **kwargs):
|
|
51
51
|
|
|
52
52
|
if getattr(method, "__name__") == "_callSetUp":
|
|
53
|
-
with httpdbg_tag("setUp"):
|
|
53
|
+
with httpdbg_tag(records, "setUp"):
|
|
54
54
|
return method(*args, **kwargs)
|
|
55
55
|
elif getattr(method, "__name__") == "_callTearDown":
|
|
56
|
-
with httpdbg_tag("tearDown"):
|
|
56
|
+
with httpdbg_tag(records, "tearDown"):
|
|
57
57
|
return method(*args, **kwargs)
|
|
58
58
|
else:
|
|
59
59
|
return method(*args, **kwargs)
|