httpdbg 0.32.1__tar.gz → 1.0.0__tar.gz

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