httpdbg 1.2.6__tar.gz → 2.0.1__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.2.6 → httpdbg-2.0.1}/PKG-INFO +13 -1
- {httpdbg-1.2.6 → httpdbg-2.0.1}/README.md +12 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/__init__.py +1 -2
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/__main__.py +4 -3
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/args.py +8 -4
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/aiohttp.py +1 -3
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/all.py +25 -26
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/external.py +1 -2
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/fastapi.py +14 -11
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/flask.py +10 -7
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/generic.py +7 -14
- httpdbg-2.0.1/httpdbg/hooks/h2.py +213 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/http.py +0 -1
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/httpx.py +24 -4
- httpdbg-2.0.1/httpdbg/hooks/record.py +179 -0
- httpdbg-2.0.1/httpdbg/hooks/recordhttp1.py +197 -0
- httpdbg-2.0.1/httpdbg/hooks/recordhttp2.py +113 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/requests.py +1 -3
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/socket.py +182 -47
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/unittest.py +31 -25
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/urllib3.py +1 -3
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/utils.py +3 -1
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/uvicorn.py +19 -13
- httpdbg-2.0.1/httpdbg/http_status_code.py +93 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/initiator.py +17 -21
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/mode_console.py +1 -2
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/mode_module.py +1 -2
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/mode_script.py +1 -2
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/preview.py +11 -5
- httpdbg-2.0.1/httpdbg/records.py +92 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/server.py +2 -2
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/utils.py +4 -5
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/api.py +3 -4
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/app.py +26 -17
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/PKG-INFO +13 -1
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/SOURCES.txt +6 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/pyproject.toml +1 -0
- httpdbg-2.0.1/tests/test_hook_h2.py +315 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_server_hook_fastapi.py +4 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_server_hook_flask.py +10 -0
- httpdbg-1.2.6/httpdbg/records.py +0 -482
- {httpdbg-1.2.6 → httpdbg-2.0.1}/LICENSE +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/env.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/exception.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/pytest.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/starlette.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/template_sitecustomize.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/log.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/api.js +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/index.htm +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/render.js +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/style.css +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/table.css +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/setup.cfg +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_api.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_cli.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_context_manager.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_httpx.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_pytest.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_requests.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_unittest.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_initiator.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_mode_console.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_mode_script.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_multiprocess.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_preview.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_pyhttpdbg_server.py +0 -0
- {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
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
|
|
@@ -32,6 +32,8 @@ To use it, execute your program using the `pyhttpdbg` command instead of `python
|
|
|
32
32
|
|
|
33
33
|

|
|
34
34
|
|
|
35
|
+
Supports `HTTP/1.0`, `HTTP/1.1`, and `HTTP/2`.
|
|
36
|
+
|
|
35
37
|
Full documentation => [https://httpdbg.readthedocs.io/](https://httpdbg.readthedocs.io/en/latest/)
|
|
36
38
|
|
|
37
39
|
## installation
|
|
@@ -156,6 +158,16 @@ The requests are grouped by tests.
|
|
|
156
158
|
| pytest | supported | - | yes |
|
|
157
159
|
| unittest | supported | - | yes |
|
|
158
160
|
|
|
161
|
+
### supported HTTP version
|
|
162
|
+
|
|
163
|
+
| version | status |
|
|
164
|
+
|----------------|-------------------------------------------------------------|
|
|
165
|
+
| 1.0 | supported |
|
|
166
|
+
| 1.1 | supported |
|
|
167
|
+
| 2 | supported (only through the `h2` lib) |
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
159
171
|
## configuration
|
|
160
172
|
|
|
161
173
|
No configuration is necessary to start but some few settings are available for particular use.
|
|
@@ -6,6 +6,8 @@ To use it, execute your program using the `pyhttpdbg` command instead of `python
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
+
Supports `HTTP/1.0`, `HTTP/1.1`, and `HTTP/2`.
|
|
10
|
+
|
|
9
11
|
Full documentation => [https://httpdbg.readthedocs.io/](https://httpdbg.readthedocs.io/en/latest/)
|
|
10
12
|
|
|
11
13
|
## installation
|
|
@@ -130,6 +132,16 @@ The requests are grouped by tests.
|
|
|
130
132
|
| pytest | supported | - | yes |
|
|
131
133
|
| unittest | supported | - | yes |
|
|
132
134
|
|
|
135
|
+
### supported HTTP version
|
|
136
|
+
|
|
137
|
+
| version | status |
|
|
138
|
+
|----------------|-------------------------------------------------------------|
|
|
139
|
+
| 1.0 | supported |
|
|
140
|
+
| 1.1 | supported |
|
|
141
|
+
| 2 | supported (only through the `h2` lib) |
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
133
145
|
## configuration
|
|
134
146
|
|
|
135
147
|
No configuration is necessary to start but some few settings are available for particular use.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
import sys
|
|
3
2
|
import time
|
|
4
3
|
|
|
@@ -24,7 +23,8 @@ def pyhttpdbg(params, subparams, test_mode=False):
|
|
|
24
23
|
|
|
25
24
|
url = f"http://{params.host}:{params.port}/{'?hi=on' if params.console else ''}"
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
if not params.no_banner:
|
|
27
|
+
print_msg(f" httpdbg - HTTP(S) requests available at {url}")
|
|
28
28
|
|
|
29
29
|
sys.path.insert(0, "") # to mimic the default python command behavior
|
|
30
30
|
|
|
@@ -39,7 +39,8 @@ def pyhttpdbg(params, subparams, test_mode=False):
|
|
|
39
39
|
run_console(records, test_mode)
|
|
40
40
|
|
|
41
41
|
if not (params.force_quit or test_mode):
|
|
42
|
-
|
|
42
|
+
if not params.no_banner:
|
|
43
|
+
print_msg(f" httpdbg - HTTP(S) requests available at {url}")
|
|
43
44
|
|
|
44
45
|
if params.keep_up:
|
|
45
46
|
input("Press enter to quit")
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
import argparse
|
|
3
2
|
from pathlib import Path
|
|
4
|
-
from typing import List, Tuple
|
|
5
3
|
|
|
6
4
|
from httpdbg.log import LogLevel
|
|
7
5
|
|
|
8
6
|
|
|
9
|
-
def read_args(args:
|
|
7
|
+
def read_args(args: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
|
10
8
|
httpdbg_args = args
|
|
11
9
|
client_args = []
|
|
12
10
|
for action in ["--console", "--module", "-m", "--script"]:
|
|
@@ -66,7 +64,13 @@ def read_args(args: List[str]) -> Tuple[argparse.Namespace, List[str]]:
|
|
|
66
64
|
help="The log level",
|
|
67
65
|
)
|
|
68
66
|
|
|
69
|
-
parser.add_argument("--log", type=Path, help="
|
|
67
|
+
parser.add_argument("--log", type=Path, help="path to the log file.")
|
|
68
|
+
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--no-banner",
|
|
71
|
+
action="store_true",
|
|
72
|
+
help="do not print the banner in the console",
|
|
73
|
+
)
|
|
70
74
|
|
|
71
75
|
actions = parser.add_mutually_exclusive_group()
|
|
72
76
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
from collections.abc import Callable
|
|
3
2
|
from contextlib import contextmanager
|
|
4
|
-
import traceback
|
|
5
3
|
from typing import Generator
|
|
6
4
|
|
|
7
5
|
from httpdbg.hooks.utils import getcallargs
|
|
@@ -16,7 +14,7 @@ def set_hook_for_aiohttp_async(records: HTTPRecords, method: Callable):
|
|
|
16
14
|
initiator_and_group = None
|
|
17
15
|
try:
|
|
18
16
|
with httpdbg_initiator(
|
|
19
|
-
records,
|
|
17
|
+
records, method, *args, **kwargs
|
|
20
18
|
) as initiator_and_group:
|
|
21
19
|
ret = await method(*args, **kwargs)
|
|
22
20
|
return ret
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
from contextlib import contextmanager
|
|
3
2
|
from typing import Generator
|
|
4
|
-
from typing import List
|
|
5
|
-
from typing import Tuple
|
|
6
3
|
from typing import Union
|
|
7
4
|
|
|
8
5
|
from httpdbg.hooks.aiohttp import hook_aiohttp
|
|
@@ -10,6 +7,7 @@ from httpdbg.hooks.external import watcher_external
|
|
|
10
7
|
from httpdbg.hooks.fastapi import hook_fastapi
|
|
11
8
|
from httpdbg.hooks.flask import hook_flask
|
|
12
9
|
from httpdbg.hooks.generic import hook_generic
|
|
10
|
+
from httpdbg.hooks.h2 import hook_h2
|
|
13
11
|
from httpdbg.hooks.http import hook_http
|
|
14
12
|
from httpdbg.hooks.httpx import hook_httpx
|
|
15
13
|
from httpdbg.hooks.pytest import hook_pytest
|
|
@@ -26,10 +24,10 @@ from httpdbg.records import HTTPRecords
|
|
|
26
24
|
@contextmanager
|
|
27
25
|
def httprecord(
|
|
28
26
|
records: HTTPRecords = None,
|
|
29
|
-
initiators: Union[
|
|
27
|
+
initiators: Union[list[str], None] = None,
|
|
30
28
|
client: bool = True,
|
|
31
29
|
server: bool = False,
|
|
32
|
-
ignore:
|
|
30
|
+
ignore: tuple[tuple[str, int], ...] = (),
|
|
33
31
|
multiprocess: bool = True,
|
|
34
32
|
) -> Generator[HTTPRecords, None, None]:
|
|
35
33
|
if records is None:
|
|
@@ -37,25 +35,26 @@ def httprecord(
|
|
|
37
35
|
|
|
38
36
|
with hook_flask(records):
|
|
39
37
|
with hook_socket(records):
|
|
40
|
-
with
|
|
41
|
-
with
|
|
42
|
-
with
|
|
43
|
-
with
|
|
44
|
-
with
|
|
45
|
-
with
|
|
46
|
-
with
|
|
47
|
-
with
|
|
48
|
-
with
|
|
49
|
-
with
|
|
50
|
-
with
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
with hook_h2(records):
|
|
39
|
+
with hook_fastapi(records):
|
|
40
|
+
with hook_starlette(records):
|
|
41
|
+
with hook_uvicorn(records):
|
|
42
|
+
with hook_http(records):
|
|
43
|
+
with hook_httpx(records):
|
|
44
|
+
with hook_requests(records):
|
|
45
|
+
with hook_urllib3(records):
|
|
46
|
+
with hook_aiohttp(records):
|
|
47
|
+
with hook_pytest(records):
|
|
48
|
+
with hook_unittest(records):
|
|
49
|
+
with hook_generic(
|
|
50
|
+
records, initiators
|
|
51
|
+
):
|
|
52
|
+
if multiprocess:
|
|
53
|
+
with watcher_external(
|
|
54
|
+
records,
|
|
55
|
+
initiators,
|
|
56
|
+
server,
|
|
57
|
+
):
|
|
58
|
+
yield records
|
|
59
|
+
else:
|
|
59
60
|
yield records
|
|
60
|
-
else:
|
|
61
|
-
yield records
|
|
@@ -7,7 +7,6 @@ import pickle
|
|
|
7
7
|
import tempfile
|
|
8
8
|
import time
|
|
9
9
|
import threading
|
|
10
|
-
from typing import List
|
|
11
10
|
from typing import Union
|
|
12
11
|
|
|
13
12
|
from httpdbg.env import HTTPDBG_MULTIPROCESS_DIR
|
|
@@ -18,7 +17,7 @@ from httpdbg.records import HTTPRecords
|
|
|
18
17
|
@contextmanager
|
|
19
18
|
def watcher_external(
|
|
20
19
|
records: HTTPRecords,
|
|
21
|
-
initiators: Union[
|
|
20
|
+
initiators: Union[list[str], None] = None,
|
|
22
21
|
server: bool = False,
|
|
23
22
|
) -> Generator[HTTPRecords, None, None]:
|
|
24
23
|
if HTTPDBG_MULTIPROCESS_DIR not in os.environ:
|
|
@@ -3,9 +3,7 @@ from collections.abc import Callable
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
import functools
|
|
5
5
|
from functools import wraps
|
|
6
|
-
from typing import Dict
|
|
7
6
|
from typing import Generator
|
|
8
|
-
from typing import Union
|
|
9
7
|
|
|
10
8
|
import datetime
|
|
11
9
|
|
|
@@ -37,7 +35,7 @@ def set_hook_fastapi_endpoint(records: HTTPRecords, method: Callable):
|
|
|
37
35
|
def set_hook_fastapi_apirouter_add_api_route(
|
|
38
36
|
records: HTTPRecords,
|
|
39
37
|
method: Callable,
|
|
40
|
-
already_mapped:
|
|
38
|
+
already_mapped: dict[Callable, Callable],
|
|
41
39
|
):
|
|
42
40
|
|
|
43
41
|
@wraps(method)
|
|
@@ -56,7 +54,9 @@ def set_hook_fastapi_apirouter_add_api_route(
|
|
|
56
54
|
else:
|
|
57
55
|
callargs[param] = already_mapped[original_view_func]
|
|
58
56
|
|
|
59
|
-
args =
|
|
57
|
+
args = tuple(
|
|
58
|
+
callargs[param] if x == original_view_func else x for x in args
|
|
59
|
+
)
|
|
60
60
|
if param in kwargs:
|
|
61
61
|
kwargs[param] = callargs[param]
|
|
62
62
|
|
|
@@ -85,12 +85,15 @@ def set_hook_fastapi_app(records: HTTPRecords, method: Callable):
|
|
|
85
85
|
*args,
|
|
86
86
|
**kwargs,
|
|
87
87
|
) as group:
|
|
88
|
-
if group and
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
if group and socketdata_id:
|
|
89
|
+
sock = records._tracerhttp1.sockets.get(socketdata_id)
|
|
90
|
+
if (sock is not None) and (sock.record is not None):
|
|
91
|
+
record = records.groups.get(group.id)
|
|
92
|
+
if record is not None:
|
|
93
|
+
record.tbegin = sock.record.tbegin - datetime.timedelta(
|
|
94
|
+
milliseconds=1
|
|
95
|
+
)
|
|
96
|
+
sock.record.group_id = group.id
|
|
94
97
|
|
|
95
98
|
ret = await method(*args, **kwargs)
|
|
96
99
|
|
|
@@ -121,7 +124,7 @@ def hook_fastapi(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
121
124
|
try:
|
|
122
125
|
import fastapi.routing
|
|
123
126
|
|
|
124
|
-
already_mapped:
|
|
127
|
+
already_mapped: dict[Callable, Callable] = {}
|
|
125
128
|
|
|
126
129
|
set_hook_fastapi_apirouter_add_api_route_with_already_mapped = (
|
|
127
130
|
functools.partial(
|
|
@@ -3,9 +3,8 @@ from collections.abc import Callable
|
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
import functools
|
|
5
5
|
from functools import wraps
|
|
6
|
-
from typing import Dict
|
|
7
6
|
from typing import Generator
|
|
8
|
-
|
|
7
|
+
|
|
9
8
|
|
|
10
9
|
from httpdbg.hooks.utils import getcallargs
|
|
11
10
|
from httpdbg.hooks.utils import decorate
|
|
@@ -36,7 +35,7 @@ def set_hook_flask_endpoint(records: HTTPRecords, method: Callable):
|
|
|
36
35
|
def set_hook_flask_add_url_rule(
|
|
37
36
|
records: HTTPRecords,
|
|
38
37
|
method: Callable,
|
|
39
|
-
already_mapped:
|
|
38
|
+
already_mapped: dict[Callable, Callable],
|
|
40
39
|
):
|
|
41
40
|
|
|
42
41
|
def hook(*args, **kwargs):
|
|
@@ -55,7 +54,9 @@ def set_hook_flask_add_url_rule(
|
|
|
55
54
|
else:
|
|
56
55
|
callargs[param] = already_mapped[original_view_func]
|
|
57
56
|
|
|
58
|
-
args =
|
|
57
|
+
args = tuple(
|
|
58
|
+
callargs[param] if x == original_view_func else x for x in args
|
|
59
|
+
)
|
|
59
60
|
if param in kwargs:
|
|
60
61
|
kwargs[param] = callargs[param]
|
|
61
62
|
|
|
@@ -67,7 +68,7 @@ def set_hook_flask_add_url_rule(
|
|
|
67
68
|
def set_hook_flask_register_error_handler(
|
|
68
69
|
records: HTTPRecords,
|
|
69
70
|
method: Callable,
|
|
70
|
-
already_mapped:
|
|
71
|
+
already_mapped: dict[Callable, Callable],
|
|
71
72
|
):
|
|
72
73
|
|
|
73
74
|
def hook(*args, **kwargs):
|
|
@@ -86,7 +87,9 @@ def set_hook_flask_register_error_handler(
|
|
|
86
87
|
else:
|
|
87
88
|
callargs[param] = already_mapped[original_view_func]
|
|
88
89
|
|
|
89
|
-
args =
|
|
90
|
+
args = tuple(
|
|
91
|
+
callargs[param] if x == original_view_func else x for x in args
|
|
92
|
+
)
|
|
90
93
|
if param in kwargs:
|
|
91
94
|
kwargs[param] = callargs[param]
|
|
92
95
|
|
|
@@ -103,7 +106,7 @@ def hook_flask(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
103
106
|
|
|
104
107
|
# we must not apply the hook more than once on a mapped endpoint function
|
|
105
108
|
# AssertionError: View function mapping is overwriting an existing endpoint function: xxxx
|
|
106
|
-
already_mapped:
|
|
109
|
+
already_mapped: dict[Callable, Callable] = {}
|
|
107
110
|
|
|
108
111
|
set_hook_flask_add_url_rule_with_already_mapped = functools.partial(
|
|
109
112
|
set_hook_flask_add_url_rule, already_mapped=already_mapped
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
import builtins
|
|
3
2
|
from collections.abc import Callable
|
|
4
3
|
from contextlib import contextmanager
|
|
5
4
|
import functools
|
|
6
5
|
import importlib
|
|
7
6
|
import inspect
|
|
8
|
-
import traceback
|
|
9
7
|
from types import ModuleType
|
|
10
8
|
from typing import Any
|
|
11
9
|
from typing import Generator
|
|
12
|
-
from typing import List
|
|
13
10
|
from typing import Union
|
|
14
11
|
|
|
15
12
|
from httpdbg.hooks.utils import decorate
|
|
@@ -23,9 +20,7 @@ def set_hook_for_generic_async(records: HTTPRecords, method: Any):
|
|
|
23
20
|
logger().info(f"SET_HOOK_FOR_GENERIC_ASYNC {method}")
|
|
24
21
|
|
|
25
22
|
async def hook(*args, **kwargs):
|
|
26
|
-
with httpdbg_initiator(
|
|
27
|
-
records, traceback.extract_stack(), method, *args, **kwargs
|
|
28
|
-
):
|
|
23
|
+
with httpdbg_initiator(records, method, *args, **kwargs):
|
|
29
24
|
ret = await method(*args, **kwargs)
|
|
30
25
|
|
|
31
26
|
return ret
|
|
@@ -37,9 +32,7 @@ def set_hook_for_generic(records: HTTPRecords, method: Any):
|
|
|
37
32
|
logger().info(f"SET_HOOK_FOR_GENERIC {method}")
|
|
38
33
|
|
|
39
34
|
def hook(*args, **kwargs):
|
|
40
|
-
with httpdbg_initiator(
|
|
41
|
-
records, traceback.extract_stack(), method, *args, **kwargs
|
|
42
|
-
):
|
|
35
|
+
with httpdbg_initiator(records, method, *args, **kwargs):
|
|
43
36
|
ret = method(*args, **kwargs)
|
|
44
37
|
|
|
45
38
|
return ret
|
|
@@ -49,13 +42,13 @@ def set_hook_for_generic(records: HTTPRecords, method: Any):
|
|
|
49
42
|
|
|
50
43
|
@contextmanager
|
|
51
44
|
def hook_generic(
|
|
52
|
-
records: HTTPRecords, initiators: Union[
|
|
45
|
+
records: HTTPRecords, initiators: Union[list[str], None] = None
|
|
53
46
|
) -> Generator[None, None, None]:
|
|
54
47
|
if initiators:
|
|
55
48
|
# we add a hook for a generic initiator only if the module is imported
|
|
56
49
|
|
|
57
|
-
hooks:
|
|
58
|
-
already_hooked:
|
|
50
|
+
hooks: list[Callable] = []
|
|
51
|
+
already_hooked: list[str] = []
|
|
59
52
|
|
|
60
53
|
original_builtin_import = builtins.__import__
|
|
61
54
|
|
|
@@ -130,7 +123,7 @@ def hook_generic(
|
|
|
130
123
|
fnc = undecorate(fnc)
|
|
131
124
|
|
|
132
125
|
|
|
133
|
-
def list_callables_from_module(records: HTTPRecords, module: str) ->
|
|
126
|
+
def list_callables_from_module(records: HTTPRecords, module: str) -> list[Any]:
|
|
134
127
|
callables = []
|
|
135
128
|
|
|
136
129
|
try:
|
|
@@ -180,7 +173,7 @@ def list_callables_from_class(
|
|
|
180
173
|
imported_module: ModuleType,
|
|
181
174
|
module: str,
|
|
182
175
|
classname: str,
|
|
183
|
-
) ->
|
|
176
|
+
) -> list[Any]:
|
|
184
177
|
callables = []
|
|
185
178
|
|
|
186
179
|
try:
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
import random
|
|
4
|
+
from typing import Generator
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from httpdbg.hooks.recordhttp2 import HTTP2Record
|
|
8
|
+
from httpdbg.hooks.utils import decorate
|
|
9
|
+
from httpdbg.hooks.utils import getcallargs
|
|
10
|
+
from httpdbg.hooks.utils import undecorate
|
|
11
|
+
from httpdbg.initiator import httpdbg_initiator
|
|
12
|
+
from httpdbg.log import logger
|
|
13
|
+
from httpdbg.records import HTTPRecords
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TracerHTTP2:
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
ignore: tuple[tuple[str, int], ...] = (),
|
|
21
|
+
):
|
|
22
|
+
self.sockets: dict[str, HTTP2Record] = dict()
|
|
23
|
+
self.ignore: tuple[tuple[str, int], ...] = ignore
|
|
24
|
+
|
|
25
|
+
def send_headers(
|
|
26
|
+
self,
|
|
27
|
+
initiator_id: str,
|
|
28
|
+
group_id: str,
|
|
29
|
+
socket_id: int,
|
|
30
|
+
stream_id: int,
|
|
31
|
+
headers: list[tuple[bytes, bytes]],
|
|
32
|
+
) -> HTTP2Record:
|
|
33
|
+
|
|
34
|
+
if f"{socket_id}-{stream_id}" in self.sockets:
|
|
35
|
+
record = self.sockets[f"{socket_id}-{stream_id}"]
|
|
36
|
+
if record.is_client:
|
|
37
|
+
# this is a new request: we "close" the previous one
|
|
38
|
+
self.sockets.pop(f"{socket_id}-{stream_id}")
|
|
39
|
+
|
|
40
|
+
record = HTTP2Record(initiator_id, group_id)
|
|
41
|
+
record.request.headers = headers
|
|
42
|
+
self.sockets[f"{socket_id}-{stream_id}"] = record
|
|
43
|
+
else:
|
|
44
|
+
record.response.headers = headers
|
|
45
|
+
else:
|
|
46
|
+
record = HTTP2Record(initiator_id, group_id)
|
|
47
|
+
record.request.headers = headers
|
|
48
|
+
self.sockets[f"{socket_id}-{stream_id}"] = record
|
|
49
|
+
|
|
50
|
+
return record
|
|
51
|
+
|
|
52
|
+
def send_data(
|
|
53
|
+
self,
|
|
54
|
+
socket_id: int,
|
|
55
|
+
stream_id: int,
|
|
56
|
+
data: bytes,
|
|
57
|
+
):
|
|
58
|
+
record = self.sockets.get(f"{socket_id}-{stream_id}")
|
|
59
|
+
if record:
|
|
60
|
+
record.send_data(data)
|
|
61
|
+
|
|
62
|
+
def receive_headers(
|
|
63
|
+
self,
|
|
64
|
+
initiator_id: str,
|
|
65
|
+
group_id: str,
|
|
66
|
+
socket_id: int,
|
|
67
|
+
stream_id: int,
|
|
68
|
+
headers: list[tuple[bytes, bytes]],
|
|
69
|
+
is_client: bool = True,
|
|
70
|
+
) -> Union[HTTP2Record, None]:
|
|
71
|
+
record = None
|
|
72
|
+
if not is_client:
|
|
73
|
+
if f"{socket_id}-{stream_id}" in self.sockets:
|
|
74
|
+
# this is a new request received by the server: we "close" the previous one
|
|
75
|
+
self.sockets.pop(f"{socket_id}-{stream_id}")
|
|
76
|
+
record = HTTP2Record(initiator_id, group_id, is_client=False)
|
|
77
|
+
record.request.headers = headers
|
|
78
|
+
self.sockets[f"{socket_id}-{stream_id}"] = record
|
|
79
|
+
else:
|
|
80
|
+
if f"{socket_id}-{stream_id}" in self.sockets:
|
|
81
|
+
record = self.sockets[f"{socket_id}-{stream_id}"]
|
|
82
|
+
record.response.headers = headers
|
|
83
|
+
return record
|
|
84
|
+
|
|
85
|
+
def receive_data(
|
|
86
|
+
self,
|
|
87
|
+
socket_id: int,
|
|
88
|
+
stream_id: int,
|
|
89
|
+
data: bytes,
|
|
90
|
+
):
|
|
91
|
+
record = self.sockets.get(f"{socket_id}-{stream_id}")
|
|
92
|
+
if record:
|
|
93
|
+
record.receive_data(data)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def set_hook_for_h2_send_headers(records: HTTPRecords, method: Callable):
|
|
97
|
+
def hook(*args, **kwargs):
|
|
98
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
99
|
+
self = callargs.get("self")
|
|
100
|
+
stream_id = callargs.get("stream_id")
|
|
101
|
+
headers = callargs.get("headers")
|
|
102
|
+
|
|
103
|
+
with httpdbg_initiator(records, method, *args, **kwargs) as initiator_and_group:
|
|
104
|
+
initiator, group, _ = initiator_and_group
|
|
105
|
+
if all(x is not None for x in (self, stream_id, headers)):
|
|
106
|
+
logger().debug(
|
|
107
|
+
f"H2 send_headers - self={id(self)} steam_id={stream_id} headers={headers}"
|
|
108
|
+
)
|
|
109
|
+
record = records._tracerhttp2.send_headers(
|
|
110
|
+
initiator.id, group.id, id(self), stream_id, headers
|
|
111
|
+
)
|
|
112
|
+
if record.is_client and records.client:
|
|
113
|
+
records.requests[record.id] = record
|
|
114
|
+
ret = method(*args, **kwargs)
|
|
115
|
+
return ret
|
|
116
|
+
|
|
117
|
+
return hook
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def set_hook_for_h2_send_data(records: HTTPRecords, method: Callable):
|
|
121
|
+
def hook(*args, **kwargs):
|
|
122
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
123
|
+
self = callargs.get("self")
|
|
124
|
+
stream_id = callargs.get("stream_id")
|
|
125
|
+
data = callargs.get("data")
|
|
126
|
+
|
|
127
|
+
with httpdbg_initiator(records, method, *args, **kwargs):
|
|
128
|
+
if all(x is not None for x in (self, stream_id, data)):
|
|
129
|
+
logger().debug(
|
|
130
|
+
f"H2 send_data - self={id(self)} steam_id={stream_id} data={data[:20]!r}"
|
|
131
|
+
)
|
|
132
|
+
records._tracerhttp2.send_data(id(self), stream_id, data)
|
|
133
|
+
ret = method(*args, **kwargs)
|
|
134
|
+
return ret
|
|
135
|
+
|
|
136
|
+
return hook
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def set_hook_for_h2_receive_data(records: HTTPRecords, method: Callable):
|
|
140
|
+
def hook(*args, **kwargs):
|
|
141
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
142
|
+
self = callargs.get("self")
|
|
143
|
+
|
|
144
|
+
with httpdbg_initiator(records, method, *args, **kwargs) as initiator_and_group:
|
|
145
|
+
initiator, group, _ = initiator_and_group
|
|
146
|
+
ret = method(*args, **kwargs)
|
|
147
|
+
|
|
148
|
+
for event in ret:
|
|
149
|
+
import h2.events
|
|
150
|
+
|
|
151
|
+
if isinstance(event, h2.events.RequestReceived):
|
|
152
|
+
stream_id = event.stream_id
|
|
153
|
+
headers = event.headers
|
|
154
|
+
if all(x is not None for x in (self, stream_id, headers)):
|
|
155
|
+
logger().debug(
|
|
156
|
+
f"H2 receive_data headers RequestReceived {random.random()} - self={id(self)} steam_id={stream_id} headers={headers}"
|
|
157
|
+
)
|
|
158
|
+
record = records._tracerhttp2.receive_headers(
|
|
159
|
+
initiator.id,
|
|
160
|
+
group.id,
|
|
161
|
+
id(self),
|
|
162
|
+
stream_id,
|
|
163
|
+
headers,
|
|
164
|
+
is_client=False,
|
|
165
|
+
)
|
|
166
|
+
if record and (record.is_client is False) and records.server:
|
|
167
|
+
records.requests[record.id] = record
|
|
168
|
+
|
|
169
|
+
elif isinstance(event, h2.events.ResponseReceived):
|
|
170
|
+
stream_id = event.stream_id
|
|
171
|
+
headers = event.headers
|
|
172
|
+
if all(x is not None for x in (self, stream_id, headers)):
|
|
173
|
+
logger().debug(
|
|
174
|
+
f"H2 receive_data headers ResponseReceived {random.random()} - self={id(self)} steam_id={stream_id} headers={headers}"
|
|
175
|
+
)
|
|
176
|
+
records._tracerhttp2.receive_headers(
|
|
177
|
+
initiator.id, group.id, id(self), stream_id, headers
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
elif isinstance(event, h2.events.DataReceived):
|
|
181
|
+
stream_id = event.stream_id
|
|
182
|
+
data = event.data
|
|
183
|
+
if all(x is not None for x in (self, stream_id, data)):
|
|
184
|
+
logger().debug(
|
|
185
|
+
f"H2 receive_data data - self={id(self)} steam_id={stream_id} data={data[:20]!r}"
|
|
186
|
+
)
|
|
187
|
+
records._tracerhttp2.receive_data(id(self), stream_id, data)
|
|
188
|
+
|
|
189
|
+
return ret
|
|
190
|
+
|
|
191
|
+
return hook
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@contextmanager
|
|
195
|
+
def hook_h2(records: HTTPRecords) -> Generator[None, None, None]:
|
|
196
|
+
hooks = False
|
|
197
|
+
try:
|
|
198
|
+
import h2.connection
|
|
199
|
+
|
|
200
|
+
h2.connection.H2Connection.send_headers = decorate(records, h2.connection.H2Connection.send_headers, set_hook_for_h2_send_headers) # type: ignore[arg-type]
|
|
201
|
+
h2.connection.H2Connection.send_data = decorate(records, h2.connection.H2Connection.send_data, set_hook_for_h2_send_data) # type: ignore[arg-type]
|
|
202
|
+
h2.connection.H2Connection.receive_data = decorate(records, h2.connection.H2Connection.receive_data, set_hook_for_h2_receive_data) # type: ignore[arg-type]
|
|
203
|
+
|
|
204
|
+
hooks = True
|
|
205
|
+
except ImportError:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
yield
|
|
209
|
+
|
|
210
|
+
if hooks:
|
|
211
|
+
h2.connection.H2Connection.send_headers = undecorate(h2.connection.H2Connection.send_headers) # type: ignore[arg-type]
|
|
212
|
+
h2.connection.H2Connection.send_data = undecorate(h2.connection.H2Connection.send_data) # type: ignore[arg-type]
|
|
213
|
+
h2.connection.H2Connection.receive_data = undecorate(h2.connection.H2Connection.receive_data) # type: ignore[arg-type]
|