httpdbg 0.28.2__tar.gz → 0.29.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.28.2 → httpdbg-0.29.0}/PKG-INFO +2 -2
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/__init__.py +1 -1
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/env.py +1 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/aiohttp.py +5 -9
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/external.py +3 -1
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/httpx.py +9 -17
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/pytest.py +29 -1
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/requests.py +5 -9
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/socket.py +66 -46
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/urllib3.py +5 -9
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/initiator.py +55 -16
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/records.py +32 -22
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/api.py +16 -3
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/api.js +11 -2
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/configuration.js +3 -3
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/filter.js +14 -14
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/index.htm +10 -10
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/main.js +1 -1
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/render.js +17 -11
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/style.css +1 -1
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/table.css +6 -6
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg.egg-info/PKG-INFO +2 -2
- httpdbg-0.29.0/httpdbg.egg-info/requires.txt +3 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/pyproject.toml +1 -1
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_hook_aiohttp.py +3 -15
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_hook_httpx.py +6 -19
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_hook_requests.py +3 -6
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_hook_urllib3.py +32 -30
- httpdbg-0.29.0/tests/test_initiator.py +167 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_mode_module_pytest.py +54 -5
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_mode_script.py +5 -12
- httpdbg-0.28.2/httpdbg.egg-info/requires.txt +0 -3
- httpdbg-0.28.2/tests/test_initiator.py +0 -172
- {httpdbg-0.28.2 → httpdbg-0.29.0}/LICENSE +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/README.md +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/__main__.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/args.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/exception.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/all.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/hooks/utils.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/log.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/mode_console.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/mode_module.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/mode_script.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/preview.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/server.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/utils.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/app.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg.egg-info/SOURCES.txt +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/setup.cfg +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_api.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_cli.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_context_manager.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_mode_console.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/tests/test_preview.py +0 -0
- {httpdbg-0.28.2 → httpdbg-0.29.0}/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.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
|
|
@@ -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
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from contextlib import contextmanager
|
|
3
4
|
import traceback
|
|
4
5
|
from typing import Generator
|
|
@@ -7,11 +8,10 @@ from httpdbg.hooks.utils import getcallargs
|
|
|
7
8
|
from httpdbg.hooks.utils import decorate
|
|
8
9
|
from httpdbg.hooks.utils import undecorate
|
|
9
10
|
from httpdbg.initiator import httpdbg_initiator
|
|
10
|
-
from httpdbg.records import HTTPRecord
|
|
11
11
|
from httpdbg.records import HTTPRecords
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def set_hook_for_aiohttp_async(records, method):
|
|
14
|
+
def set_hook_for_aiohttp_async(records: HTTPRecords, method: Callable):
|
|
15
15
|
async def hook(*args, **kwargs):
|
|
16
16
|
initiator = None
|
|
17
17
|
try:
|
|
@@ -25,13 +25,9 @@ def set_hook_for_aiohttp_async(records, method):
|
|
|
25
25
|
|
|
26
26
|
if "str_or_url" in callargs:
|
|
27
27
|
if initiator:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
record.url = str(callargs["str_or_url"])
|
|
32
|
-
record.exception = ex
|
|
33
|
-
|
|
34
|
-
records.requests[record.id] = record
|
|
28
|
+
records.add_new_record_exception(
|
|
29
|
+
initiator, str(callargs["str_or_url"]), ex
|
|
30
|
+
)
|
|
35
31
|
raise
|
|
36
32
|
|
|
37
33
|
return hook
|
|
@@ -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:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from contextlib import contextmanager
|
|
3
4
|
import traceback
|
|
4
5
|
from typing import Generator
|
|
@@ -7,11 +8,10 @@ from httpdbg.hooks.utils import getcallargs
|
|
|
7
8
|
from httpdbg.hooks.utils import decorate
|
|
8
9
|
from httpdbg.hooks.utils import undecorate
|
|
9
10
|
from httpdbg.initiator import httpdbg_initiator
|
|
10
|
-
from httpdbg.records import HTTPRecord
|
|
11
11
|
from httpdbg.records import HTTPRecords
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def set_hook_for_httpx_async(records, method):
|
|
14
|
+
def set_hook_for_httpx_async(records: HTTPRecords, method: Callable):
|
|
15
15
|
async def hook(*args, **kwargs):
|
|
16
16
|
initiator = None
|
|
17
17
|
try:
|
|
@@ -25,19 +25,15 @@ def set_hook_for_httpx_async(records, method):
|
|
|
25
25
|
|
|
26
26
|
if "url" in callargs:
|
|
27
27
|
if initiator:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
record.url = str(callargs["url"])
|
|
32
|
-
record.exception = ex
|
|
33
|
-
|
|
34
|
-
records.requests[record.id] = record
|
|
28
|
+
records.add_new_record_exception(
|
|
29
|
+
initiator, str(callargs["url"]), ex
|
|
30
|
+
)
|
|
35
31
|
raise
|
|
36
32
|
|
|
37
33
|
return hook
|
|
38
34
|
|
|
39
35
|
|
|
40
|
-
def set_hook_for_httpx(records, method):
|
|
36
|
+
def set_hook_for_httpx(records: HTTPRecords, method: Callable):
|
|
41
37
|
def hook(*args, **kwargs):
|
|
42
38
|
initiator = None
|
|
43
39
|
try:
|
|
@@ -51,13 +47,9 @@ def set_hook_for_httpx(records, method):
|
|
|
51
47
|
|
|
52
48
|
if "url" in callargs:
|
|
53
49
|
if initiator:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
record.url = str(callargs["url"])
|
|
58
|
-
record.exception = ex
|
|
59
|
-
|
|
60
|
-
records.requests[record.id] = record
|
|
50
|
+
records.add_new_record_exception(
|
|
51
|
+
initiator, str(callargs["url"]), ex
|
|
52
|
+
)
|
|
61
53
|
raise
|
|
62
54
|
|
|
63
55
|
return hook
|
|
@@ -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)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from contextlib import contextmanager
|
|
3
4
|
import traceback
|
|
4
5
|
from typing import Generator
|
|
@@ -7,11 +8,10 @@ from httpdbg.hooks.utils import getcallargs
|
|
|
7
8
|
from httpdbg.hooks.utils import decorate
|
|
8
9
|
from httpdbg.hooks.utils import undecorate
|
|
9
10
|
from httpdbg.initiator import httpdbg_initiator
|
|
10
|
-
from httpdbg.records import HTTPRecord
|
|
11
11
|
from httpdbg.records import HTTPRecords
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def set_hook_for_requests(records, method):
|
|
14
|
+
def set_hook_for_requests(records: HTTPRecords, method: Callable):
|
|
15
15
|
def hook(*args, **kwargs):
|
|
16
16
|
initiator = None
|
|
17
17
|
try:
|
|
@@ -25,13 +25,9 @@ def set_hook_for_requests(records, method):
|
|
|
25
25
|
|
|
26
26
|
if "url" in callargs:
|
|
27
27
|
if initiator:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
record.url = str(callargs["url"])
|
|
32
|
-
record.exception = ex
|
|
33
|
-
|
|
34
|
-
records.requests[record.id] = record
|
|
28
|
+
records.add_new_record_exception(
|
|
29
|
+
initiator, str(callargs["url"]), ex
|
|
30
|
+
)
|
|
35
31
|
raise
|
|
36
32
|
|
|
37
33
|
return hook
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
import asyncio
|
|
3
3
|
import asyncio.proactor_events
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from contextlib import contextmanager
|
|
5
6
|
import platform
|
|
6
7
|
import socket
|
|
@@ -16,7 +17,10 @@ from httpdbg.records import HTTPRecords
|
|
|
16
17
|
from httpdbg.log import logger
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
# hook: socket.socket.__init__
|
|
21
|
+
# what: A new socket object is created.
|
|
22
|
+
# action: If an entry exists in the temporary raw socket storage list, it is removed.
|
|
23
|
+
def set_hook_for_socket_init(records: HTTPRecords, method: Callable):
|
|
20
24
|
def hook(self, *args, **kwargs):
|
|
21
25
|
records.del_socket_data(self)
|
|
22
26
|
|
|
@@ -25,27 +29,24 @@ def set_hook_for_socket_init(records, method):
|
|
|
25
29
|
return hook
|
|
26
30
|
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
# hook: socket.socket.connect, ssl.SSLSocket.connect
|
|
33
|
+
# what: A connection to a remote socket is initiated.
|
|
34
|
+
# action: A new entry is added to the temporary raw socket storage list.
|
|
35
|
+
def set_hook_for_socket_connect(records: HTTPRecords, method: Callable):
|
|
29
36
|
def hook(self, address):
|
|
30
37
|
socketdata = records.get_socket_data(self, force_new=True)
|
|
31
38
|
if socketdata:
|
|
32
39
|
logger().info(
|
|
33
40
|
f"CONNECT - self={self} id={id(self)} socketdata={socketdata} address={address}"
|
|
34
41
|
)
|
|
35
|
-
|
|
36
42
|
try:
|
|
37
43
|
r = method(self, address)
|
|
38
44
|
except Exception as ex:
|
|
39
45
|
if not isinstance(
|
|
40
46
|
ex, (BlockingIOError, OSError)
|
|
41
47
|
): # BlockingIOError for async, OSError for ipv6
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
record.exception = ex
|
|
46
|
-
|
|
47
|
-
records.requests[record.id] = record
|
|
48
|
-
|
|
48
|
+
initiator = records.get_initiator()
|
|
49
|
+
records.add_new_record_exception(initiator, "", ex)
|
|
49
50
|
raise
|
|
50
51
|
|
|
51
52
|
return r
|
|
@@ -53,18 +54,17 @@ def set_hook_for_socket_connect(records, method):
|
|
|
53
54
|
return hook
|
|
54
55
|
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
# hook: ssl.wrap_socket,
|
|
58
|
+
# what: Takes an instance sock of socket.socket, and returns an instance of ssl.SSLSocket.
|
|
59
|
+
# a subtype of socket.socket, which wraps the underlying socket in an SSL context.
|
|
60
|
+
# action: Link the socket and the sslsocket
|
|
61
|
+
def set_hook_for_ssl_wrap_socket(records: HTTPRecords, method: Callable):
|
|
57
62
|
def hook(sock, *args, **kwargs):
|
|
58
63
|
try:
|
|
59
64
|
sslsocket = method(sock, *args, **kwargs)
|
|
60
65
|
except Exception as ex:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
record.initiator = records.get_initiator()
|
|
64
|
-
record.exception = ex
|
|
65
|
-
|
|
66
|
-
records.requests[record.id] = record
|
|
67
|
-
|
|
66
|
+
initiator = records.get_initiator()
|
|
67
|
+
records.add_new_record_exception(initiator, "", ex)
|
|
68
68
|
raise
|
|
69
69
|
|
|
70
70
|
logger().info(
|
|
@@ -80,18 +80,16 @@ def set_hook_for_ssl_wrap_socket(records, method):
|
|
|
80
80
|
return hook
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
# hook: ssl.SSLContext.wrap_socket
|
|
84
|
+
# what: Wrap an existing Python socket sock and return an instance of SSLContext.sslsocket_class (default SSLSocket).
|
|
85
|
+
# action: Link the socket and the sslsocket
|
|
86
|
+
def set_hook_for_sslcontext_wrap_socket(records: HTTPRecords, method: Callable):
|
|
84
87
|
def hook(self, sock, *args, **kwargs):
|
|
85
88
|
try:
|
|
86
89
|
sslsocket = method(self, sock, *args, **kwargs)
|
|
87
90
|
except Exception as ex:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
record.initiator = records.get_initiator()
|
|
91
|
-
record.exception = ex
|
|
92
|
-
|
|
93
|
-
records.requests[record.id] = record
|
|
94
|
-
|
|
91
|
+
initiator = records.get_initiator()
|
|
92
|
+
records.add_new_record_exception(initiator, "", ex)
|
|
95
93
|
raise
|
|
96
94
|
|
|
97
95
|
logger().info(
|
|
@@ -107,18 +105,16 @@ def set_hook_for_sslcontext_wrap_socket(records, method):
|
|
|
107
105
|
return hook
|
|
108
106
|
|
|
109
107
|
|
|
110
|
-
|
|
108
|
+
# hook: ssl.SSLContext.wrap_bio
|
|
109
|
+
# what: Wrap the BIO objects incoming and outgoing and return an instance of SSLContext.sslobject_class (default SSLObject).
|
|
110
|
+
# action: Record a new SocketRawData if necessary
|
|
111
|
+
def set_hook_for_socket_wrap_bio(records: HTTPRecords, method: Callable):
|
|
111
112
|
def hook(self, *args, **kwargs):
|
|
112
113
|
try:
|
|
113
114
|
sslobject = method(self, *args, **kwargs)
|
|
114
115
|
except Exception as ex:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
record.exception = ex
|
|
119
|
-
|
|
120
|
-
records.requests[record.id] = record
|
|
121
|
-
|
|
116
|
+
initiator = records.get_initiator()
|
|
117
|
+
records.add_new_record_exception(initiator, "", ex)
|
|
122
118
|
raise
|
|
123
119
|
|
|
124
120
|
logger().info(
|
|
@@ -134,7 +130,10 @@ def set_hook_for_socket_wrap_bio(records, method):
|
|
|
134
130
|
return hook
|
|
135
131
|
|
|
136
132
|
|
|
137
|
-
|
|
133
|
+
# hook: socket.socket.recv_into, ssl.SSLSocket.recv_into, asyncio.proactor_events._ProactorReadPipeTransport._data_received
|
|
134
|
+
# what: Receive up to nbytes bytes from the socket, storing the data into a buffer rather than creating a new bytestring.
|
|
135
|
+
# action: Append the data to an existing SocketRawData
|
|
136
|
+
def set_hook_for_socket_recv_into(records: HTTPRecords, method: Callable):
|
|
138
137
|
def hook(self, buffer, *args, **kwargs):
|
|
139
138
|
socketdata = records.get_socket_data(self)
|
|
140
139
|
if socketdata:
|
|
@@ -160,7 +159,10 @@ def set_hook_for_socket_recv_into(records, method):
|
|
|
160
159
|
return hook
|
|
161
160
|
|
|
162
161
|
|
|
163
|
-
|
|
162
|
+
# hook: socket.socket.recv, ssl.SSLSocket.recv
|
|
163
|
+
# what: Receive data from the socket. The return value is a bytes object representing the data received.
|
|
164
|
+
# action: Append the data to an existing SocketRawData
|
|
165
|
+
def set_hook_for_socket_recv(records: HTTPRecords, method: Callable):
|
|
164
166
|
def hook(self, bufsize, *args, **kwargs):
|
|
165
167
|
socketdata = records.get_socket_data(self)
|
|
166
168
|
if socketdata:
|
|
@@ -183,7 +185,11 @@ def set_hook_for_socket_recv(records, method):
|
|
|
183
185
|
return hook
|
|
184
186
|
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
# hook: socket.socket.sendall
|
|
189
|
+
# what: Send data to the socket.
|
|
190
|
+
# action: Append the data to an existing SocketRawData. Check if this is an HTTP request.
|
|
191
|
+
# and record it if this is case, otherwise delete the temporay SocketRawData.
|
|
192
|
+
def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
|
|
187
193
|
def hook(self, bytes, *args, **kwargs):
|
|
188
194
|
socketdata = records.get_socket_data(self, request=True)
|
|
189
195
|
if socketdata:
|
|
@@ -200,7 +206,7 @@ def set_hook_for_socket_sendall(records, method):
|
|
|
200
206
|
if http_detected:
|
|
201
207
|
logger().info("SENDALL - http detected")
|
|
202
208
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
203
|
-
socketdata.record.
|
|
209
|
+
socketdata.record.initiator_id = records.get_initiator()
|
|
204
210
|
socketdata.record.address = socketdata.address
|
|
205
211
|
socketdata.record.ssl = socketdata.ssl
|
|
206
212
|
socketdata.record.request.rawdata = socketdata.rawdata
|
|
@@ -218,7 +224,11 @@ def set_hook_for_socket_sendall(records, method):
|
|
|
218
224
|
return hook
|
|
219
225
|
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
# hook: socket.socket.send, ssl.SSLSocket.send, asyncio.proactor_events._ProactorBaseWritePipeTransport.write
|
|
228
|
+
# what: Send data to the socket.
|
|
229
|
+
# action: Append the data to an existing SocketRawData. Check if this is an HTTP request.
|
|
230
|
+
# and record it if this is case, otherwise delete the temporay SocketRawData.
|
|
231
|
+
def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
|
|
222
232
|
def hook(self, bytes, *args, **kwargs):
|
|
223
233
|
socketdata = records.get_socket_data(self, request=True)
|
|
224
234
|
if socketdata:
|
|
@@ -241,7 +251,7 @@ def set_hook_for_socket_send(records, method):
|
|
|
241
251
|
http_detected = socketdata.http_detected()
|
|
242
252
|
if http_detected:
|
|
243
253
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
244
|
-
socketdata.record.
|
|
254
|
+
socketdata.record.initiator_id = records.get_initiator()
|
|
245
255
|
socketdata.record.address = socketdata.address
|
|
246
256
|
socketdata.record.ssl = socketdata.ssl
|
|
247
257
|
socketdata.record.request.rawdata = socketdata.rawdata
|
|
@@ -253,7 +263,10 @@ def set_hook_for_socket_send(records, method):
|
|
|
253
263
|
return hook
|
|
254
264
|
|
|
255
265
|
|
|
256
|
-
|
|
266
|
+
# hook: asyncio.BaseEventLoop.create_connection
|
|
267
|
+
# what: Open a streaming transport connection to a given address specified by host and port.
|
|
268
|
+
# action: Link the socket and the sslsocket
|
|
269
|
+
def set_hook_for_asyncio_create_connection(records: HTTPRecords, method: Callable):
|
|
257
270
|
async def hook(self, *args, **kwargs):
|
|
258
271
|
logger().info(
|
|
259
272
|
f"CREATE_CONNECTION - self={self} id={id(self)} args={args} kwargs={kwargs}"
|
|
@@ -276,7 +289,11 @@ def set_hook_for_asyncio_create_connection(records, method):
|
|
|
276
289
|
return hook
|
|
277
290
|
|
|
278
291
|
|
|
279
|
-
|
|
292
|
+
# hook: ssl.SSLObject.write
|
|
293
|
+
# what: Write buf to the SSL socket and return the number of bytes written.
|
|
294
|
+
# action: Append the data to an existing SocketRawData. Check if this is an HTTP request.
|
|
295
|
+
# and record it if this is case, otherwise delete the temporay SocketRawData.
|
|
296
|
+
def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
|
|
280
297
|
def hook(self, buf, *args, **kwargs):
|
|
281
298
|
logger().info(f"WRITE - {type(self)}={id(self)} buf={(b'' + buf)[:20]}")
|
|
282
299
|
socketdata = records.get_socket_data(self, request=True)
|
|
@@ -298,7 +315,7 @@ def set_hook_for_sslobject_write(records, method):
|
|
|
298
315
|
http_detected = socketdata.http_detected()
|
|
299
316
|
if http_detected:
|
|
300
317
|
socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
|
|
301
|
-
socketdata.record.
|
|
318
|
+
socketdata.record.initiator_id = records.get_initiator()
|
|
302
319
|
socketdata.record.address = socketdata.address
|
|
303
320
|
socketdata.record.ssl = socketdata.ssl
|
|
304
321
|
socketdata.record.request.rawdata = socketdata.rawdata
|
|
@@ -310,7 +327,10 @@ def set_hook_for_sslobject_write(records, method):
|
|
|
310
327
|
return hook
|
|
311
328
|
|
|
312
329
|
|
|
313
|
-
|
|
330
|
+
# hook: ssl.SSLObject.read
|
|
331
|
+
# what: Read up to len bytes of data from the SSL socket and return the result as a bytes instance.
|
|
332
|
+
# action: Append the data to an existing SocketRawData.
|
|
333
|
+
def set_hook_for_sslobject_read(records: HTTPRecords, method: Callable):
|
|
314
334
|
def hook(self, *args, **kwargs):
|
|
315
335
|
logger().info(f"READ - {type(self)}={id(self)}")
|
|
316
336
|
socketdata = records.get_socket_data(self)
|
|
@@ -393,7 +413,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
393
413
|
)
|
|
394
414
|
|
|
395
415
|
# only for async HTTP requests (not HTTPS) on Windows
|
|
396
|
-
if
|
|
416
|
+
if platform.system().lower() == "windows":
|
|
397
417
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received = decorate( # type: ignore
|
|
398
418
|
records,
|
|
399
419
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received, # type: ignore
|
|
@@ -435,7 +455,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
|
|
|
435
455
|
)
|
|
436
456
|
|
|
437
457
|
# only for async HTTP requests (not HTTPS) on Windows
|
|
438
|
-
if
|
|
458
|
+
if platform.system().lower() == "windows":
|
|
439
459
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received = undecorate( # type: ignore
|
|
440
460
|
asyncio.proactor_events._ProactorReadPipeTransport._data_received # type: ignore
|
|
441
461
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from contextlib import contextmanager
|
|
3
4
|
import traceback
|
|
4
5
|
from typing import Generator
|
|
@@ -7,11 +8,10 @@ from httpdbg.hooks.utils import getcallargs
|
|
|
7
8
|
from httpdbg.hooks.utils import decorate
|
|
8
9
|
from httpdbg.hooks.utils import undecorate
|
|
9
10
|
from httpdbg.initiator import httpdbg_initiator
|
|
10
|
-
from httpdbg.records import HTTPRecord
|
|
11
11
|
from httpdbg.records import HTTPRecords
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def set_hook_for_urllib3(records, method):
|
|
14
|
+
def set_hook_for_urllib3(records: HTTPRecords, method: Callable):
|
|
15
15
|
def hook(*args, **kwargs):
|
|
16
16
|
initiator = None
|
|
17
17
|
try:
|
|
@@ -25,13 +25,9 @@ def set_hook_for_urllib3(records, method):
|
|
|
25
25
|
|
|
26
26
|
if "url" in callargs:
|
|
27
27
|
if initiator:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
record.url = str(callargs["url"])
|
|
32
|
-
record.exception = ex
|
|
33
|
-
|
|
34
|
-
records.requests[record.id] = record
|
|
28
|
+
records.add_new_record_exception(
|
|
29
|
+
initiator, str(callargs["url"]), ex
|
|
30
|
+
)
|
|
35
31
|
raise
|
|
36
32
|
|
|
37
33
|
return hook
|
|
@@ -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,23 @@ 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
26
|
id: str,
|
|
22
|
-
|
|
23
|
-
long_label: Union[str, None],
|
|
27
|
+
label: str,
|
|
24
28
|
short_stack: str,
|
|
25
29
|
stack: List[str],
|
|
26
30
|
):
|
|
27
31
|
self.id = id
|
|
28
|
-
self.
|
|
29
|
-
self.long_label = long_label
|
|
32
|
+
self.label = label
|
|
30
33
|
self.short_stack = short_stack
|
|
31
34
|
self.stack = stack
|
|
32
35
|
|
|
33
36
|
def __eq__(self, other) -> bool:
|
|
34
37
|
if type(other) is Initiator:
|
|
35
38
|
return (
|
|
36
|
-
self.
|
|
37
|
-
and self.long_label == other.long_label
|
|
39
|
+
self.label == other.label
|
|
38
40
|
and self.short_stack == other.short_stack
|
|
39
41
|
and self.stack == other.stack
|
|
40
42
|
)
|
|
@@ -45,21 +47,29 @@ class Initiator(object):
|
|
|
45
47
|
if full:
|
|
46
48
|
json = {
|
|
47
49
|
"id": self.id,
|
|
48
|
-
"
|
|
49
|
-
"long_label": self.long_label,
|
|
50
|
+
"label": self.label,
|
|
50
51
|
"short_stack": self.short_stack,
|
|
51
52
|
"stack": "\n".join(self.stack),
|
|
52
53
|
}
|
|
53
54
|
else:
|
|
54
55
|
json = {
|
|
55
56
|
"id": self.id,
|
|
56
|
-
"
|
|
57
|
-
"long_label": self.long_label,
|
|
57
|
+
"label": self.label,
|
|
58
58
|
"short_stack": self.short_stack,
|
|
59
59
|
}
|
|
60
60
|
return json
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
class Group:
|
|
64
|
+
def __init__(self, id: str, label: str, full_label: str):
|
|
65
|
+
self.id = id
|
|
66
|
+
self.label = label
|
|
67
|
+
self.full_label = full_label
|
|
68
|
+
|
|
69
|
+
def to_json(self) -> dict:
|
|
70
|
+
return {"id": self.id, "label": self.label, "full_label": self.full_label}
|
|
71
|
+
|
|
72
|
+
|
|
63
73
|
def compatible_path(path: str) -> str:
|
|
64
74
|
p = path
|
|
65
75
|
if platform.system().lower() == "windows":
|
|
@@ -205,15 +215,16 @@ def httpdbg_initiator(
|
|
|
205
215
|
short_stack += f" {k}={v}\n"
|
|
206
216
|
short_stack += ")"
|
|
207
217
|
|
|
208
|
-
current_initiator = Initiator(
|
|
209
|
-
|
|
210
|
-
)
|
|
211
|
-
records._initiators[current_initiator.id] = current_initiator
|
|
218
|
+
current_initiator = Initiator(get_new_uuid(), instruction, short_stack, stack)
|
|
219
|
+
records.initiators[current_initiator.id] = current_initiator
|
|
212
220
|
|
|
213
221
|
os.environ[envname] = current_initiator.id
|
|
214
222
|
|
|
215
223
|
try:
|
|
216
|
-
|
|
224
|
+
with httpdbg_group(
|
|
225
|
+
records, instruction, short_stack
|
|
226
|
+
): # by default we group the requests by initiator
|
|
227
|
+
yield current_initiator
|
|
217
228
|
except Exception:
|
|
218
229
|
del os.environ[envname]
|
|
219
230
|
raise
|
|
@@ -233,6 +244,7 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
|
|
|
233
244
|
os.environ[HTTPDBG_CURRENT_TAG] = tag
|
|
234
245
|
|
|
235
246
|
try:
|
|
247
|
+
logger().info(f"httpdbg_tag {tag}")
|
|
236
248
|
yield
|
|
237
249
|
except Exception:
|
|
238
250
|
if (not tag_already_set) and (HTTPDBG_CURRENT_TAG in os.environ):
|
|
@@ -241,3 +253,30 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
|
|
|
241
253
|
|
|
242
254
|
if (not tag_already_set) and (HTTPDBG_CURRENT_TAG in os.environ):
|
|
243
255
|
del os.environ[HTTPDBG_CURRENT_TAG]
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@contextmanager
|
|
259
|
+
def httpdbg_group(
|
|
260
|
+
records: "HTTPRecords", label: str, full_label: str
|
|
261
|
+
) -> Generator[None, None, None]:
|
|
262
|
+
|
|
263
|
+
group_already_set = HTTPDBG_CURRENT_GROUP in os.environ
|
|
264
|
+
|
|
265
|
+
if not group_already_set:
|
|
266
|
+
logger().info("httpdbg_group (new)")
|
|
267
|
+
group_id = get_new_uuid()
|
|
268
|
+
records.groups[group_id] = Group(group_id, label, full_label)
|
|
269
|
+
os.environ[HTTPDBG_CURRENT_GROUP] = group_id
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
logger().info(
|
|
273
|
+
f"httpdbg_group {records.groups} group_id={os.environ[HTTPDBG_CURRENT_GROUP]} label={label} full_label={full_label}"
|
|
274
|
+
)
|
|
275
|
+
yield
|
|
276
|
+
except Exception:
|
|
277
|
+
if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
|
|
278
|
+
del os.environ[HTTPDBG_CURRENT_GROUP]
|
|
279
|
+
raise
|
|
280
|
+
|
|
281
|
+
if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
|
|
282
|
+
del os.environ[HTTPDBG_CURRENT_GROUP]
|