httpdbg 0.28.3__tar.gz → 0.29.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-0.28.3 → httpdbg-0.29.1}/PKG-INFO +2 -2
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/__init__.py +1 -1
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/env.py +1 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/aiohttp.py +5 -4
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/external.py +3 -1
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/httpx.py +10 -8
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/pytest.py +29 -1
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/requests.py +5 -4
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/socket.py +5 -5
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/urllib3.py +5 -4
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/initiator.py +69 -19
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/records.py +22 -24
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/api.py +22 -4
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/api.js +12 -2
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/configuration.js +3 -3
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/filter.js +14 -14
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/index.htm +12 -12
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/main.js +1 -1
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/render.js +17 -11
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/style.css +1 -1
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/table.css +6 -6
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg.egg-info/PKG-INFO +2 -2
- httpdbg-0.29.1/httpdbg.egg-info/requires.txt +3 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/pyproject.toml +1 -1
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_hook_aiohttp.py +3 -15
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_hook_httpx.py +6 -19
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_hook_requests.py +3 -6
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_hook_urllib3.py +32 -30
- httpdbg-0.29.1/tests/test_initiator.py +167 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_mode_module_pytest.py +54 -5
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_mode_script.py +5 -12
- httpdbg-0.28.3/httpdbg.egg-info/requires.txt +0 -3
- httpdbg-0.28.3/tests/test_initiator.py +0 -172
- {httpdbg-0.28.3 → httpdbg-0.29.1}/LICENSE +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/README.md +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/__main__.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/args.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/exception.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/all.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/hooks/utils.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/log.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/mode_console.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/mode_module.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/mode_script.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/preview.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/server.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/utils.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/app.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg.egg-info/SOURCES.txt +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/setup.cfg +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_api.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_cli.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_context_manager.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_mode_console.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_preview.py +0 -0
- {httpdbg-0.28.3 → httpdbg-0.29.1}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.29.1
|
|
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
|
|
@@ -24,7 +24,7 @@ Requires-Python: >=3.9
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Provides-Extra: pytest
|
|
27
|
-
Requires-Dist: pytest-httpdbg>=0.
|
|
27
|
+
Requires-Dist: pytest-httpdbg>=0.6.0; extra == "pytest"
|
|
28
28
|
|
|
29
29
|
# httpdbg
|
|
30
30
|
|
|
@@ -13,20 +13,21 @@ from httpdbg.records import HTTPRecords
|
|
|
13
13
|
|
|
14
14
|
def set_hook_for_aiohttp_async(records: HTTPRecords, method: Callable):
|
|
15
15
|
async def hook(*args, **kwargs):
|
|
16
|
-
|
|
16
|
+
initiator_and_group = None
|
|
17
17
|
try:
|
|
18
18
|
with httpdbg_initiator(
|
|
19
19
|
records, traceback.extract_stack(), method, *args, **kwargs
|
|
20
|
-
) as
|
|
20
|
+
) as initiator_and_group:
|
|
21
21
|
ret = await method(*args, **kwargs)
|
|
22
22
|
return ret
|
|
23
23
|
except Exception as ex:
|
|
24
24
|
callargs = getcallargs(method, *args, **kwargs)
|
|
25
25
|
|
|
26
26
|
if "str_or_url" in callargs:
|
|
27
|
-
if
|
|
27
|
+
if initiator_and_group:
|
|
28
|
+
initiator, group = initiator_and_group
|
|
28
29
|
records.add_new_record_exception(
|
|
29
|
-
initiator, str(callargs["str_or_url"]), ex
|
|
30
|
+
initiator, group, str(callargs["str_or_url"]), ex
|
|
30
31
|
)
|
|
31
32
|
raise
|
|
32
33
|
|
|
@@ -46,8 +46,10 @@ class WatcherSubprocessDirThread(threading.Thread):
|
|
|
46
46
|
def load_dump(self):
|
|
47
47
|
for dump in glob.glob(f"{os.environ[HTTPDBG_SUBPROCESS_DIR]}/*.httpdbgrecords"):
|
|
48
48
|
with open(dump, "rb") as dumpfile:
|
|
49
|
-
newrecords = pickle.load(dumpfile)
|
|
49
|
+
newrecords: HTTPRecords = pickle.load(dumpfile)
|
|
50
50
|
self.records.requests.update(newrecords.requests)
|
|
51
|
+
self.records.initiators.update(newrecords.initiators)
|
|
52
|
+
self.records.groups.update(newrecords.groups)
|
|
51
53
|
|
|
52
54
|
def run(self):
|
|
53
55
|
while not self.stop:
|
|
@@ -13,20 +13,21 @@ from httpdbg.records import HTTPRecords
|
|
|
13
13
|
|
|
14
14
|
def set_hook_for_httpx_async(records: HTTPRecords, method: Callable):
|
|
15
15
|
async def hook(*args, **kwargs):
|
|
16
|
-
|
|
16
|
+
initiator_and_group = None
|
|
17
17
|
try:
|
|
18
18
|
with httpdbg_initiator(
|
|
19
19
|
records, traceback.extract_stack(), method, *args, **kwargs
|
|
20
|
-
) as
|
|
20
|
+
) as initiator_and_group:
|
|
21
21
|
ret = await method(*args, **kwargs)
|
|
22
22
|
return ret
|
|
23
23
|
except Exception as ex:
|
|
24
24
|
callargs = getcallargs(method, *args, **kwargs)
|
|
25
25
|
|
|
26
26
|
if "url" in callargs:
|
|
27
|
-
if
|
|
27
|
+
if initiator_and_group:
|
|
28
|
+
initiator, group = initiator_and_group
|
|
28
29
|
records.add_new_record_exception(
|
|
29
|
-
initiator, str(callargs["url"]), ex
|
|
30
|
+
initiator, group, str(callargs["url"]), ex
|
|
30
31
|
)
|
|
31
32
|
raise
|
|
32
33
|
|
|
@@ -35,20 +36,21 @@ def set_hook_for_httpx_async(records: HTTPRecords, method: Callable):
|
|
|
35
36
|
|
|
36
37
|
def set_hook_for_httpx(records: HTTPRecords, method: Callable):
|
|
37
38
|
def hook(*args, **kwargs):
|
|
38
|
-
|
|
39
|
+
initiator_and_group = None
|
|
39
40
|
try:
|
|
40
41
|
with httpdbg_initiator(
|
|
41
42
|
records, traceback.extract_stack(), method, *args, **kwargs
|
|
42
|
-
) as
|
|
43
|
+
) as initiator_and_group:
|
|
43
44
|
ret = method(*args, **kwargs)
|
|
44
45
|
return ret
|
|
45
46
|
except Exception as ex:
|
|
46
47
|
callargs = getcallargs(method, *args, **kwargs)
|
|
47
48
|
|
|
48
49
|
if "url" in callargs:
|
|
49
|
-
if
|
|
50
|
+
if initiator_and_group:
|
|
51
|
+
initiator, group = initiator_and_group
|
|
50
52
|
records.add_new_record_exception(
|
|
51
|
-
initiator, str(callargs["url"]), ex
|
|
53
|
+
initiator, group, str(callargs["url"]), ex
|
|
52
54
|
)
|
|
53
55
|
raise
|
|
54
56
|
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from contextlib import contextmanager
|
|
3
4
|
from typing import Generator
|
|
4
5
|
|
|
5
6
|
from httpdbg.hooks.utils import decorate
|
|
6
7
|
from httpdbg.hooks.utils import getcallargs
|
|
7
8
|
from httpdbg.hooks.utils import undecorate
|
|
9
|
+
from httpdbg.initiator import httpdbg_group
|
|
8
10
|
from httpdbg.initiator import httpdbg_tag
|
|
11
|
+
from httpdbg.log import logger
|
|
9
12
|
from httpdbg.records import HTTPRecords
|
|
10
13
|
|
|
11
14
|
|
|
12
|
-
def set_hook_for_pytest_fixture(_, method):
|
|
15
|
+
def set_hook_for_pytest_fixture(_: HTTPRecords, method: Callable):
|
|
13
16
|
def hook(*args, **kwargs):
|
|
17
|
+
logger().info("SET_HOOK_FOR_PYTEST_FIXTURE")
|
|
14
18
|
callargs = getcallargs(method, *args, **kwargs)
|
|
15
19
|
|
|
16
20
|
if "fixturefunc" in callargs:
|
|
@@ -22,11 +26,28 @@ def set_hook_for_pytest_fixture(_, method):
|
|
|
22
26
|
return hook
|
|
23
27
|
|
|
24
28
|
|
|
29
|
+
def set_hook_for_pytest_runtest(records: HTTPRecords, method: Callable):
|
|
30
|
+
def hook(*args, **kwargs):
|
|
31
|
+
logger().info("SET_HOOK_FOR_PYTEST_RUNTEST")
|
|
32
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
if "item" in callargs:
|
|
35
|
+
full_label = getattr(callargs["item"], "nodeid", "unknown test")
|
|
36
|
+
label = getattr(callargs["item"], "name", "unknown test")
|
|
37
|
+
with httpdbg_group(records, label, full_label):
|
|
38
|
+
return method(*args, **kwargs)
|
|
39
|
+
else:
|
|
40
|
+
return method(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
return hook
|
|
43
|
+
|
|
44
|
+
|
|
25
45
|
@contextmanager
|
|
26
46
|
def hook_pytest(records: HTTPRecords) -> Generator[None, None, None]:
|
|
27
47
|
hooks = False
|
|
28
48
|
try:
|
|
29
49
|
import _pytest.fixtures
|
|
50
|
+
import _pytest.runner
|
|
30
51
|
|
|
31
52
|
_pytest.fixtures.call_fixture_func = decorate(
|
|
32
53
|
records, _pytest.fixtures.call_fixture_func, set_hook_for_pytest_fixture
|
|
@@ -36,6 +57,11 @@ def hook_pytest(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
36
57
|
_pytest.fixtures._teardown_yield_fixture,
|
|
37
58
|
set_hook_for_pytest_fixture,
|
|
38
59
|
)
|
|
60
|
+
|
|
61
|
+
_pytest.runner.runtestprotocol = decorate(
|
|
62
|
+
records, _pytest.runner.runtestprotocol, set_hook_for_pytest_runtest
|
|
63
|
+
)
|
|
64
|
+
|
|
39
65
|
hooks = True
|
|
40
66
|
except ImportError:
|
|
41
67
|
pass
|
|
@@ -49,3 +75,5 @@ def hook_pytest(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
49
75
|
_pytest.fixtures._teardown_yield_fixture = undecorate(
|
|
50
76
|
_pytest.fixtures._teardown_yield_fixture
|
|
51
77
|
)
|
|
78
|
+
|
|
79
|
+
_pytest.runner.runtestprotocol = undecorate(_pytest.runner.runtestprotocol)
|
|
@@ -13,20 +13,21 @@ from httpdbg.records import HTTPRecords
|
|
|
13
13
|
|
|
14
14
|
def set_hook_for_requests(records: HTTPRecords, method: Callable):
|
|
15
15
|
def hook(*args, **kwargs):
|
|
16
|
-
|
|
16
|
+
initiator_and_group = None
|
|
17
17
|
try:
|
|
18
18
|
with httpdbg_initiator(
|
|
19
19
|
records, traceback.extract_stack(), method, *args, **kwargs
|
|
20
|
-
) as
|
|
20
|
+
) as initiator_and_group:
|
|
21
21
|
ret = method(*args, **kwargs)
|
|
22
22
|
return ret
|
|
23
23
|
except Exception as ex:
|
|
24
24
|
callargs = getcallargs(method, *args, **kwargs)
|
|
25
25
|
|
|
26
26
|
if "url" in callargs:
|
|
27
|
-
if
|
|
27
|
+
if initiator_and_group:
|
|
28
|
+
initiator, group = initiator_and_group
|
|
28
29
|
records.add_new_record_exception(
|
|
29
|
-
initiator, str(callargs["url"]), ex
|
|
30
|
+
initiator, group, str(callargs["url"]), ex
|
|
30
31
|
)
|
|
31
32
|
raise
|
|
32
33
|
|
|
@@ -206,7 +206,7 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
|
|
|
206
206
|
if http_detected:
|
|
207
207
|
logger().info("SENDALL - http detected")
|
|
208
208
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
209
|
-
socketdata.record.
|
|
209
|
+
socketdata.record.initiator_id = records.get_initiator()
|
|
210
210
|
socketdata.record.address = socketdata.address
|
|
211
211
|
socketdata.record.ssl = socketdata.ssl
|
|
212
212
|
socketdata.record.request.rawdata = socketdata.rawdata
|
|
@@ -251,7 +251,7 @@ def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
|
|
|
251
251
|
http_detected = socketdata.http_detected()
|
|
252
252
|
if http_detected:
|
|
253
253
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
254
|
-
socketdata.record.
|
|
254
|
+
socketdata.record.initiator_id = records.get_initiator()
|
|
255
255
|
socketdata.record.address = socketdata.address
|
|
256
256
|
socketdata.record.ssl = socketdata.ssl
|
|
257
257
|
socketdata.record.request.rawdata = socketdata.rawdata
|
|
@@ -315,7 +315,7 @@ def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
|
|
|
315
315
|
http_detected = socketdata.http_detected()
|
|
316
316
|
if http_detected:
|
|
317
317
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
318
|
-
socketdata.record.
|
|
318
|
+
socketdata.record.initiator_id = records.get_initiator()
|
|
319
319
|
socketdata.record.address = socketdata.address
|
|
320
320
|
socketdata.record.ssl = socketdata.ssl
|
|
321
321
|
socketdata.record.request.rawdata = socketdata.rawdata
|
|
@@ -413,7 +413,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
413
413
|
)
|
|
414
414
|
|
|
415
415
|
# only for async HTTP requests (not HTTPS) on Windows
|
|
416
|
-
if
|
|
416
|
+
if platform.system().lower() == "windows":
|
|
417
417
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received = decorate( # type: ignore
|
|
418
418
|
records,
|
|
419
419
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received, # type: ignore
|
|
@@ -455,7 +455,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
455
455
|
)
|
|
456
456
|
|
|
457
457
|
# only for async HTTP requests (not HTTPS) on Windows
|
|
458
|
-
if
|
|
458
|
+
if platform.system().lower() == "windows":
|
|
459
459
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received = undecorate( # type: ignore
|
|
460
460
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received # type: ignore
|
|
461
461
|
)
|
|
@@ -13,20 +13,21 @@ from httpdbg.records import HTTPRecords
|
|
|
13
13
|
|
|
14
14
|
def set_hook_for_urllib3(records: HTTPRecords, method: Callable):
|
|
15
15
|
def hook(*args, **kwargs):
|
|
16
|
-
|
|
16
|
+
initiator_and_group = None
|
|
17
17
|
try:
|
|
18
18
|
with httpdbg_initiator(
|
|
19
19
|
records, traceback.extract_stack(), method, *args, **kwargs
|
|
20
|
-
) as
|
|
20
|
+
) as initiator_and_group:
|
|
21
21
|
ret = method(*args, **kwargs)
|
|
22
22
|
return ret
|
|
23
23
|
except Exception as ex:
|
|
24
24
|
callargs = getcallargs(method, *args, **kwargs)
|
|
25
25
|
|
|
26
26
|
if "url" in callargs:
|
|
27
|
-
if
|
|
27
|
+
if initiator_and_group:
|
|
28
|
+
initiator, group = initiator_and_group
|
|
28
29
|
records.add_new_record_exception(
|
|
29
|
-
initiator, str(callargs["url"]), ex
|
|
30
|
+
initiator, group, str(callargs["url"]), ex
|
|
30
31
|
)
|
|
31
32
|
raise
|
|
32
33
|
|
|
@@ -7,7 +7,12 @@ from typing import Generator
|
|
|
7
7
|
from typing import List
|
|
8
8
|
from typing import Tuple
|
|
9
9
|
from typing import Union
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from httpdbg.records import HTTPRecords
|
|
14
|
+
|
|
15
|
+
from httpdbg.env import HTTPDBG_CURRENT_GROUP
|
|
11
16
|
from httpdbg.env import HTTPDBG_CURRENT_INITIATOR
|
|
12
17
|
from httpdbg.env import HTTPDBG_CURRENT_TAG
|
|
13
18
|
from httpdbg.hooks.utils import getcallargs
|
|
@@ -15,26 +20,22 @@ from httpdbg.utils import get_new_uuid
|
|
|
15
20
|
from httpdbg.log import logger
|
|
16
21
|
|
|
17
22
|
|
|
18
|
-
class Initiator
|
|
23
|
+
class Initiator:
|
|
19
24
|
def __init__(
|
|
20
25
|
self,
|
|
21
|
-
|
|
22
|
-
short_label: str,
|
|
23
|
-
long_label: Union[str, None],
|
|
26
|
+
label: str,
|
|
24
27
|
short_stack: str,
|
|
25
28
|
stack: List[str],
|
|
26
29
|
):
|
|
27
|
-
self.id =
|
|
28
|
-
self.
|
|
29
|
-
self.long_label = long_label
|
|
30
|
+
self.id = get_new_uuid()
|
|
31
|
+
self.label = label
|
|
30
32
|
self.short_stack = short_stack
|
|
31
33
|
self.stack = stack
|
|
32
34
|
|
|
33
35
|
def __eq__(self, other) -> bool:
|
|
34
36
|
if type(other) is Initiator:
|
|
35
37
|
return (
|
|
36
|
-
self.
|
|
37
|
-
and self.long_label == other.long_label
|
|
38
|
+
self.label == other.label
|
|
38
39
|
and self.short_stack == other.short_stack
|
|
39
40
|
and self.stack == other.stack
|
|
40
41
|
)
|
|
@@ -45,21 +46,29 @@ class Initiator(object):
|
|
|
45
46
|
if full:
|
|
46
47
|
json = {
|
|
47
48
|
"id": self.id,
|
|
48
|
-
"
|
|
49
|
-
"long_label": self.long_label,
|
|
49
|
+
"label": self.label,
|
|
50
50
|
"short_stack": self.short_stack,
|
|
51
51
|
"stack": "\n".join(self.stack),
|
|
52
52
|
}
|
|
53
53
|
else:
|
|
54
54
|
json = {
|
|
55
55
|
"id": self.id,
|
|
56
|
-
"
|
|
57
|
-
"long_label": self.long_label,
|
|
56
|
+
"label": self.label,
|
|
58
57
|
"short_stack": self.short_stack,
|
|
59
58
|
}
|
|
60
59
|
return json
|
|
61
60
|
|
|
62
61
|
|
|
62
|
+
class Group:
|
|
63
|
+
def __init__(self, label: str, full_label: str):
|
|
64
|
+
self.id: str = get_new_uuid()
|
|
65
|
+
self.label: str = label
|
|
66
|
+
self.full_label: str = full_label
|
|
67
|
+
|
|
68
|
+
def to_json(self) -> dict:
|
|
69
|
+
return {"id": self.id, "label": self.label, "full_label": self.full_label}
|
|
70
|
+
|
|
71
|
+
|
|
63
72
|
def compatible_path(path: str) -> str:
|
|
64
73
|
p = path
|
|
65
74
|
if platform.system().lower() == "windows":
|
|
@@ -185,7 +194,7 @@ def extract_short_stack_from_file(
|
|
|
185
194
|
@contextmanager
|
|
186
195
|
def httpdbg_initiator(
|
|
187
196
|
records, extracted_stack: traceback.StackSummary, original_method, *args, **kwargs
|
|
188
|
-
) -> Generator[Union[Initiator, None], None, None]:
|
|
197
|
+
) -> Generator[Union[Tuple[Initiator, Group], None], None, None]:
|
|
189
198
|
envname = f"{HTTPDBG_CURRENT_INITIATOR}_{records.id}"
|
|
190
199
|
|
|
191
200
|
if not os.environ.get(envname):
|
|
@@ -205,15 +214,16 @@ def httpdbg_initiator(
|
|
|
205
214
|
short_stack += f" {k}={v}\n"
|
|
206
215
|
short_stack += ")"
|
|
207
216
|
|
|
208
|
-
current_initiator = Initiator(
|
|
209
|
-
|
|
210
|
-
)
|
|
211
|
-
records._initiators[current_initiator.id] = current_initiator
|
|
217
|
+
current_initiator = Initiator(instruction, short_stack, stack)
|
|
218
|
+
records.initiators[current_initiator.id] = current_initiator
|
|
212
219
|
|
|
213
220
|
os.environ[envname] = current_initiator.id
|
|
214
221
|
|
|
215
222
|
try:
|
|
216
|
-
|
|
223
|
+
with httpdbg_group(
|
|
224
|
+
records, instruction, short_stack
|
|
225
|
+
) as group: # by default we group the requests by initiator
|
|
226
|
+
yield current_initiator, group
|
|
217
227
|
except Exception:
|
|
218
228
|
del os.environ[envname]
|
|
219
229
|
raise
|
|
@@ -233,6 +243,7 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
|
|
|
233
243
|
os.environ[HTTPDBG_CURRENT_TAG] = tag
|
|
234
244
|
|
|
235
245
|
try:
|
|
246
|
+
logger().info(f"httpdbg_tag {tag}")
|
|
236
247
|
yield
|
|
237
248
|
except Exception:
|
|
238
249
|
if (not tag_already_set) and (HTTPDBG_CURRENT_TAG in os.environ):
|
|
@@ -241,3 +252,42 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
|
|
|
241
252
|
|
|
242
253
|
if (not tag_already_set) and (HTTPDBG_CURRENT_TAG in os.environ):
|
|
243
254
|
del os.environ[HTTPDBG_CURRENT_TAG]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@contextmanager
|
|
258
|
+
def httpdbg_group(
|
|
259
|
+
records: "HTTPRecords", label: str, full_label: str
|
|
260
|
+
) -> Generator[Group, None, None]:
|
|
261
|
+
|
|
262
|
+
# A group is considered set if the environment variable exists and the group
|
|
263
|
+
# is recorded.If the environment variable exists but the group is not recorded,
|
|
264
|
+
# it means httpdbg was called reentrantly, and we need to record the group again
|
|
265
|
+
# using the same id.
|
|
266
|
+
group_already_set = (HTTPDBG_CURRENT_GROUP in os.environ) and (
|
|
267
|
+
os.environ[HTTPDBG_CURRENT_GROUP] in records.groups
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if not group_already_set:
|
|
271
|
+
logger().info("httpdbg_group (new)")
|
|
272
|
+
group = Group(label, full_label)
|
|
273
|
+
# in case of a reentrant call to httpdbg, we force the group id to be the same
|
|
274
|
+
if HTTPDBG_CURRENT_GROUP in os.environ:
|
|
275
|
+
group.id = os.environ[HTTPDBG_CURRENT_GROUP]
|
|
276
|
+
logger().info("httpdbg_group (reentrant) keep id {group.id}")
|
|
277
|
+
records.groups[group.id] = group
|
|
278
|
+
os.environ[HTTPDBG_CURRENT_GROUP] = group.id
|
|
279
|
+
else:
|
|
280
|
+
group = records.groups[os.environ[HTTPDBG_CURRENT_GROUP]]
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
logger().info(
|
|
284
|
+
f"httpdbg_group {records.groups} group_id={os.environ[HTTPDBG_CURRENT_GROUP]} label={label} full_label={full_label}"
|
|
285
|
+
)
|
|
286
|
+
yield group
|
|
287
|
+
except Exception:
|
|
288
|
+
if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
|
|
289
|
+
del os.environ[HTTPDBG_CURRENT_GROUP]
|
|
290
|
+
raise
|
|
291
|
+
|
|
292
|
+
if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
|
|
293
|
+
del os.environ[HTTPDBG_CURRENT_GROUP]
|
|
@@ -9,11 +9,13 @@ import traceback
|
|
|
9
9
|
from urllib.parse import urlparse
|
|
10
10
|
from typing import Dict, List, Tuple, Union
|
|
11
11
|
|
|
12
|
+
from httpdbg.env import HTTPDBG_CURRENT_GROUP
|
|
12
13
|
from httpdbg.env import HTTPDBG_CURRENT_INITIATOR
|
|
13
14
|
from httpdbg.env import HTTPDBG_CURRENT_TAG
|
|
14
15
|
from httpdbg.utils import HTTPDBGCookie
|
|
15
16
|
from httpdbg.utils import HTTPDBGHeader
|
|
16
17
|
from httpdbg.initiator import in_lib
|
|
18
|
+
from httpdbg.initiator import Group
|
|
17
19
|
from httpdbg.initiator import Initiator
|
|
18
20
|
from httpdbg.preview import generate_preview
|
|
19
21
|
from httpdbg.utils import get_new_uuid
|
|
@@ -214,13 +216,14 @@ class HTTPRecord:
|
|
|
214
216
|
self.id = get_new_uuid()
|
|
215
217
|
self.address: Tuple[str, int] = ("", 0)
|
|
216
218
|
self._url: Union[str, None] = None
|
|
217
|
-
self.
|
|
219
|
+
self.initiator_id: Union[str, None] = None
|
|
218
220
|
self.exception: Union[Exception, None] = None
|
|
219
221
|
self.request: HTTPRecordRequest = HTTPRecordRequest()
|
|
220
222
|
self.response: HTTPRecordResponse = HTTPRecordResponse()
|
|
221
223
|
self.ssl: Union[bool, None] = None
|
|
222
224
|
self.tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
|
|
223
|
-
self.tag = os.environ.get(HTTPDBG_CURRENT_TAG)
|
|
225
|
+
self.tag: Union[str, None] = os.environ.get(HTTPDBG_CURRENT_TAG)
|
|
226
|
+
self.group_id: Union[str, None] = os.environ.get(HTTPDBG_CURRENT_GROUP)
|
|
224
227
|
if tbegin:
|
|
225
228
|
self.tbegin = tbegin
|
|
226
229
|
|
|
@@ -295,10 +298,12 @@ class HTTPRecords:
|
|
|
295
298
|
self.reset()
|
|
296
299
|
|
|
297
300
|
def reset(self) -> None:
|
|
301
|
+
logger().info("HTTPRecords.reset")
|
|
298
302
|
self.id = get_new_uuid()
|
|
299
303
|
self.requests: Dict[str, HTTPRecord] = {}
|
|
300
304
|
self.requests_already_loaded = 0
|
|
301
|
-
self.
|
|
305
|
+
self.initiators: Dict[str, Initiator] = {}
|
|
306
|
+
self.groups: Dict[str, Group] = {}
|
|
302
307
|
self._sockets: Dict[int, SocketRawData] = {}
|
|
303
308
|
|
|
304
309
|
@property
|
|
@@ -311,11 +316,11 @@ class HTTPRecords:
|
|
|
311
316
|
def __len__(self) -> int:
|
|
312
317
|
return len(self.requests)
|
|
313
318
|
|
|
314
|
-
def get_initiator(self) ->
|
|
319
|
+
def get_initiator(self) -> str:
|
|
315
320
|
envname = f"{HTTPDBG_CURRENT_INITIATOR}_{self.id}"
|
|
316
321
|
|
|
317
322
|
if envname in os.environ:
|
|
318
|
-
initiator = self.
|
|
323
|
+
initiator = self.initiators[os.environ[envname]]
|
|
319
324
|
else:
|
|
320
325
|
fullstack = traceback.format_stack()
|
|
321
326
|
stack: List[str] = []
|
|
@@ -324,23 +329,13 @@ class HTTPRecords:
|
|
|
324
329
|
break
|
|
325
330
|
stack.append(line)
|
|
326
331
|
long_label = stack[-1]
|
|
327
|
-
|
|
328
|
-
initiator = Initiator(
|
|
329
|
-
|
|
330
|
-
if
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
short_label = long_label.split("::")[-1]
|
|
335
|
-
initiator = Initiator(
|
|
336
|
-
f"{long_label}{self.id}",
|
|
337
|
-
short_label,
|
|
338
|
-
long_label,
|
|
339
|
-
initiator.short_stack,
|
|
340
|
-
initiator.stack,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
return initiator
|
|
332
|
+
label = long_label.split("\n")[1]
|
|
333
|
+
initiator = Initiator(label, long_label, stack)
|
|
334
|
+
|
|
335
|
+
if initiator.id not in self.initiators:
|
|
336
|
+
self.initiators[initiator.id] = initiator
|
|
337
|
+
|
|
338
|
+
return initiator.id
|
|
344
339
|
|
|
345
340
|
def get_socket_data(
|
|
346
341
|
self, obj, extra_sock=None, force_new=False, request=None
|
|
@@ -415,11 +410,14 @@ class HTTPRecords:
|
|
|
415
410
|
del self._sockets[id(obj)]
|
|
416
411
|
|
|
417
412
|
def add_new_record_exception(
|
|
418
|
-
self, initiator: Initiator, url: str, exception: Exception
|
|
413
|
+
self, initiator: Initiator, group: Group, url: str, exception: Exception
|
|
419
414
|
) -> HTTPRecord:
|
|
415
|
+
if initiator.id not in self.initiators:
|
|
416
|
+
self.initiators[initiator.id] = initiator
|
|
420
417
|
new_record = HTTPRecord()
|
|
421
418
|
new_record.url = url
|
|
422
|
-
new_record.
|
|
419
|
+
new_record.initiator_id = initiator.id
|
|
420
|
+
new_record.group_id = group.id
|
|
423
421
|
new_record.exception = exception
|
|
424
422
|
self.requests[new_record.id] = new_record
|
|
425
423
|
return new_record
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from json import JSONEncoder
|
|
3
|
+
import traceback
|
|
3
4
|
from typing import Any
|
|
4
5
|
from typing import Dict
|
|
5
6
|
|
|
@@ -21,7 +22,8 @@ class RequestPayload(JSONEncoder):
|
|
|
21
22
|
"response": None,
|
|
22
23
|
"exception": None,
|
|
23
24
|
"tag": req.tag,
|
|
24
|
-
"
|
|
25
|
+
"initiator_id": req.initiator_id,
|
|
26
|
+
"group_id": req.group_id,
|
|
25
27
|
"in_progress": req.in_progress,
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -52,7 +54,11 @@ class RequestPayload(JSONEncoder):
|
|
|
52
54
|
if req.exception is not None:
|
|
53
55
|
payload["exception"] = {
|
|
54
56
|
"type": str(type(req.exception)),
|
|
55
|
-
"message":
|
|
57
|
+
"message": "".join(
|
|
58
|
+
traceback.format_exception(
|
|
59
|
+
type(req.exception), req.exception, req.exception.__traceback__
|
|
60
|
+
)
|
|
61
|
+
),
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
return payload
|
|
@@ -64,7 +70,12 @@ class RequestListPayload(JSONEncoder):
|
|
|
64
70
|
records, HTTPRecords
|
|
65
71
|
), "This encoder works only for HTTPRecords object."
|
|
66
72
|
|
|
67
|
-
payload = {
|
|
73
|
+
payload = {
|
|
74
|
+
"id": records.id,
|
|
75
|
+
"requests": {},
|
|
76
|
+
"initiators": {},
|
|
77
|
+
"groups": {},
|
|
78
|
+
}
|
|
68
79
|
|
|
69
80
|
for id, req in records.requests.items():
|
|
70
81
|
payload["requests"][id] = {
|
|
@@ -76,10 +87,17 @@ class RequestListPayload(JSONEncoder):
|
|
|
76
87
|
"reason": req.reason,
|
|
77
88
|
"verb": req.method,
|
|
78
89
|
"tag": req.tag,
|
|
79
|
-
"
|
|
90
|
+
"initiator_id": req.initiator_id,
|
|
91
|
+
"group_id": req.group_id,
|
|
80
92
|
"in_progress": req.in_progress,
|
|
81
93
|
"tbegin": req.tbegin.isoformat(),
|
|
82
94
|
"last_update": req.last_update.isoformat(),
|
|
83
95
|
}
|
|
84
96
|
|
|
97
|
+
for id, initiator in records.initiators.items():
|
|
98
|
+
payload["initiators"][id] = initiator.to_json()
|
|
99
|
+
|
|
100
|
+
for id, group in records.groups.items():
|
|
101
|
+
payload["groups"][id] = group.to_json()
|
|
102
|
+
|
|
85
103
|
return payload
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
const global = {
|
|
4
4
|
"k7": null,
|
|
5
5
|
"requests": {},
|
|
6
|
+
"initiators": {},
|
|
7
|
+
"groups": {},
|
|
6
8
|
"connected": false,
|
|
7
|
-
"
|
|
9
|
+
"group_collapse": []
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
function save_request(request_id, request) {
|
|
@@ -18,6 +20,8 @@ function save_request(request_id, request) {
|
|
|
18
20
|
}
|
|
19
21
|
global.requests[request_id] = request;
|
|
20
22
|
|
|
23
|
+
global.requests[request_id].initiator = global.initiators[request.initiator_id];
|
|
24
|
+
|
|
21
25
|
if (global.requests[request_id].in_progress) {
|
|
22
26
|
global.requests[request_id].status_code_view = '<img class="icon" src="static/icons/wait-sandclock-icon.svg-+-$**HTTPDBG_VERSION**$" alt="loading"/>';
|
|
23
27
|
} else {
|
|
@@ -62,6 +66,11 @@ async function get_all_requests() {
|
|
|
62
66
|
clean();
|
|
63
67
|
};
|
|
64
68
|
|
|
69
|
+
// for the initiators and the groups, we can just save them without any verification
|
|
70
|
+
Object.assign(global.initiators, data.initiators);
|
|
71
|
+
Object.assign(global.groups, data.groups);
|
|
72
|
+
|
|
73
|
+
// for the requests, we may have to update them
|
|
65
74
|
for (const [request_id, request] of Object.entries(data.requests)) {
|
|
66
75
|
if (!(request_id in global.requests)) {
|
|
67
76
|
// this is a new request
|
|
@@ -109,7 +118,8 @@ async function get_request(request_id) {
|
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
// the full stack is not present in request summary
|
|
112
|
-
global.requests[request_id].
|
|
121
|
+
global.requests[request_id].initiator_id = data.initiator_id;
|
|
122
|
+
global.requests[request_id].exception = data.exception;
|
|
113
123
|
|
|
114
124
|
global.requests[request_id].to_refresh = true;
|
|
115
125
|
|