httpdbg 0.32.1__tar.gz → 1.0.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-0.32.1 → httpdbg-1.0.0}/PKG-INFO +15 -4
- {httpdbg-0.32.1 → httpdbg-1.0.0}/README.md +14 -3
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/__init__.py +1 -1
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/__main__.py +2 -1
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/args.py +6 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/all.py +8 -3
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/external.py +21 -3
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/socket.py +166 -98
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/template_sitecustomize.py +6 -1
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/utils.py +1 -1
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/records.py +57 -23
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/server.py +3 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/api.py +2 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/index.htm +2 -1
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/style.css +1 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/table.css +4 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg.egg-info/PKG-INFO +15 -4
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg.egg-info/SOURCES.txt +1 -0
- httpdbg-0.32.1/tests/test_server.py → httpdbg-1.0.0/tests/test_pyhttpdbg_server.py +7 -6
- httpdbg-1.0.0/tests/test_server.py +51 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/LICENSE +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/env.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/exception.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/aiohttp.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/httpx.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/pytest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/requests.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/unittest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/hooks/urllib3.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/initiator.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/log.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/mode_console.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/mode_module.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/mode_script.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/preview.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/utils.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/app.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/api.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg/webapp/static/render.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/pyproject.toml +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/setup.cfg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_api.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_cli.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_context_manager.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_hook_httpx.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_hook_pytest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_hook_requests.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_hook_unittest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_initiator.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_mode_console.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_mode_script.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_multiprocess.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.0.0}/tests/test_preview.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A very simple tool to debug HTTP(S) client requests
|
|
5
5
|
Author-email: cle-b <cle@tictac.pm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -26,7 +26,7 @@ License-File: LICENSE
|
|
|
26
26
|
|
|
27
27
|
# httpdbg
|
|
28
28
|
|
|
29
|
-
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client requests in a Python program.
|
|
29
|
+
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client and server requests in a Python program.
|
|
30
30
|
|
|
31
31
|
To use it, execute your program using the `pyhttpdbg` command instead of `python` and that's it. Open a browser to `http://localhost:4909` to view the requests:
|
|
32
32
|
|
|
@@ -42,6 +42,8 @@ pip install httpdbg
|
|
|
42
42
|
|
|
43
43
|
## usage
|
|
44
44
|
|
|
45
|
+
By default, both client and server requests are recorded. To record only client requests, use the `--only-client` command line argument.
|
|
46
|
+
|
|
45
47
|
### interactive console
|
|
46
48
|
|
|
47
49
|
Open an interactive console using the command `pyhttpdbg`.
|
|
@@ -101,7 +103,7 @@ pyhttpdbg -i api_client_pck --script my_script.py
|
|
|
101
103
|
|
|
102
104
|
You can use any package as an initiator, this is not limited to HTTP requests.
|
|
103
105
|
|
|
104
|
-
### Already supported packages
|
|
106
|
+
### Already supported packages for HTTP client
|
|
105
107
|
|
|
106
108
|
| packages | status |
|
|
107
109
|
|----------------|-------------------------------------|
|
|
@@ -120,7 +122,9 @@ No configuration is necessary to start but some few settings are available for p
|
|
|
120
122
|
### command line
|
|
121
123
|
|
|
122
124
|
```console
|
|
123
|
-
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
125
|
+
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
126
|
+
[--initiator INITIATOR] [--only-client]
|
|
127
|
+
[--keep-up | --force-quit]
|
|
124
128
|
[--console | --module MODULE | --script SCRIPT]
|
|
125
129
|
|
|
126
130
|
httdbg - a very simple tool to debug HTTP(S) client requests
|
|
@@ -132,6 +136,7 @@ options:
|
|
|
132
136
|
--version, -v print the httpdbg version
|
|
133
137
|
--initiator INITIATOR, -i INITIATOR
|
|
134
138
|
add a new initiator (package)
|
|
139
|
+
--only-client record only HTTP client requests
|
|
135
140
|
--keep-up, -k keep the server up even if the requests have been read
|
|
136
141
|
--force-quit, -q stop the server even if the requests have not been read
|
|
137
142
|
--console run a python console (default)
|
|
@@ -167,6 +172,12 @@ The requests:
|
|
|
167
172
|
* are still available in the web page even if the python process stopped (except if you force quit before the requests have been loaded by the web page).
|
|
168
173
|
* are automatically cleaned if a new execution is detected.
|
|
169
174
|
|
|
175
|
+
## limitations
|
|
176
|
+
|
|
177
|
+
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
178
|
+
|
|
179
|
+
Support for recording requests on the server side is new in `httpdbg 1.0.0` and is currently limited. For example, it doesn't work with `FastAPI`, which uses `uvloop`, but it does work with `Uvicorn` if the loop is `asyncio`. Work is in progress to improve this.
|
|
180
|
+
|
|
170
181
|
## documentation
|
|
171
182
|
|
|
172
183
|
https://httpdbg.readthedocs.io
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# httpdbg
|
|
2
2
|
|
|
3
|
-
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client requests in a Python program.
|
|
3
|
+
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client and server requests in a Python program.
|
|
4
4
|
|
|
5
5
|
To use it, execute your program using the `pyhttpdbg` command instead of `python` and that's it. Open a browser to `http://localhost:4909` to view the requests:
|
|
6
6
|
|
|
@@ -16,6 +16,8 @@ pip install httpdbg
|
|
|
16
16
|
|
|
17
17
|
## usage
|
|
18
18
|
|
|
19
|
+
By default, both client and server requests are recorded. To record only client requests, use the `--only-client` command line argument.
|
|
20
|
+
|
|
19
21
|
### interactive console
|
|
20
22
|
|
|
21
23
|
Open an interactive console using the command `pyhttpdbg`.
|
|
@@ -75,7 +77,7 @@ pyhttpdbg -i api_client_pck --script my_script.py
|
|
|
75
77
|
|
|
76
78
|
You can use any package as an initiator, this is not limited to HTTP requests.
|
|
77
79
|
|
|
78
|
-
### Already supported packages
|
|
80
|
+
### Already supported packages for HTTP client
|
|
79
81
|
|
|
80
82
|
| packages | status |
|
|
81
83
|
|----------------|-------------------------------------|
|
|
@@ -94,7 +96,9 @@ No configuration is necessary to start but some few settings are available for p
|
|
|
94
96
|
### command line
|
|
95
97
|
|
|
96
98
|
```console
|
|
97
|
-
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
99
|
+
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
100
|
+
[--initiator INITIATOR] [--only-client]
|
|
101
|
+
[--keep-up | --force-quit]
|
|
98
102
|
[--console | --module MODULE | --script SCRIPT]
|
|
99
103
|
|
|
100
104
|
httdbg - a very simple tool to debug HTTP(S) client requests
|
|
@@ -106,6 +110,7 @@ options:
|
|
|
106
110
|
--version, -v print the httpdbg version
|
|
107
111
|
--initiator INITIATOR, -i INITIATOR
|
|
108
112
|
add a new initiator (package)
|
|
113
|
+
--only-client record only HTTP client requests
|
|
109
114
|
--keep-up, -k keep the server up even if the requests have been read
|
|
110
115
|
--force-quit, -q stop the server even if the requests have not been read
|
|
111
116
|
--console run a python console (default)
|
|
@@ -141,6 +146,12 @@ The requests:
|
|
|
141
146
|
* are still available in the web page even if the python process stopped (except if you force quit before the requests have been loaded by the web page).
|
|
142
147
|
* are automatically cleaned if a new execution is detected.
|
|
143
148
|
|
|
149
|
+
## limitations
|
|
150
|
+
|
|
151
|
+
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
152
|
+
|
|
153
|
+
Support for recording requests on the server side is new in `httpdbg 1.0.0` and is currently limited. For example, it doesn't work with `FastAPI`, which uses `uvloop`, but it does work with `Uvicorn` if the loop is `asyncio`. Work is in progress to improve this.
|
|
154
|
+
|
|
144
155
|
## documentation
|
|
145
156
|
|
|
146
157
|
https://httpdbg.readthedocs.io
|
|
@@ -29,7 +29,8 @@ def pyhttpdbg(params, subparams, test_mode=False):
|
|
|
29
29
|
sys.path.insert(0, "") # to mimic the default python command behavior
|
|
30
30
|
|
|
31
31
|
with httpdbg_srv(params.host, params.port) as records:
|
|
32
|
-
|
|
32
|
+
records.server = not params.only_client
|
|
33
|
+
with httprecord(records, params.initiator, server=records.server):
|
|
33
34
|
if params.module:
|
|
34
35
|
run_module(subparams)
|
|
35
36
|
elif params.script:
|
|
@@ -36,6 +36,12 @@ def read_args(args: List[str]) -> Tuple[argparse.Namespace, List[str]]:
|
|
|
36
36
|
"--initiator", "-i", action="append", help="add a new initiator (package)"
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--only-client",
|
|
41
|
+
action="store_true",
|
|
42
|
+
help="record only HTTP client requests",
|
|
43
|
+
)
|
|
44
|
+
|
|
39
45
|
server_state = parser.add_mutually_exclusive_group()
|
|
40
46
|
|
|
41
47
|
server_state.add_argument(
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from contextlib import contextmanager
|
|
3
3
|
from typing import Generator
|
|
4
4
|
from typing import List
|
|
5
|
+
from typing import Tuple
|
|
5
6
|
from typing import Union
|
|
6
7
|
|
|
7
8
|
from httpdbg.hooks.aiohttp import hook_aiohttp
|
|
@@ -18,12 +19,16 @@ from httpdbg.records import HTTPRecords
|
|
|
18
19
|
|
|
19
20
|
@contextmanager
|
|
20
21
|
def httprecord(
|
|
21
|
-
records: HTTPRecords = None,
|
|
22
|
+
records: HTTPRecords = None,
|
|
23
|
+
initiators: Union[List[str], None] = None,
|
|
24
|
+
client: bool = True,
|
|
25
|
+
server: bool = False,
|
|
26
|
+
ignore: Union[List[Tuple[str, int]], None] = None,
|
|
22
27
|
) -> Generator[HTTPRecords, None, None]:
|
|
23
28
|
if records is None:
|
|
24
|
-
records = HTTPRecords()
|
|
29
|
+
records = HTTPRecords(client=client, server=server, ignore=ignore)
|
|
25
30
|
|
|
26
|
-
with watcher_external(records):
|
|
31
|
+
with watcher_external(records, initiators, server):
|
|
27
32
|
with hook_socket(records):
|
|
28
33
|
with hook_httpx(records):
|
|
29
34
|
with hook_requests(records):
|
|
@@ -4,10 +4,11 @@ from typing import Generator
|
|
|
4
4
|
import glob
|
|
5
5
|
import os
|
|
6
6
|
import pickle
|
|
7
|
-
import shutil
|
|
8
7
|
import tempfile
|
|
9
8
|
import time
|
|
10
9
|
import threading
|
|
10
|
+
from typing import List
|
|
11
|
+
from typing import Union
|
|
11
12
|
|
|
12
13
|
from httpdbg.env import HTTPDBG_MULTIPROCESS_DIR
|
|
13
14
|
from httpdbg.log import logger
|
|
@@ -15,7 +16,11 @@ from httpdbg.records import HTTPRecords
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@contextmanager
|
|
18
|
-
def watcher_external(
|
|
19
|
+
def watcher_external(
|
|
20
|
+
records: HTTPRecords,
|
|
21
|
+
initiators: Union[List[str], None] = None,
|
|
22
|
+
server: bool = False,
|
|
23
|
+
) -> Generator[HTTPRecords, None, None]:
|
|
19
24
|
if HTTPDBG_MULTIPROCESS_DIR not in os.environ:
|
|
20
25
|
with tempfile.TemporaryDirectory(prefix="httpdbg_") as httpdbg_multiprocess_dir:
|
|
21
26
|
|
|
@@ -31,7 +36,20 @@ def watcher_external(records: HTTPRecords) -> Generator[HTTPRecords, None, None]
|
|
|
31
36
|
)
|
|
32
37
|
sitecustomize = os.path.join(httpdbg_multiprocess_dir, "sitecustomize.py")
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
with open(template) as f:
|
|
40
|
+
template_content = f.read()
|
|
41
|
+
|
|
42
|
+
if initiators:
|
|
43
|
+
template_content = template_content.replace(
|
|
44
|
+
"HTTPDBG_INITIATORS = []", f"HTTPDBG_INITIATORS = {initiators}", 1
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
template_content = template_content.replace(
|
|
48
|
+
"HTTPDBG_RECORD_SERVER = False", f"HTTPDBG_RECORD_SERVER = {server}", 1
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
with open(sitecustomize, "w") as f:
|
|
52
|
+
f.write(template_content)
|
|
35
53
|
|
|
36
54
|
if "PYTHONPATH" in os.environ:
|
|
37
55
|
os.environ["PYTHONPATH"] = (
|
|
@@ -49,16 +49,17 @@ def set_hook_for_socket_connect(records: HTTPRecords, method: Callable):
|
|
|
49
49
|
if not isinstance(
|
|
50
50
|
ex, (BlockingIOError, OSError)
|
|
51
51
|
): # BlockingIOError for async, OSError for ipv6
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
if records.client:
|
|
53
|
+
with httpdbg_initiator(
|
|
54
|
+
records, traceback.extract_stack(), method, *args, **kwargs
|
|
55
|
+
) as initiator_and_group:
|
|
56
|
+
initiator, group, is_new = initiator_and_group
|
|
57
|
+
if is_new:
|
|
58
|
+
initiator.tbegin = tbegin
|
|
59
|
+
group.tbegin = tbegin
|
|
60
|
+
records.add_new_record_exception(
|
|
61
|
+
initiator, group, "http:///", ex
|
|
62
|
+
)
|
|
62
63
|
raise
|
|
63
64
|
|
|
64
65
|
return r
|
|
@@ -76,14 +77,17 @@ def set_hook_for_ssl_wrap_socket(records: HTTPRecords, method: Callable):
|
|
|
76
77
|
try:
|
|
77
78
|
sslsocket = method(sock, *args, **kwargs)
|
|
78
79
|
except Exception as ex:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
if records.client:
|
|
81
|
+
with httpdbg_initiator(
|
|
82
|
+
records, traceback.extract_stack(), method, *args, **kwargs
|
|
83
|
+
) as initiator_and_group:
|
|
84
|
+
initiator, group, is_new = initiator_and_group
|
|
85
|
+
if is_new:
|
|
86
|
+
initiator.tbegin = tbegin
|
|
87
|
+
group.tbegin = tbegin
|
|
88
|
+
records.add_new_record_exception(
|
|
89
|
+
initiator, group, "http:///", ex
|
|
90
|
+
)
|
|
87
91
|
raise
|
|
88
92
|
|
|
89
93
|
logger().info(
|
|
@@ -108,14 +112,17 @@ def set_hook_for_sslcontext_wrap_socket(records: HTTPRecords, method: Callable):
|
|
|
108
112
|
try:
|
|
109
113
|
sslsocket = method(self, sock, *args, **kwargs)
|
|
110
114
|
except Exception as ex:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
if records.client:
|
|
116
|
+
with httpdbg_initiator(
|
|
117
|
+
records, traceback.extract_stack(), method, *args, **kwargs
|
|
118
|
+
) as initiator_and_group:
|
|
119
|
+
initiator, group, is_new = initiator_and_group
|
|
120
|
+
if is_new:
|
|
121
|
+
initiator.tbegin = tbegin
|
|
122
|
+
group.tbegin = tbegin
|
|
123
|
+
records.add_new_record_exception(
|
|
124
|
+
initiator, group, "http:///", ex
|
|
125
|
+
)
|
|
119
126
|
raise
|
|
120
127
|
|
|
121
128
|
logger().info(
|
|
@@ -140,14 +147,17 @@ def set_hook_for_socket_wrap_bio(records: HTTPRecords, method: Callable):
|
|
|
140
147
|
try:
|
|
141
148
|
sslobject = method(self, *args, **kwargs)
|
|
142
149
|
except Exception as ex:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
if records.client:
|
|
151
|
+
with httpdbg_initiator(
|
|
152
|
+
records, traceback.extract_stack(), method, *args, **kwargs
|
|
153
|
+
) as initiator_and_group:
|
|
154
|
+
initiator, group, is_new = initiator_and_group
|
|
155
|
+
if is_new:
|
|
156
|
+
initiator.tbegin = tbegin
|
|
157
|
+
group.tbegin = tbegin
|
|
158
|
+
records.add_new_record_exception(
|
|
159
|
+
initiator, group, "http:///", ex
|
|
160
|
+
)
|
|
151
161
|
raise
|
|
152
162
|
|
|
153
163
|
logger().info(
|
|
@@ -174,18 +184,48 @@ def set_hook_for_socket_recv_into(records: HTTPRecords, method: Callable):
|
|
|
174
184
|
f"RECV_INTO - self={self} id={id(self)} socketdata={socketdata} args={args} kwargs={kwargs}"
|
|
175
185
|
)
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
nbytes = method(self, buffer, *args, **kwargs)
|
|
179
|
-
except Exception as ex:
|
|
180
|
-
if socketdata and socketdata.record:
|
|
181
|
-
socketdata.record.exception = ex
|
|
182
|
-
raise
|
|
187
|
+
nbytes = method(self, buffer, *args, **kwargs)
|
|
183
188
|
|
|
184
|
-
if socketdata
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
if socketdata:
|
|
190
|
+
if socketdata.record:
|
|
191
|
+
logger().info(
|
|
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")
|
|
200
|
+
logger().info(
|
|
201
|
+
f"RECV_INTO (after) - id={id(self)} buffer={(b''+buffer)[:20]}"
|
|
202
|
+
)
|
|
203
|
+
with httpdbg_initiator(
|
|
204
|
+
records,
|
|
205
|
+
traceback.extract_stack(),
|
|
206
|
+
method,
|
|
207
|
+
self,
|
|
208
|
+
buffer,
|
|
209
|
+
*args,
|
|
210
|
+
**kwargs,
|
|
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
|
|
221
|
+
)
|
|
222
|
+
socketdata.record.address = socketdata.address
|
|
223
|
+
socketdata.record.ssl = socketdata.ssl
|
|
224
|
+
socketdata.record.receive_data(socketdata.rawdata)
|
|
225
|
+
if records.server:
|
|
226
|
+
records.requests[socketdata.record.id] = socketdata.record
|
|
227
|
+
elif http_detected is False: # if None, there is nothing to do
|
|
228
|
+
records._sockets[id(self)] = None
|
|
189
229
|
|
|
190
230
|
return nbytes
|
|
191
231
|
|
|
@@ -203,15 +243,42 @@ def set_hook_for_socket_recv(records: HTTPRecords, method: Callable):
|
|
|
203
243
|
f"RECV - self={self} id={id(self)} socketdata={socketdata} bufsize={bufsize} args={args} kwargs={kwargs}"
|
|
204
244
|
)
|
|
205
245
|
|
|
206
|
-
|
|
207
|
-
buffer = method(self, bufsize, *args, **kwargs)
|
|
208
|
-
except Exception as ex:
|
|
209
|
-
if socketdata and socketdata.record:
|
|
210
|
-
socketdata.record.exception = ex
|
|
211
|
-
raise
|
|
246
|
+
buffer = method(self, bufsize, *args, **kwargs)
|
|
212
247
|
|
|
213
|
-
if socketdata
|
|
214
|
-
socketdata.record
|
|
248
|
+
if socketdata:
|
|
249
|
+
if socketdata.record:
|
|
250
|
+
socketdata.record.receive_data(buffer)
|
|
251
|
+
else:
|
|
252
|
+
socketdata.rawdata += buffer
|
|
253
|
+
http_detected = socketdata.http_detected()
|
|
254
|
+
if http_detected:
|
|
255
|
+
logger().info("RECV - http detected")
|
|
256
|
+
with httpdbg_initiator(
|
|
257
|
+
records,
|
|
258
|
+
traceback.extract_stack(),
|
|
259
|
+
method,
|
|
260
|
+
self,
|
|
261
|
+
bufsize,
|
|
262
|
+
*args,
|
|
263
|
+
**kwargs,
|
|
264
|
+
) as initiator_and_group:
|
|
265
|
+
initiator, group, is_new = initiator_and_group
|
|
266
|
+
if is_new:
|
|
267
|
+
tbegin = socketdata.tbegin - datetime.timedelta(
|
|
268
|
+
milliseconds=1
|
|
269
|
+
)
|
|
270
|
+
initiator.tbegin = tbegin
|
|
271
|
+
group.tbegin = tbegin
|
|
272
|
+
socketdata.record = HTTPRecord(
|
|
273
|
+
tbegin=socketdata.tbegin, is_client=False
|
|
274
|
+
)
|
|
275
|
+
socketdata.record.address = socketdata.address
|
|
276
|
+
socketdata.record.ssl = socketdata.ssl
|
|
277
|
+
socketdata.record.receive_data(socketdata.rawdata)
|
|
278
|
+
if records.server:
|
|
279
|
+
records.requests[socketdata.record.id] = socketdata.record
|
|
280
|
+
elif http_detected is False: # if None, there is nothing to do
|
|
281
|
+
records._sockets[id(self)] = None
|
|
215
282
|
|
|
216
283
|
return buffer
|
|
217
284
|
|
|
@@ -223,22 +290,28 @@ def set_hook_for_socket_recv(records: HTTPRecords, method: Callable):
|
|
|
223
290
|
# action: Append the data to an existing SocketRawData. Check if this is an HTTP request.
|
|
224
291
|
# and record it if this is case, otherwise delete the temporay SocketRawData.
|
|
225
292
|
def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
|
|
226
|
-
def hook(self,
|
|
293
|
+
def hook(self, data, *args, **kwargs):
|
|
227
294
|
socketdata = records.get_socket_data(self, request=True)
|
|
228
295
|
if socketdata:
|
|
229
296
|
logger().info(
|
|
230
|
-
f"SENDALL - self={self} id={id(self)} socketdata={socketdata}
|
|
297
|
+
f"SENDALL - self={self} id={id(self)} socketdata={socketdata} data={(b''+bytes(data))[:20]} type={type(data)} args={args} kwargs={kwargs}"
|
|
231
298
|
)
|
|
232
299
|
if socketdata:
|
|
233
300
|
if socketdata.record:
|
|
234
|
-
socketdata.record.
|
|
301
|
+
socketdata.record.send_data(data)
|
|
235
302
|
else:
|
|
236
|
-
socketdata.rawdata +=
|
|
303
|
+
socketdata.rawdata += data
|
|
237
304
|
http_detected = socketdata.http_detected()
|
|
238
305
|
if http_detected:
|
|
239
306
|
logger().info("SENDALL - http detected")
|
|
240
307
|
with httpdbg_initiator(
|
|
241
|
-
records,
|
|
308
|
+
records,
|
|
309
|
+
traceback.extract_stack(),
|
|
310
|
+
method,
|
|
311
|
+
self,
|
|
312
|
+
data,
|
|
313
|
+
*args,
|
|
314
|
+
**kwargs,
|
|
242
315
|
) as initiator_and_group:
|
|
243
316
|
initiator, group, is_new = initiator_and_group
|
|
244
317
|
if is_new:
|
|
@@ -250,16 +323,13 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
|
|
|
250
323
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
251
324
|
socketdata.record.address = socketdata.address
|
|
252
325
|
socketdata.record.ssl = socketdata.ssl
|
|
253
|
-
socketdata.record.
|
|
254
|
-
records.
|
|
326
|
+
socketdata.record.send_data(socketdata.rawdata)
|
|
327
|
+
if records.client:
|
|
328
|
+
records.requests[socketdata.record.id] = socketdata.record
|
|
255
329
|
elif http_detected is False: # if None, there is nothing to do
|
|
256
330
|
records._sockets[id(self)] = None
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
except Exception as ex:
|
|
260
|
-
if socketdata and socketdata.record:
|
|
261
|
-
socketdata.record.exception = ex
|
|
262
|
-
raise
|
|
331
|
+
|
|
332
|
+
return method(self, data, *args, **kwargs)
|
|
263
333
|
|
|
264
334
|
return hook
|
|
265
335
|
|
|
@@ -269,29 +339,30 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
|
|
|
269
339
|
# action: Append the data to an existing SocketRawData. Check if this is an HTTP request.
|
|
270
340
|
# and record it if this is case, otherwise delete the temporay SocketRawData.
|
|
271
341
|
def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
|
|
272
|
-
def hook(self,
|
|
342
|
+
def hook(self, data, *args, **kwargs):
|
|
273
343
|
socketdata = records.get_socket_data(self, request=True)
|
|
274
344
|
if socketdata:
|
|
275
345
|
logger().info(
|
|
276
|
-
f"SEND - self={self} id={id(self)} socketdata={socketdata} bytes={(b''+
|
|
346
|
+
f"SEND - self={self} id={id(self)} socketdata={socketdata} bytes={(b''+data)[:20]} args={args} kwargs={kwargs}"
|
|
277
347
|
)
|
|
278
348
|
|
|
279
|
-
|
|
280
|
-
size = method(self, bytes, *args, **kwargs)
|
|
281
|
-
except Exception as ex:
|
|
282
|
-
if socketdata and socketdata.record:
|
|
283
|
-
socketdata.record.exception = ex
|
|
284
|
-
raise
|
|
349
|
+
size = method(self, data, *args, **kwargs)
|
|
285
350
|
|
|
286
351
|
if socketdata:
|
|
287
352
|
if socketdata.record:
|
|
288
|
-
socketdata.record.
|
|
353
|
+
socketdata.record.send_data(data[:size])
|
|
289
354
|
else:
|
|
290
|
-
socketdata.rawdata +=
|
|
355
|
+
socketdata.rawdata += data[:size]
|
|
291
356
|
http_detected = socketdata.http_detected()
|
|
292
357
|
if http_detected:
|
|
293
358
|
with httpdbg_initiator(
|
|
294
|
-
records,
|
|
359
|
+
records,
|
|
360
|
+
traceback.extract_stack(),
|
|
361
|
+
method,
|
|
362
|
+
self,
|
|
363
|
+
data,
|
|
364
|
+
*args,
|
|
365
|
+
**kwargs,
|
|
295
366
|
) as initiator_and_group:
|
|
296
367
|
initiator, group, is_new = initiator_and_group
|
|
297
368
|
if is_new:
|
|
@@ -303,8 +374,9 @@ def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
|
|
|
303
374
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
304
375
|
socketdata.record.address = socketdata.address
|
|
305
376
|
socketdata.record.ssl = socketdata.ssl
|
|
306
|
-
socketdata.record.
|
|
307
|
-
records.
|
|
377
|
+
socketdata.record.send_data(socketdata.rawdata)
|
|
378
|
+
if records.client:
|
|
379
|
+
records.requests[socketdata.record.id] = socketdata.record
|
|
308
380
|
elif http_detected is False: # if None, there is nothing to do
|
|
309
381
|
records._sockets[id(self)] = None
|
|
310
382
|
return size
|
|
@@ -349,22 +421,23 @@ def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
|
|
|
349
421
|
if socketdata:
|
|
350
422
|
logger().info(f"WRITE * - socketdata={socketdata}")
|
|
351
423
|
|
|
352
|
-
|
|
353
|
-
size = method(self, buf, *args, **kwargs)
|
|
354
|
-
except Exception as ex:
|
|
355
|
-
if socketdata and socketdata.record:
|
|
356
|
-
socketdata.record.exception = ex
|
|
357
|
-
raise
|
|
424
|
+
size = method(self, buf, *args, **kwargs)
|
|
358
425
|
|
|
359
426
|
if socketdata:
|
|
360
427
|
if socketdata.record:
|
|
361
|
-
socketdata.record.
|
|
428
|
+
socketdata.record.send_data(bytes(buf[:size]))
|
|
362
429
|
else:
|
|
363
430
|
socketdata.rawdata += bytes(buf[:size])
|
|
364
431
|
http_detected = socketdata.http_detected()
|
|
365
432
|
if http_detected:
|
|
366
433
|
with httpdbg_initiator(
|
|
367
|
-
records,
|
|
434
|
+
records,
|
|
435
|
+
traceback.extract_stack(),
|
|
436
|
+
method,
|
|
437
|
+
self,
|
|
438
|
+
buf,
|
|
439
|
+
*args,
|
|
440
|
+
**kwargs,
|
|
368
441
|
) as initiator_and_group:
|
|
369
442
|
initiator, group, is_new = initiator_and_group
|
|
370
443
|
if is_new:
|
|
@@ -376,8 +449,9 @@ def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
|
|
|
376
449
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
377
450
|
socketdata.record.address = socketdata.address
|
|
378
451
|
socketdata.record.ssl = socketdata.ssl
|
|
379
|
-
socketdata.record.
|
|
380
|
-
records.
|
|
452
|
+
socketdata.record.send_data(socketdata.rawdata)
|
|
453
|
+
if records.client:
|
|
454
|
+
records.requests[socketdata.record.id] = socketdata.record
|
|
381
455
|
elif http_detected is False: # if None, there is nothing to do
|
|
382
456
|
records.sockets[id(self)] = None
|
|
383
457
|
return size
|
|
@@ -395,20 +469,14 @@ def set_hook_for_sslobject_read(records: HTTPRecords, method: Callable):
|
|
|
395
469
|
if socketdata:
|
|
396
470
|
logger().info(f"READ * - socketdata={socketdata}")
|
|
397
471
|
|
|
398
|
-
|
|
399
|
-
r = method(self, *args, **kwargs)
|
|
400
|
-
except Exception as ex:
|
|
401
|
-
if not isinstance(ex, ssl.SSLWantReadError):
|
|
402
|
-
if socketdata and socketdata.record:
|
|
403
|
-
socketdata.record.exception = ex
|
|
404
|
-
raise
|
|
472
|
+
r = method(self, *args, **kwargs)
|
|
405
473
|
|
|
406
474
|
if socketdata and socketdata.record:
|
|
407
475
|
allargs = getcallargs(method, self, *args, **kwargs)
|
|
408
476
|
if allargs.get("buffer"):
|
|
409
|
-
socketdata.record.
|
|
477
|
+
socketdata.record.receive_data(bytes(allargs.get("buffer"))[:r])
|
|
410
478
|
else:
|
|
411
|
-
socketdata.record.
|
|
479
|
+
socketdata.record.receive_data(bytes(r)[: allargs.get("len")])
|
|
412
480
|
|
|
413
481
|
return r
|
|
414
482
|
|
|
@@ -10,6 +10,9 @@ import uuid
|
|
|
10
10
|
from httpdbg.hooks.all import httprecord
|
|
11
11
|
from httpdbg import HTTPRecords
|
|
12
12
|
|
|
13
|
+
HTTPDBG_INITIATORS = [] # type: ignore
|
|
14
|
+
HTTPDBG_RECORD_SERVER = False
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class HttpdbgRecorder:
|
|
15
18
|
"""Records the HTTP requests in subprocesses and saves them to disk."""
|
|
@@ -37,7 +40,9 @@ class HttpdbgRecorder:
|
|
|
37
40
|
|
|
38
41
|
def start(self):
|
|
39
42
|
"""Start recording."""
|
|
40
|
-
self.context = httprecord(
|
|
43
|
+
self.context = httprecord(
|
|
44
|
+
initiators=HTTPDBG_INITIATORS, server=HTTPDBG_RECORD_SERVER
|
|
45
|
+
)
|
|
41
46
|
self.records = self.context.__enter__()
|
|
42
47
|
self._running = True
|
|
43
48
|
self._thread = threading.Thread(target=self._save_to_disk_loop, daemon=True)
|
|
@@ -15,7 +15,7 @@ def getcallargs(original_method, *args, **kwargs):
|
|
|
15
15
|
inspect.signature(original_method).bind_partial(*args, **kwargs).arguments
|
|
16
16
|
)
|
|
17
17
|
except Exception as ex:
|
|
18
|
-
logger().
|
|
18
|
+
logger().debug(f"getcallargs - exception {str(ex)}")
|
|
19
19
|
# TypeError('too many positional arguments') may occur when using pytest (seems related to teardown_method)
|
|
20
20
|
callargs = {}
|
|
21
21
|
i = 0
|