httpdbg 1.2.6__tar.gz → 2.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. {httpdbg-1.2.6 → httpdbg-2.0.1}/PKG-INFO +13 -1
  2. {httpdbg-1.2.6 → httpdbg-2.0.1}/README.md +12 -0
  3. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/__init__.py +1 -2
  4. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/__main__.py +4 -3
  5. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/args.py +8 -4
  6. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/aiohttp.py +1 -3
  7. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/all.py +25 -26
  8. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/external.py +1 -2
  9. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/fastapi.py +14 -11
  10. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/flask.py +10 -7
  11. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/generic.py +7 -14
  12. httpdbg-2.0.1/httpdbg/hooks/h2.py +213 -0
  13. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/http.py +0 -1
  14. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/httpx.py +24 -4
  15. httpdbg-2.0.1/httpdbg/hooks/record.py +179 -0
  16. httpdbg-2.0.1/httpdbg/hooks/recordhttp1.py +197 -0
  17. httpdbg-2.0.1/httpdbg/hooks/recordhttp2.py +113 -0
  18. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/requests.py +1 -3
  19. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/socket.py +182 -47
  20. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/unittest.py +31 -25
  21. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/urllib3.py +1 -3
  22. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/utils.py +3 -1
  23. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/uvicorn.py +19 -13
  24. httpdbg-2.0.1/httpdbg/http_status_code.py +93 -0
  25. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/initiator.py +17 -21
  26. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/mode_console.py +1 -2
  27. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/mode_module.py +1 -2
  28. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/mode_script.py +1 -2
  29. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/preview.py +11 -5
  30. httpdbg-2.0.1/httpdbg/records.py +92 -0
  31. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/server.py +2 -2
  32. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/utils.py +4 -5
  33. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/api.py +3 -4
  34. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/app.py +26 -17
  35. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/PKG-INFO +13 -1
  36. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/SOURCES.txt +6 -0
  37. {httpdbg-1.2.6 → httpdbg-2.0.1}/pyproject.toml +1 -0
  38. httpdbg-2.0.1/tests/test_hook_h2.py +315 -0
  39. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_server_hook_fastapi.py +4 -0
  40. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_server_hook_flask.py +10 -0
  41. httpdbg-1.2.6/httpdbg/records.py +0 -482
  42. {httpdbg-1.2.6 → httpdbg-2.0.1}/LICENSE +0 -0
  43. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/env.py +0 -0
  44. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/exception.py +0 -0
  45. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/__init__.py +0 -0
  46. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/pytest.py +0 -0
  47. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/starlette.py +0 -0
  48. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/hooks/template_sitecustomize.py +0 -0
  49. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/log.py +0 -0
  50. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/__init__.py +0 -0
  51. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/api.js +0 -0
  52. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/column.css +0 -0
  53. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/configuration.css +0 -0
  54. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/configuration.js +0 -0
  55. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/detail.css +0 -0
  56. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/favicon.ico +0 -0
  57. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/filter.js +0 -0
  58. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
  59. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
  60. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
  61. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
  62. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
  63. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
  64. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
  65. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
  66. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
  67. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
  68. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
  69. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
  70. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
  71. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
  72. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
  73. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/index.htm +0 -0
  74. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/main.js +0 -0
  75. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/mustache.min.js +0 -0
  76. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/render.js +0 -0
  77. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/style.css +0 -0
  78. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg/webapp/static/table.css +0 -0
  79. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/dependency_links.txt +0 -0
  80. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/entry_points.txt +0 -0
  81. {httpdbg-1.2.6 → httpdbg-2.0.1}/httpdbg.egg-info/top_level.txt +0 -0
  82. {httpdbg-1.2.6 → httpdbg-2.0.1}/setup.cfg +0 -0
  83. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_api.py +0 -0
  84. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_cli.py +0 -0
  85. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_context_manager.py +0 -0
  86. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_aiohttp.py +0 -0
  87. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_httpx.py +0 -0
  88. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_pytest.py +0 -0
  89. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_requests.py +0 -0
  90. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_unittest.py +0 -0
  91. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_hook_urllib3.py +0 -0
  92. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_initiator.py +0 -0
  93. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_mode_console.py +0 -0
  94. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_mode_module_pytest.py +0 -0
  95. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_mode_script.py +0 -0
  96. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_multiprocess.py +0 -0
  97. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_preview.py +0 -0
  98. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_pyhttpdbg_server.py +0 -0
  99. {httpdbg-1.2.6 → httpdbg-2.0.1}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpdbg
3
- Version: 1.2.6
3
+ Version: 2.0.1
4
4
  Summary: A very simple tool to debug HTTP(S) client and server requests.
5
5
  Author-email: cle-b <cle@tictac.pm>
6
6
  License: Apache-2.0
@@ -32,6 +32,8 @@ To use it, execute your program using the `pyhttpdbg` command instead of `python
32
32
 
33
33
  ![](https://github.com/cle-b/httpdbg/blob/main/ui.png?raw=true)
34
34
 
35
+ Supports `HTTP/1.0`, `HTTP/1.1`, and `HTTP/2`.
36
+
35
37
  Full documentation => [https://httpdbg.readthedocs.io/](https://httpdbg.readthedocs.io/en/latest/)
36
38
 
37
39
  ## installation
@@ -156,6 +158,16 @@ The requests are grouped by tests.
156
158
  | pytest | supported | - | yes |
157
159
  | unittest | supported | - | yes |
158
160
 
161
+ ### supported HTTP version
162
+
163
+ | version | status |
164
+ |----------------|-------------------------------------------------------------|
165
+ | 1.0 | supported |
166
+ | 1.1 | supported |
167
+ | 2 | supported (only through the `h2` lib) |
168
+
169
+
170
+
159
171
  ## configuration
160
172
 
161
173
  No configuration is necessary to start but some few settings are available for particular use.
@@ -6,6 +6,8 @@ To use it, execute your program using the `pyhttpdbg` command instead of `python
6
6
 
7
7
  ![](https://github.com/cle-b/httpdbg/blob/main/ui.png?raw=true)
8
8
 
9
+ Supports `HTTP/1.0`, `HTTP/1.1`, and `HTTP/2`.
10
+
9
11
  Full documentation => [https://httpdbg.readthedocs.io/](https://httpdbg.readthedocs.io/en/latest/)
10
12
 
11
13
  ## installation
@@ -130,6 +132,16 @@ The requests are grouped by tests.
130
132
  | pytest | supported | - | yes |
131
133
  | unittest | supported | - | yes |
132
134
 
135
+ ### supported HTTP version
136
+
137
+ | version | status |
138
+ |----------------|-------------------------------------------------------------|
139
+ | 1.0 | supported |
140
+ | 1.1 | supported |
141
+ | 2 | supported (only through the `h2` lib) |
142
+
143
+
144
+
133
145
  ## configuration
134
146
 
135
147
  No configuration is necessary to start but some few settings are available for particular use.
@@ -1,8 +1,7 @@
1
- # -*- coding: utf-8 -*-
2
1
  from httpdbg.hooks.all import httprecord
3
2
  from httpdbg.records import HTTPRecords
4
3
 
5
4
 
6
- __version__ = "1.2.6"
5
+ __version__ = "2.0.1"
7
6
 
8
7
  __all__ = ["httprecord", "HTTPRecords"]
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  import sys
3
2
  import time
4
3
 
@@ -24,7 +23,8 @@ def pyhttpdbg(params, subparams, test_mode=False):
24
23
 
25
24
  url = f"http://{params.host}:{params.port}/{'?hi=on' if params.console else ''}"
26
25
 
27
- print_msg(f" httpdbg - HTTP(S) requests available at {url}")
26
+ if not params.no_banner:
27
+ print_msg(f" httpdbg - HTTP(S) requests available at {url}")
28
28
 
29
29
  sys.path.insert(0, "") # to mimic the default python command behavior
30
30
 
@@ -39,7 +39,8 @@ def pyhttpdbg(params, subparams, test_mode=False):
39
39
  run_console(records, test_mode)
40
40
 
41
41
  if not (params.force_quit or test_mode):
42
- print_msg(f" httpdbg - HTTP(S) requests available at {url}")
42
+ if not params.no_banner:
43
+ print_msg(f" httpdbg - HTTP(S) requests available at {url}")
43
44
 
44
45
  if params.keep_up:
45
46
  input("Press enter to quit")
@@ -1,12 +1,10 @@
1
- # -*- coding: utf-8 -*-
2
1
  import argparse
3
2
  from pathlib import Path
4
- from typing import List, Tuple
5
3
 
6
4
  from httpdbg.log import LogLevel
7
5
 
8
6
 
9
- def read_args(args: List[str]) -> Tuple[argparse.Namespace, List[str]]:
7
+ def read_args(args: list[str]) -> tuple[argparse.Namespace, list[str]]:
10
8
  httpdbg_args = args
11
9
  client_args = []
12
10
  for action in ["--console", "--module", "-m", "--script"]:
@@ -66,7 +64,13 @@ def read_args(args: List[str]) -> Tuple[argparse.Namespace, List[str]]:
66
64
  help="The log level",
67
65
  )
68
66
 
69
- parser.add_argument("--log", type=Path, help="Path to the log file.")
67
+ parser.add_argument("--log", type=Path, help="path to the log file.")
68
+
69
+ parser.add_argument(
70
+ "--no-banner",
71
+ action="store_true",
72
+ help="do not print the banner in the console",
73
+ )
70
74
 
71
75
  actions = parser.add_mutually_exclusive_group()
72
76
 
@@ -1,7 +1,5 @@
1
- # -*- coding: utf-8 -*-
2
1
  from collections.abc import Callable
3
2
  from contextlib import contextmanager
4
- import traceback
5
3
  from typing import Generator
6
4
 
7
5
  from httpdbg.hooks.utils import getcallargs
@@ -16,7 +14,7 @@ def set_hook_for_aiohttp_async(records: HTTPRecords, method: Callable):
16
14
  initiator_and_group = None
17
15
  try:
18
16
  with httpdbg_initiator(
19
- records, traceback.extract_stack(), method, *args, **kwargs
17
+ records, method, *args, **kwargs
20
18
  ) as initiator_and_group:
21
19
  ret = await method(*args, **kwargs)
22
20
  return ret
@@ -1,8 +1,5 @@
1
- # -*- coding: utf-8 -*-
2
1
  from contextlib import contextmanager
3
2
  from typing import Generator
4
- from typing import List
5
- from typing import Tuple
6
3
  from typing import Union
7
4
 
8
5
  from httpdbg.hooks.aiohttp import hook_aiohttp
@@ -10,6 +7,7 @@ from httpdbg.hooks.external import watcher_external
10
7
  from httpdbg.hooks.fastapi import hook_fastapi
11
8
  from httpdbg.hooks.flask import hook_flask
12
9
  from httpdbg.hooks.generic import hook_generic
10
+ from httpdbg.hooks.h2 import hook_h2
13
11
  from httpdbg.hooks.http import hook_http
14
12
  from httpdbg.hooks.httpx import hook_httpx
15
13
  from httpdbg.hooks.pytest import hook_pytest
@@ -26,10 +24,10 @@ from httpdbg.records import HTTPRecords
26
24
  @contextmanager
27
25
  def httprecord(
28
26
  records: HTTPRecords = None,
29
- initiators: Union[List[str], None] = None,
27
+ initiators: Union[list[str], None] = None,
30
28
  client: bool = True,
31
29
  server: bool = False,
32
- ignore: Union[List[Tuple[str, int]], None] = None,
30
+ ignore: tuple[tuple[str, int], ...] = (),
33
31
  multiprocess: bool = True,
34
32
  ) -> Generator[HTTPRecords, None, None]:
35
33
  if records is None:
@@ -37,25 +35,26 @@ def httprecord(
37
35
 
38
36
  with hook_flask(records):
39
37
  with hook_socket(records):
40
- with hook_fastapi(records):
41
- with hook_starlette(records):
42
- with hook_uvicorn(records):
43
- with hook_http(records):
44
- with hook_httpx(records):
45
- with hook_requests(records):
46
- with hook_urllib3(records):
47
- with hook_aiohttp(records):
48
- with hook_pytest(records):
49
- with hook_unittest(records):
50
- with hook_generic(
51
- records, initiators
52
- ):
53
- if multiprocess:
54
- with watcher_external(
55
- records,
56
- initiators,
57
- server,
58
- ):
38
+ with hook_h2(records):
39
+ with hook_fastapi(records):
40
+ with hook_starlette(records):
41
+ with hook_uvicorn(records):
42
+ with hook_http(records):
43
+ with hook_httpx(records):
44
+ with hook_requests(records):
45
+ with hook_urllib3(records):
46
+ with hook_aiohttp(records):
47
+ with hook_pytest(records):
48
+ with hook_unittest(records):
49
+ with hook_generic(
50
+ records, initiators
51
+ ):
52
+ if multiprocess:
53
+ with watcher_external(
54
+ records,
55
+ initiators,
56
+ server,
57
+ ):
58
+ yield records
59
+ else:
59
60
  yield records
60
- else:
61
- yield records
@@ -7,7 +7,6 @@ import pickle
7
7
  import tempfile
8
8
  import time
9
9
  import threading
10
- from typing import List
11
10
  from typing import Union
12
11
 
13
12
  from httpdbg.env import HTTPDBG_MULTIPROCESS_DIR
@@ -18,7 +17,7 @@ from httpdbg.records import HTTPRecords
18
17
  @contextmanager
19
18
  def watcher_external(
20
19
  records: HTTPRecords,
21
- initiators: Union[List[str], None] = None,
20
+ initiators: Union[list[str], None] = None,
22
21
  server: bool = False,
23
22
  ) -> Generator[HTTPRecords, None, None]:
24
23
  if HTTPDBG_MULTIPROCESS_DIR not in os.environ:
@@ -3,9 +3,7 @@ from collections.abc import Callable
3
3
  from contextlib import contextmanager
4
4
  import functools
5
5
  from functools import wraps
6
- from typing import Dict
7
6
  from typing import Generator
8
- from typing import Union
9
7
 
10
8
  import datetime
11
9
 
@@ -37,7 +35,7 @@ def set_hook_fastapi_endpoint(records: HTTPRecords, method: Callable):
37
35
  def set_hook_fastapi_apirouter_add_api_route(
38
36
  records: HTTPRecords,
39
37
  method: Callable,
40
- already_mapped: Union[Dict[Callable, Callable], None] = None,
38
+ already_mapped: dict[Callable, Callable],
41
39
  ):
42
40
 
43
41
  @wraps(method)
@@ -56,7 +54,9 @@ def set_hook_fastapi_apirouter_add_api_route(
56
54
  else:
57
55
  callargs[param] = already_mapped[original_view_func]
58
56
 
59
- args = [callargs[param] if x == original_view_func else x for x in args]
57
+ args = tuple(
58
+ callargs[param] if x == original_view_func else x for x in args
59
+ )
60
60
  if param in kwargs:
61
61
  kwargs[param] = callargs[param]
62
62
 
@@ -85,12 +85,15 @@ def set_hook_fastapi_app(records: HTTPRecords, method: Callable):
85
85
  *args,
86
86
  **kwargs,
87
87
  ) as group:
88
- if group and (group.id in records.groups):
89
- if socketdata_id and (socketdata_id in records._sockets):
90
- records.groups[group.id].tbegin = records._sockets[
91
- socketdata_id
92
- ].record.tbegin - datetime.timedelta(milliseconds=1)
93
- records._sockets[socketdata_id].record.group_id = group.id
88
+ if group and socketdata_id:
89
+ sock = records._tracerhttp1.sockets.get(socketdata_id)
90
+ if (sock is not None) and (sock.record is not None):
91
+ record = records.groups.get(group.id)
92
+ if record is not None:
93
+ record.tbegin = sock.record.tbegin - datetime.timedelta(
94
+ milliseconds=1
95
+ )
96
+ sock.record.group_id = group.id
94
97
 
95
98
  ret = await method(*args, **kwargs)
96
99
 
@@ -121,7 +124,7 @@ def hook_fastapi(records: HTTPRecords) -> Generator[None, None, None]:
121
124
  try:
122
125
  import fastapi.routing
123
126
 
124
- already_mapped: Dict[Callable, Callable] = {}
127
+ already_mapped: dict[Callable, Callable] = {}
125
128
 
126
129
  set_hook_fastapi_apirouter_add_api_route_with_already_mapped = (
127
130
  functools.partial(
@@ -3,9 +3,8 @@ from collections.abc import Callable
3
3
  from contextlib import contextmanager
4
4
  import functools
5
5
  from functools import wraps
6
- from typing import Dict
7
6
  from typing import Generator
8
- from typing import Union
7
+
9
8
 
10
9
  from httpdbg.hooks.utils import getcallargs
11
10
  from httpdbg.hooks.utils import decorate
@@ -36,7 +35,7 @@ def set_hook_flask_endpoint(records: HTTPRecords, method: Callable):
36
35
  def set_hook_flask_add_url_rule(
37
36
  records: HTTPRecords,
38
37
  method: Callable,
39
- already_mapped: Union[Dict[Callable, Callable], None] = None,
38
+ already_mapped: dict[Callable, Callable],
40
39
  ):
41
40
 
42
41
  def hook(*args, **kwargs):
@@ -55,7 +54,9 @@ def set_hook_flask_add_url_rule(
55
54
  else:
56
55
  callargs[param] = already_mapped[original_view_func]
57
56
 
58
- args = [callargs[param] if x == original_view_func else x for x in args]
57
+ args = tuple(
58
+ callargs[param] if x == original_view_func else x for x in args
59
+ )
59
60
  if param in kwargs:
60
61
  kwargs[param] = callargs[param]
61
62
 
@@ -67,7 +68,7 @@ def set_hook_flask_add_url_rule(
67
68
  def set_hook_flask_register_error_handler(
68
69
  records: HTTPRecords,
69
70
  method: Callable,
70
- already_mapped: Union[Dict[Callable, Callable], None] = None,
71
+ already_mapped: dict[Callable, Callable],
71
72
  ):
72
73
 
73
74
  def hook(*args, **kwargs):
@@ -86,7 +87,9 @@ def set_hook_flask_register_error_handler(
86
87
  else:
87
88
  callargs[param] = already_mapped[original_view_func]
88
89
 
89
- args = [callargs[param] if x == original_view_func else x for x in args]
90
+ args = tuple(
91
+ callargs[param] if x == original_view_func else x for x in args
92
+ )
90
93
  if param in kwargs:
91
94
  kwargs[param] = callargs[param]
92
95
 
@@ -103,7 +106,7 @@ def hook_flask(records: HTTPRecords) -> Generator[None, None, None]:
103
106
 
104
107
  # we must not apply the hook more than once on a mapped endpoint function
105
108
  # AssertionError: View function mapping is overwriting an existing endpoint function: xxxx
106
- already_mapped: Dict[Callable, Callable] = {}
109
+ already_mapped: dict[Callable, Callable] = {}
107
110
 
108
111
  set_hook_flask_add_url_rule_with_already_mapped = functools.partial(
109
112
  set_hook_flask_add_url_rule, already_mapped=already_mapped
@@ -1,15 +1,12 @@
1
- # -*- coding: utf-8 -*-
2
1
  import builtins
3
2
  from collections.abc import Callable
4
3
  from contextlib import contextmanager
5
4
  import functools
6
5
  import importlib
7
6
  import inspect
8
- import traceback
9
7
  from types import ModuleType
10
8
  from typing import Any
11
9
  from typing import Generator
12
- from typing import List
13
10
  from typing import Union
14
11
 
15
12
  from httpdbg.hooks.utils import decorate
@@ -23,9 +20,7 @@ def set_hook_for_generic_async(records: HTTPRecords, method: Any):
23
20
  logger().info(f"SET_HOOK_FOR_GENERIC_ASYNC {method}")
24
21
 
25
22
  async def hook(*args, **kwargs):
26
- with httpdbg_initiator(
27
- records, traceback.extract_stack(), method, *args, **kwargs
28
- ):
23
+ with httpdbg_initiator(records, method, *args, **kwargs):
29
24
  ret = await method(*args, **kwargs)
30
25
 
31
26
  return ret
@@ -37,9 +32,7 @@ def set_hook_for_generic(records: HTTPRecords, method: Any):
37
32
  logger().info(f"SET_HOOK_FOR_GENERIC {method}")
38
33
 
39
34
  def hook(*args, **kwargs):
40
- with httpdbg_initiator(
41
- records, traceback.extract_stack(), method, *args, **kwargs
42
- ):
35
+ with httpdbg_initiator(records, method, *args, **kwargs):
43
36
  ret = method(*args, **kwargs)
44
37
 
45
38
  return ret
@@ -49,13 +42,13 @@ def set_hook_for_generic(records: HTTPRecords, method: Any):
49
42
 
50
43
  @contextmanager
51
44
  def hook_generic(
52
- records: HTTPRecords, initiators: Union[List[str], None] = None
45
+ records: HTTPRecords, initiators: Union[list[str], None] = None
53
46
  ) -> Generator[None, None, None]:
54
47
  if initiators:
55
48
  # we add a hook for a generic initiator only if the module is imported
56
49
 
57
- hooks: List[Callable] = []
58
- already_hooked: List[str] = []
50
+ hooks: list[Callable] = []
51
+ already_hooked: list[str] = []
59
52
 
60
53
  original_builtin_import = builtins.__import__
61
54
 
@@ -130,7 +123,7 @@ def hook_generic(
130
123
  fnc = undecorate(fnc)
131
124
 
132
125
 
133
- def list_callables_from_module(records: HTTPRecords, module: str) -> List[Any]:
126
+ def list_callables_from_module(records: HTTPRecords, module: str) -> list[Any]:
134
127
  callables = []
135
128
 
136
129
  try:
@@ -180,7 +173,7 @@ def list_callables_from_class(
180
173
  imported_module: ModuleType,
181
174
  module: str,
182
175
  classname: str,
183
- ) -> List[Any]:
176
+ ) -> list[Any]:
184
177
  callables = []
185
178
 
186
179
  try:
@@ -0,0 +1,213 @@
1
+ from collections.abc import Callable
2
+ from contextlib import contextmanager
3
+ import random
4
+ from typing import Generator
5
+ from typing import Union
6
+
7
+ from httpdbg.hooks.recordhttp2 import HTTP2Record
8
+ from httpdbg.hooks.utils import decorate
9
+ from httpdbg.hooks.utils import getcallargs
10
+ from httpdbg.hooks.utils import undecorate
11
+ from httpdbg.initiator import httpdbg_initiator
12
+ from httpdbg.log import logger
13
+ from httpdbg.records import HTTPRecords
14
+
15
+
16
+ class TracerHTTP2:
17
+
18
+ def __init__(
19
+ self,
20
+ ignore: tuple[tuple[str, int], ...] = (),
21
+ ):
22
+ self.sockets: dict[str, HTTP2Record] = dict()
23
+ self.ignore: tuple[tuple[str, int], ...] = ignore
24
+
25
+ def send_headers(
26
+ self,
27
+ initiator_id: str,
28
+ group_id: str,
29
+ socket_id: int,
30
+ stream_id: int,
31
+ headers: list[tuple[bytes, bytes]],
32
+ ) -> HTTP2Record:
33
+
34
+ if f"{socket_id}-{stream_id}" in self.sockets:
35
+ record = self.sockets[f"{socket_id}-{stream_id}"]
36
+ if record.is_client:
37
+ # this is a new request: we "close" the previous one
38
+ self.sockets.pop(f"{socket_id}-{stream_id}")
39
+
40
+ record = HTTP2Record(initiator_id, group_id)
41
+ record.request.headers = headers
42
+ self.sockets[f"{socket_id}-{stream_id}"] = record
43
+ else:
44
+ record.response.headers = headers
45
+ else:
46
+ record = HTTP2Record(initiator_id, group_id)
47
+ record.request.headers = headers
48
+ self.sockets[f"{socket_id}-{stream_id}"] = record
49
+
50
+ return record
51
+
52
+ def send_data(
53
+ self,
54
+ socket_id: int,
55
+ stream_id: int,
56
+ data: bytes,
57
+ ):
58
+ record = self.sockets.get(f"{socket_id}-{stream_id}")
59
+ if record:
60
+ record.send_data(data)
61
+
62
+ def receive_headers(
63
+ self,
64
+ initiator_id: str,
65
+ group_id: str,
66
+ socket_id: int,
67
+ stream_id: int,
68
+ headers: list[tuple[bytes, bytes]],
69
+ is_client: bool = True,
70
+ ) -> Union[HTTP2Record, None]:
71
+ record = None
72
+ if not is_client:
73
+ if f"{socket_id}-{stream_id}" in self.sockets:
74
+ # this is a new request received by the server: we "close" the previous one
75
+ self.sockets.pop(f"{socket_id}-{stream_id}")
76
+ record = HTTP2Record(initiator_id, group_id, is_client=False)
77
+ record.request.headers = headers
78
+ self.sockets[f"{socket_id}-{stream_id}"] = record
79
+ else:
80
+ if f"{socket_id}-{stream_id}" in self.sockets:
81
+ record = self.sockets[f"{socket_id}-{stream_id}"]
82
+ record.response.headers = headers
83
+ return record
84
+
85
+ def receive_data(
86
+ self,
87
+ socket_id: int,
88
+ stream_id: int,
89
+ data: bytes,
90
+ ):
91
+ record = self.sockets.get(f"{socket_id}-{stream_id}")
92
+ if record:
93
+ record.receive_data(data)
94
+
95
+
96
+ def set_hook_for_h2_send_headers(records: HTTPRecords, method: Callable):
97
+ def hook(*args, **kwargs):
98
+ callargs = getcallargs(method, *args, **kwargs)
99
+ self = callargs.get("self")
100
+ stream_id = callargs.get("stream_id")
101
+ headers = callargs.get("headers")
102
+
103
+ with httpdbg_initiator(records, method, *args, **kwargs) as initiator_and_group:
104
+ initiator, group, _ = initiator_and_group
105
+ if all(x is not None for x in (self, stream_id, headers)):
106
+ logger().debug(
107
+ f"H2 send_headers - self={id(self)} steam_id={stream_id} headers={headers}"
108
+ )
109
+ record = records._tracerhttp2.send_headers(
110
+ initiator.id, group.id, id(self), stream_id, headers
111
+ )
112
+ if record.is_client and records.client:
113
+ records.requests[record.id] = record
114
+ ret = method(*args, **kwargs)
115
+ return ret
116
+
117
+ return hook
118
+
119
+
120
+ def set_hook_for_h2_send_data(records: HTTPRecords, method: Callable):
121
+ def hook(*args, **kwargs):
122
+ callargs = getcallargs(method, *args, **kwargs)
123
+ self = callargs.get("self")
124
+ stream_id = callargs.get("stream_id")
125
+ data = callargs.get("data")
126
+
127
+ with httpdbg_initiator(records, method, *args, **kwargs):
128
+ if all(x is not None for x in (self, stream_id, data)):
129
+ logger().debug(
130
+ f"H2 send_data - self={id(self)} steam_id={stream_id} data={data[:20]!r}"
131
+ )
132
+ records._tracerhttp2.send_data(id(self), stream_id, data)
133
+ ret = method(*args, **kwargs)
134
+ return ret
135
+
136
+ return hook
137
+
138
+
139
+ def set_hook_for_h2_receive_data(records: HTTPRecords, method: Callable):
140
+ def hook(*args, **kwargs):
141
+ callargs = getcallargs(method, *args, **kwargs)
142
+ self = callargs.get("self")
143
+
144
+ with httpdbg_initiator(records, method, *args, **kwargs) as initiator_and_group:
145
+ initiator, group, _ = initiator_and_group
146
+ ret = method(*args, **kwargs)
147
+
148
+ for event in ret:
149
+ import h2.events
150
+
151
+ if isinstance(event, h2.events.RequestReceived):
152
+ stream_id = event.stream_id
153
+ headers = event.headers
154
+ if all(x is not None for x in (self, stream_id, headers)):
155
+ logger().debug(
156
+ f"H2 receive_data headers RequestReceived {random.random()} - self={id(self)} steam_id={stream_id} headers={headers}"
157
+ )
158
+ record = records._tracerhttp2.receive_headers(
159
+ initiator.id,
160
+ group.id,
161
+ id(self),
162
+ stream_id,
163
+ headers,
164
+ is_client=False,
165
+ )
166
+ if record and (record.is_client is False) and records.server:
167
+ records.requests[record.id] = record
168
+
169
+ elif isinstance(event, h2.events.ResponseReceived):
170
+ stream_id = event.stream_id
171
+ headers = event.headers
172
+ if all(x is not None for x in (self, stream_id, headers)):
173
+ logger().debug(
174
+ f"H2 receive_data headers ResponseReceived {random.random()} - self={id(self)} steam_id={stream_id} headers={headers}"
175
+ )
176
+ records._tracerhttp2.receive_headers(
177
+ initiator.id, group.id, id(self), stream_id, headers
178
+ )
179
+
180
+ elif isinstance(event, h2.events.DataReceived):
181
+ stream_id = event.stream_id
182
+ data = event.data
183
+ if all(x is not None for x in (self, stream_id, data)):
184
+ logger().debug(
185
+ f"H2 receive_data data - self={id(self)} steam_id={stream_id} data={data[:20]!r}"
186
+ )
187
+ records._tracerhttp2.receive_data(id(self), stream_id, data)
188
+
189
+ return ret
190
+
191
+ return hook
192
+
193
+
194
+ @contextmanager
195
+ def hook_h2(records: HTTPRecords) -> Generator[None, None, None]:
196
+ hooks = False
197
+ try:
198
+ import h2.connection
199
+
200
+ h2.connection.H2Connection.send_headers = decorate(records, h2.connection.H2Connection.send_headers, set_hook_for_h2_send_headers) # type: ignore[arg-type]
201
+ h2.connection.H2Connection.send_data = decorate(records, h2.connection.H2Connection.send_data, set_hook_for_h2_send_data) # type: ignore[arg-type]
202
+ h2.connection.H2Connection.receive_data = decorate(records, h2.connection.H2Connection.receive_data, set_hook_for_h2_receive_data) # type: ignore[arg-type]
203
+
204
+ hooks = True
205
+ except ImportError:
206
+ pass
207
+
208
+ yield
209
+
210
+ if hooks:
211
+ h2.connection.H2Connection.send_headers = undecorate(h2.connection.H2Connection.send_headers) # type: ignore[arg-type]
212
+ h2.connection.H2Connection.send_data = undecorate(h2.connection.H2Connection.send_data) # type: ignore[arg-type]
213
+ h2.connection.H2Connection.receive_data = undecorate(h2.connection.H2Connection.receive_data) # type: ignore[arg-type]
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  from collections.abc import Callable
3
2
  from contextlib import contextmanager
4
3
  from typing import Generator