httpdbg 0.31.3__tar.gz → 0.31.5__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 (84) hide show
  1. {httpdbg-0.31.3 → httpdbg-0.31.5}/PKG-INFO +1 -1
  2. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/__init__.py +1 -1
  3. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/aiohttp.py +5 -4
  4. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/httpx.py +10 -8
  5. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/requests.py +5 -4
  6. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/socket.py +92 -34
  7. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/urllib3.py +5 -4
  8. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/initiator.py +64 -33
  9. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/records.py +3 -24
  10. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg.egg-info/PKG-INFO +1 -1
  11. httpdbg-0.31.5/tests/test_context_manager.py +164 -0
  12. httpdbg-0.31.3/tests/test_context_manager.py +0 -56
  13. {httpdbg-0.31.3 → httpdbg-0.31.5}/LICENSE +0 -0
  14. {httpdbg-0.31.3 → httpdbg-0.31.5}/README.md +0 -0
  15. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/__main__.py +0 -0
  16. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/args.py +0 -0
  17. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/env.py +0 -0
  18. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/exception.py +0 -0
  19. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/__init__.py +0 -0
  20. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/all.py +0 -0
  21. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/external.py +0 -0
  22. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/generic.py +0 -0
  23. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/pytest.py +0 -0
  24. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/unittest.py +0 -0
  25. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/hooks/utils.py +0 -0
  26. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/log.py +0 -0
  27. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/mode_console.py +0 -0
  28. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/mode_module.py +0 -0
  29. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/mode_script.py +0 -0
  30. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/preview.py +0 -0
  31. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/server.py +0 -0
  32. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/utils.py +0 -0
  33. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/__init__.py +0 -0
  34. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/api.py +0 -0
  35. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/app.py +0 -0
  36. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/api.js +0 -0
  37. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/column.css +0 -0
  38. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/configuration.css +0 -0
  39. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/configuration.js +0 -0
  40. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/detail.css +0 -0
  41. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/favicon.ico +0 -0
  42. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/filter.js +0 -0
  43. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
  44. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
  45. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
  46. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
  47. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
  48. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
  49. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
  50. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
  51. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
  52. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
  53. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
  54. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
  55. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
  56. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
  57. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
  58. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/index.htm +0 -0
  59. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/main.js +0 -0
  60. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/mustache.min.js +0 -0
  61. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/render.js +0 -0
  62. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/style.css +0 -0
  63. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg/webapp/static/table.css +0 -0
  64. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg.egg-info/SOURCES.txt +0 -0
  65. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg.egg-info/dependency_links.txt +0 -0
  66. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg.egg-info/entry_points.txt +0 -0
  67. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg.egg-info/requires.txt +0 -0
  68. {httpdbg-0.31.3 → httpdbg-0.31.5}/httpdbg.egg-info/top_level.txt +0 -0
  69. {httpdbg-0.31.3 → httpdbg-0.31.5}/pyproject.toml +0 -0
  70. {httpdbg-0.31.3 → httpdbg-0.31.5}/setup.cfg +0 -0
  71. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_api.py +0 -0
  72. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_cli.py +0 -0
  73. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_hook_aiohttp.py +0 -0
  74. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_hook_httpx.py +0 -0
  75. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_hook_pytest.py +0 -0
  76. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_hook_requests.py +0 -0
  77. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_hook_unittest.py +0 -0
  78. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_hook_urllib3.py +0 -0
  79. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_initiator.py +0 -0
  80. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_mode_console.py +0 -0
  81. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_mode_module_pytest.py +0 -0
  82. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_mode_script.py +0 -0
  83. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_preview.py +0 -0
  84. {httpdbg-0.31.3 → httpdbg-0.31.5}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: httpdbg
3
- Version: 0.31.3
3
+ Version: 0.31.5
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
@@ -3,6 +3,6 @@ from httpdbg.hooks.all import httprecord
3
3
  from httpdbg.records import HTTPRecords
4
4
 
5
5
 
6
- __version__ = "0.31.3"
6
+ __version__ = "0.31.5"
7
7
 
8
8
  __all__ = ["httprecord", "HTTPRecords"]
@@ -25,10 +25,11 @@ def set_hook_for_aiohttp_async(records: HTTPRecords, method: Callable):
25
25
 
26
26
  if "str_or_url" in callargs:
27
27
  if initiator_and_group:
28
- initiator, group = initiator_and_group
29
- records.add_new_record_exception(
30
- initiator, group, str(callargs["str_or_url"]), ex
31
- )
28
+ initiator, group, is_new = initiator_and_group
29
+ if is_new:
30
+ records.add_new_record_exception(
31
+ initiator, group, str(callargs["str_or_url"]), ex
32
+ )
32
33
  raise
33
34
 
34
35
  return hook
@@ -25,10 +25,11 @@ def set_hook_for_httpx_async(records: HTTPRecords, method: Callable):
25
25
 
26
26
  if "url" in callargs:
27
27
  if initiator_and_group:
28
- initiator, group = initiator_and_group
29
- records.add_new_record_exception(
30
- initiator, group, str(callargs["url"]), ex
31
- )
28
+ initiator, group, is_new = initiator_and_group
29
+ if is_new:
30
+ records.add_new_record_exception(
31
+ initiator, group, str(callargs["url"]), ex
32
+ )
32
33
  raise
33
34
 
34
35
  return hook
@@ -48,10 +49,11 @@ def set_hook_for_httpx(records: HTTPRecords, method: Callable):
48
49
 
49
50
  if "url" in callargs:
50
51
  if initiator_and_group:
51
- initiator, group = initiator_and_group
52
- records.add_new_record_exception(
53
- initiator, group, str(callargs["url"]), ex
54
- )
52
+ initiator, group, is_new = initiator_and_group
53
+ if is_new:
54
+ records.add_new_record_exception(
55
+ initiator, group, str(callargs["url"]), ex
56
+ )
55
57
  raise
56
58
 
57
59
  return hook
@@ -25,10 +25,11 @@ def set_hook_for_requests(records: HTTPRecords, method: Callable):
25
25
 
26
26
  if "url" in callargs:
27
27
  if initiator_and_group:
28
- initiator, group = initiator_and_group
29
- records.add_new_record_exception(
30
- initiator, group, str(callargs["url"]), ex
31
- )
28
+ initiator, group, is_new = initiator_and_group
29
+ if is_new:
30
+ records.add_new_record_exception(
31
+ initiator, group, str(callargs["url"]), ex
32
+ )
32
33
  raise
33
34
 
34
35
  return hook
@@ -3,15 +3,18 @@ import asyncio
3
3
  import asyncio.proactor_events
4
4
  from collections.abc import Callable
5
5
  from contextlib import contextmanager
6
+ import datetime
6
7
  import platform
7
8
  import socket
8
9
  import ssl
9
10
  import sys
11
+ import traceback
10
12
  from typing import Generator
11
13
 
12
14
  from httpdbg.hooks.utils import getcallargs
13
15
  from httpdbg.hooks.utils import decorate
14
16
  from httpdbg.hooks.utils import undecorate
17
+ from httpdbg.initiator import httpdbg_initiator
15
18
  from httpdbg.records import HTTPRecord
16
19
  from httpdbg.records import HTTPRecords
17
20
  from httpdbg.log import logger
@@ -33,20 +36,29 @@ def set_hook_for_socket_init(records: HTTPRecords, method: Callable):
33
36
  # what: A connection to a remote socket is initiated.
34
37
  # action: A new entry is added to the temporary raw socket storage list.
35
38
  def set_hook_for_socket_connect(records: HTTPRecords, method: Callable):
36
- def hook(self, address):
39
+ def hook(self, *args, **kwargs):
40
+ tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
37
41
  socketdata = records.get_socket_data(self, force_new=True)
38
42
  if socketdata:
39
43
  logger().info(
40
- f"CONNECT - self={self} id={id(self)} socketdata={socketdata} address={address}"
44
+ f"CONNECT - self={self} id={id(self)} socketdata={socketdata} args={args} kwargs={kwargs}"
41
45
  )
42
46
  try:
43
- r = method(self, address)
47
+ r = method(self, *args, **kwargs)
44
48
  except Exception as ex:
45
49
  if not isinstance(
46
50
  ex, (BlockingIOError, OSError)
47
51
  ): # BlockingIOError for async, OSError for ipv6
48
- initiator = records.get_initiator()
49
- records.add_new_record_exception(initiator, "", ex)
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
+ )
50
62
  raise
51
63
 
52
64
  return r
@@ -60,11 +72,18 @@ def set_hook_for_socket_connect(records: HTTPRecords, method: Callable):
60
72
  # action: Link the socket and the sslsocket
61
73
  def set_hook_for_ssl_wrap_socket(records: HTTPRecords, method: Callable):
62
74
  def hook(sock, *args, **kwargs):
75
+ tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
63
76
  try:
64
77
  sslsocket = method(sock, *args, **kwargs)
65
78
  except Exception as ex:
66
- initiator = records.get_initiator()
67
- records.add_new_record_exception(initiator, "", 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)
68
87
  raise
69
88
 
70
89
  logger().info(
@@ -85,11 +104,18 @@ def set_hook_for_ssl_wrap_socket(records: HTTPRecords, method: Callable):
85
104
  # action: Link the socket and the sslsocket
86
105
  def set_hook_for_sslcontext_wrap_socket(records: HTTPRecords, method: Callable):
87
106
  def hook(self, sock, *args, **kwargs):
107
+ tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
88
108
  try:
89
109
  sslsocket = method(self, sock, *args, **kwargs)
90
110
  except Exception as ex:
91
- initiator = records.get_initiator()
92
- records.add_new_record_exception(initiator, "", 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)
93
119
  raise
94
120
 
95
121
  logger().info(
@@ -110,11 +136,18 @@ def set_hook_for_sslcontext_wrap_socket(records: HTTPRecords, method: Callable):
110
136
  # action: Record a new SocketRawData if necessary
111
137
  def set_hook_for_socket_wrap_bio(records: HTTPRecords, method: Callable):
112
138
  def hook(self, *args, **kwargs):
139
+ tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
113
140
  try:
114
141
  sslobject = method(self, *args, **kwargs)
115
142
  except Exception as ex:
116
- initiator = records.get_initiator()
117
- records.add_new_record_exception(initiator, "", 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)
118
151
  raise
119
152
 
120
153
  logger().info(
@@ -196,7 +229,6 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
196
229
  logger().info(
197
230
  f"SENDALL - self={self} id={id(self)} socketdata={socketdata} bytes={(b''+bytes)[:20]} type={type(bytes)} len={len(bytes)} args={args} kwargs={kwargs}"
198
231
  )
199
-
200
232
  if socketdata:
201
233
  if socketdata.record:
202
234
  socketdata.record.request.rawdata += bytes
@@ -205,15 +237,23 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
205
237
  http_detected = socketdata.http_detected()
206
238
  if http_detected:
207
239
  logger().info("SENDALL - http detected")
208
- socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
209
- socketdata.record.initiator_id = records.get_initiator()
210
- socketdata.record.address = socketdata.address
211
- socketdata.record.ssl = socketdata.ssl
212
- socketdata.record.request.rawdata = socketdata.rawdata
213
- records.requests[socketdata.record.id] = socketdata.record
240
+ with httpdbg_initiator(
241
+ records, traceback.extract_stack(), method, *args, **kwargs
242
+ ) as initiator_and_group:
243
+ initiator, group, is_new = initiator_and_group
244
+ if is_new:
245
+ tbegin = socketdata.tbegin - datetime.timedelta(
246
+ milliseconds=1
247
+ )
248
+ initiator.tbegin = tbegin
249
+ group.tbegin = tbegin
250
+ socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
251
+ socketdata.record.address = socketdata.address
252
+ socketdata.record.ssl = socketdata.ssl
253
+ socketdata.record.request.rawdata = socketdata.rawdata
254
+ records.requests[socketdata.record.id] = socketdata.record
214
255
  elif http_detected is False: # if None, there is nothing to do
215
256
  records._sockets[id(self)] = None
216
-
217
257
  try:
218
258
  return method(self, bytes, *args, **kwargs)
219
259
  except Exception as ex:
@@ -250,12 +290,21 @@ def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
250
290
  socketdata.rawdata += bytes[:size]
251
291
  http_detected = socketdata.http_detected()
252
292
  if http_detected:
253
- socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
254
- socketdata.record.initiator_id = records.get_initiator()
255
- socketdata.record.address = socketdata.address
256
- socketdata.record.ssl = socketdata.ssl
257
- socketdata.record.request.rawdata = socketdata.rawdata
258
- records.requests[socketdata.record.id] = socketdata.record
293
+ with httpdbg_initiator(
294
+ records, traceback.extract_stack(), method, *args, **kwargs
295
+ ) as initiator_and_group:
296
+ initiator, group, is_new = initiator_and_group
297
+ if is_new:
298
+ tbegin = socketdata.tbegin - datetime.timedelta(
299
+ milliseconds=1
300
+ )
301
+ initiator.tbegin = tbegin
302
+ group.tbegin = tbegin
303
+ socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
304
+ socketdata.record.address = socketdata.address
305
+ socketdata.record.ssl = socketdata.ssl
306
+ socketdata.record.request.rawdata = socketdata.rawdata
307
+ records.requests[socketdata.record.id] = socketdata.record
259
308
  elif http_detected is False: # if None, there is nothing to do
260
309
  records._sockets[id(self)] = None
261
310
  return size
@@ -314,12 +363,21 @@ def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
314
363
  socketdata.rawdata += bytes(buf[:size])
315
364
  http_detected = socketdata.http_detected()
316
365
  if http_detected:
317
- socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
318
- socketdata.record.initiator_id = records.get_initiator()
319
- socketdata.record.address = socketdata.address
320
- socketdata.record.ssl = socketdata.ssl
321
- socketdata.record.request.rawdata = socketdata.rawdata
322
- records.requests[socketdata.record.id] = socketdata.record
366
+ with httpdbg_initiator(
367
+ records, traceback.extract_stack(), method, *args, **kwargs
368
+ ) as initiator_and_group:
369
+ initiator, group, is_new = initiator_and_group
370
+ if is_new:
371
+ tbegin = socketdata.tbegin - datetime.timedelta(
372
+ milliseconds=1
373
+ )
374
+ initiator.tbegin = tbegin
375
+ group.tbegin = tbegin
376
+ socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
377
+ socketdata.record.address = socketdata.address
378
+ socketdata.record.ssl = socketdata.ssl
379
+ socketdata.record.request.rawdata = socketdata.rawdata
380
+ records.requests[socketdata.record.id] = socketdata.record
323
381
  elif http_detected is False: # if None, there is nothing to do
324
382
  records.sockets[id(self)] = None
325
383
  return size
@@ -376,8 +434,8 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
376
434
  socket.socket.send = decorate(records, socket.socket.send, set_hook_for_socket_send)
377
435
 
378
436
  if (sys.version_info.major == 3) and (sys.version_info.minor < 12):
379
- ssl.wrap_socket = decorate(
380
- records, ssl.wrap_socket, set_hook_for_ssl_wrap_socket
437
+ ssl.wrap_socket = decorate( # type: ignore[attr-defined]
438
+ records, ssl.wrap_socket, set_hook_for_ssl_wrap_socket # type: ignore[attr-defined]
381
439
  )
382
440
 
383
441
  ssl.SSLContext.wrap_socket = decorate(
@@ -436,7 +494,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
436
494
  socket.socket.send = undecorate(socket.socket.send)
437
495
 
438
496
  if (sys.version_info.major == 3) and (sys.version_info.minor < 12):
439
- ssl.wrap_socket = undecorate(ssl.wrap_socket)
497
+ ssl.wrap_socket = undecorate(ssl.wrap_socket) # type: ignore[attr-defined]
440
498
 
441
499
  ssl.SSLContext.wrap_socket = undecorate(ssl.SSLContext.wrap_socket)
442
500
  ssl.SSLContext.wrap_bio = undecorate(ssl.SSLContext.wrap_bio)
@@ -25,10 +25,11 @@ def set_hook_for_urllib3(records: HTTPRecords, method: Callable):
25
25
 
26
26
  if "url" in callargs:
27
27
  if initiator_and_group:
28
- initiator, group = initiator_and_group
29
- records.add_new_record_exception(
30
- initiator, group, str(callargs["url"]), ex
31
- )
28
+ initiator, group, is_new = initiator_and_group
29
+ if is_new:
30
+ records.add_new_record_exception(
31
+ initiator, group, str(callargs["url"]), ex
32
+ )
32
33
  raise
33
34
 
34
35
  return hook
@@ -112,7 +112,9 @@ def get_current_instruction(
112
112
  n_stack -= 1
113
113
  framesummary = extracted_stack[n_stack - 1]
114
114
 
115
- while "httpdbg/hooks" in framesummary.filename:
115
+ while ("httpdbg/hooks" in framesummary.filename) or (
116
+ "httpdbg\\hooks" in framesummary.filename
117
+ ):
116
118
  n_stack -= 1
117
119
  framesummary = extracted_stack[n_stack - 1]
118
120
 
@@ -209,44 +211,73 @@ def httpdbg_initiator(
209
211
  original_method: Callable,
210
212
  *args,
211
213
  **kwargs,
212
- ) -> Generator[Union[Tuple[Initiator, Group], None], None, None]:
213
- envname = f"{HTTPDBG_CURRENT_INITIATOR}_{records.session.id}"
214
-
215
- if not os.environ.get(envname):
216
- # temporary set a fake initiator env variable to avoid a recursion error
217
- # RecursionError: maximum recursion depth exceeded while calling a Python object
218
- # TL;DR When we construct the short_stack string, a recursion error occurs if there
219
- # is an object from a class where a hooked method is called in __repr__ or __str__.
220
- os.environ[envname] = "blabla"
221
-
222
- callargs = getcallargs(original_method, *args, **kwargs)
223
- instruction, short_stack, stack = get_current_instruction(extracted_stack)
214
+ ) -> Generator[Union[Tuple[Initiator, Group, bool], None], None, None]:
215
+ # An initiator is considered set if the environment variable exists and the initiator
216
+ # is recorded.If the environment variable exists but the initiatir is not recorded,
217
+ # it means httpdbg was called reentrantly, and we need to record the initiator again
218
+ # using the same id.
219
+ initiator_already_set = (HTTPDBG_CURRENT_INITIATOR in os.environ) and (
220
+ os.environ[HTTPDBG_CURRENT_INITIATOR] in records.initiators
221
+ )
222
+ try:
223
+ if not initiator_already_set:
224
+ original_initiator_id = None
225
+ if HTTPDBG_CURRENT_INITIATOR not in os.environ:
226
+ # temporary set a fake initiator env variable to avoid a recursion error
227
+ # RecursionError: maximum recursion depth exceeded while calling a Python object
228
+ # TL;DR When we construct the short_stack string, a recursion error occurs if there
229
+ # is an object from a class where a hooked method is called in __repr__ or __str__.
230
+ os.environ[HTTPDBG_CURRENT_INITIATOR] = "blabla"
231
+ else:
232
+ original_initiator_id = os.environ[HTTPDBG_CURRENT_INITIATOR]
233
+
234
+ # in any case, we create an Initiator
235
+
236
+ callargs = getcallargs(original_method, *args, **kwargs)
237
+ instruction, short_stack, stack = get_current_instruction(extracted_stack)
238
+
239
+ str_module = (
240
+ f"{original_method.__module__}."
241
+ if hasattr(original_method, "__module__")
242
+ else ""
243
+ )
244
+ str_name = (
245
+ f"{original_method.__name__}"
246
+ if hasattr(original_method, "__name__")
247
+ else str(original_method)
248
+ )
249
+ short_stack += f"----------\n{str_module}{str_name}(\n"
250
+ for k, v in callargs.items():
251
+ short_stack += f" {k}={v}\n"
252
+ short_stack += ")"
224
253
 
225
- short_stack += (
226
- f"----------\n{original_method.__module__}.{original_method.__name__}(\n"
227
- )
228
- for k, v in callargs.items():
229
- short_stack += f" {k}={v}\n"
230
- short_stack += ")"
254
+ current_initiator = Initiator(instruction, short_stack, stack)
231
255
 
232
- current_initiator = Initiator(instruction, short_stack, stack)
233
- records.initiators[current_initiator.id] = current_initiator
256
+ if original_initiator_id:
257
+ # if we already have an initiator id (reentrant call) we change the initiator id
258
+ # to keep the same
259
+ current_initiator.id = original_initiator_id
234
260
 
235
- os.environ[envname] = current_initiator.id
261
+ records.initiators[current_initiator.id] = current_initiator
236
262
 
237
- try:
238
- with httpdbg_group(
239
- records, instruction, short_stack
240
- ) as group: # by default we group the requests by initiator
241
- yield current_initiator, group
242
- except Exception:
243
- del os.environ[envname]
244
- raise
263
+ os.environ[HTTPDBG_CURRENT_INITIATOR] = current_initiator.id
245
264
 
246
- del os.environ[envname]
265
+ else:
266
+ current_initiator = records.initiators[
267
+ os.environ[HTTPDBG_CURRENT_INITIATOR]
268
+ ]
269
+
270
+ with httpdbg_group(
271
+ records, current_initiator.label, current_initiator.short_stack
272
+ ) as group: # by default we group the requests by initiator
273
+ yield current_initiator, group, initiator_already_set is False
274
+ except Exception:
275
+ if (not initiator_already_set) and (HTTPDBG_CURRENT_INITIATOR in os.environ):
276
+ del os.environ[HTTPDBG_CURRENT_INITIATOR]
277
+ raise
247
278
 
248
- else:
249
- yield None
279
+ if (not initiator_already_set) and (HTTPDBG_CURRENT_INITIATOR in os.environ):
280
+ del os.environ[HTTPDBG_CURRENT_INITIATOR]
250
281
 
251
282
 
252
283
  @contextmanager
@@ -6,7 +6,6 @@ import os
6
6
  import socket
7
7
  import ssl
8
8
  import sys
9
- import traceback
10
9
  from urllib.parse import urlparse
11
10
  from typing import Dict, List, Tuple, Union
12
11
 
@@ -15,7 +14,6 @@ from httpdbg.env import HTTPDBG_CURRENT_INITIATOR
15
14
  from httpdbg.env import HTTPDBG_CURRENT_TAG
16
15
  from httpdbg.utils import HTTPDBGCookie
17
16
  from httpdbg.utils import HTTPDBGHeader
18
- from httpdbg.initiator import in_lib
19
17
  from httpdbg.initiator import Group
20
18
  from httpdbg.initiator import Initiator
21
19
  from httpdbg.preview import generate_preview
@@ -217,7 +215,9 @@ class HTTPRecord:
217
215
  self.id = get_new_uuid()
218
216
  self.address: Tuple[str, int] = ("", 0)
219
217
  self._url: Union[str, None] = None
220
- self.initiator_id: Union[str, None] = None
218
+ self.initiator_id: Union[str, None] = os.environ.get(
219
+ f"{HTTPDBG_CURRENT_INITIATOR}"
220
+ )
221
221
  self.exception: Union[Exception, None] = None
222
222
  self.request: HTTPRecordRequest = HTTPRecordRequest()
223
223
  self.response: HTTPRecordResponse = HTTPRecordResponse()
@@ -325,27 +325,6 @@ class HTTPRecords:
325
325
  def __len__(self) -> int:
326
326
  return len(self.requests)
327
327
 
328
- def get_initiator(self) -> str:
329
- envname = f"{HTTPDBG_CURRENT_INITIATOR}_{self.session.id}"
330
-
331
- if envname in os.environ:
332
- initiator = self.initiators[os.environ[envname]]
333
- else:
334
- fullstack = traceback.format_stack()
335
- stack: List[str] = []
336
- for line in fullstack[6:]:
337
- if in_lib(line):
338
- break
339
- stack.append(line)
340
- long_label = stack[-1]
341
- label = long_label.split("\n")[1]
342
- initiator = Initiator(label, long_label, stack)
343
-
344
- if initiator.id not in self.initiators:
345
- self.initiators[initiator.id] = initiator
346
-
347
- return initiator.id
348
-
349
328
  def get_socket_data(
350
329
  self, obj, extra_sock=None, force_new=False, request=None
351
330
  ) -> Union[SocketRawData, None]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: httpdbg
3
- Version: 0.31.3
3
+ Version: 0.31.5
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
@@ -0,0 +1,164 @@
1
+ # -*- coding: utf-8 -*-
2
+ from httpdbg import httprecord
3
+
4
+ import pytest
5
+ import requests
6
+
7
+
8
+ @pytest.mark.cm
9
+ @pytest.mark.api
10
+ def test_context_manager(httpbin):
11
+ requests.get(httpbin.url + "/get")
12
+
13
+ with httprecord() as records:
14
+ requests.get(httpbin.url + "/get")
15
+
16
+ assert len(records.requests) == 1
17
+
18
+
19
+ @pytest.mark.cm
20
+ @pytest.mark.api
21
+ def test_context_manager_two_calls(httpbin):
22
+ requests.get(httpbin.url + "/get")
23
+
24
+ with httprecord() as records:
25
+ requests.get(httpbin.url + "/get")
26
+
27
+ assert len(records.requests) == 1
28
+ for _, req in records.requests.items():
29
+ assert req.method.lower() == "get"
30
+
31
+ with httprecord() as records2:
32
+ requests.post(httpbin.url + "/post")
33
+
34
+ assert len(records2.requests) == 1
35
+ for _, req in records2.requests.items():
36
+ assert req.method.lower() == "post"
37
+
38
+
39
+ @pytest.mark.cm
40
+ def test_context_manager_reentrant(httpbin):
41
+ requests.get(httpbin.url + "/get")
42
+
43
+ with httprecord() as records1:
44
+ requests.get(httpbin.url + "/get/a1")
45
+ with httprecord() as records2:
46
+ requests.get(httpbin.url + "/get/b1")
47
+ requests.get(httpbin.url + "/get/b2")
48
+ with httprecord() as records3:
49
+ requests.get(httpbin.url + "/get/c1")
50
+ requests.get(httpbin.url + "/get/a2")
51
+
52
+ requests.get(httpbin.url + "/get")
53
+
54
+ assert len(records1.requests) == 5
55
+ assert len(records2.requests) == 3
56
+ assert len(records3.requests) == 1
57
+
58
+
59
+ @pytest.mark.cm
60
+ @pytest.mark.initiator
61
+ def test_context_manager_reentrant_initiator(httpbin):
62
+ requests.get(httpbin.url + "/get")
63
+
64
+ with httprecord() as records1:
65
+ requests.get(httpbin.url + "/get/a1")
66
+ with httprecord() as records2:
67
+ requests.get(httpbin.url + "/get/b1")
68
+ requests.get(httpbin.url + "/get/b2")
69
+ with httprecord() as records3:
70
+ requests.get(httpbin.url + "/get/c1")
71
+ requests.get(httpbin.url + "/get/a2")
72
+
73
+ requests.get(httpbin.url + "/get")
74
+
75
+ assert (
76
+ records1.initiators[records1[0].initiator_id].label
77
+ == 'requests.get(httpbin.url + "/get/a1")'
78
+ )
79
+ assert (
80
+ records1.initiators[records1[1].initiator_id].label
81
+ == 'requests.get(httpbin.url + "/get/b1")'
82
+ )
83
+ assert (
84
+ records1.initiators[records1[2].initiator_id].label
85
+ == 'requests.get(httpbin.url + "/get/b2")'
86
+ )
87
+ assert (
88
+ records1.initiators[records1[3].initiator_id].label
89
+ == 'requests.get(httpbin.url + "/get/c1")'
90
+ )
91
+ assert (
92
+ records1.initiators[records1[4].initiator_id].label
93
+ == 'requests.get(httpbin.url + "/get/a2")'
94
+ )
95
+ assert (
96
+ records2.initiators[records2[0].initiator_id].label
97
+ == 'requests.get(httpbin.url + "/get/b1")'
98
+ )
99
+ assert (
100
+ records2.initiators[records2[1].initiator_id].label
101
+ == 'requests.get(httpbin.url + "/get/b2")'
102
+ )
103
+ assert (
104
+ records2.initiators[records2[2].initiator_id].label
105
+ == 'requests.get(httpbin.url + "/get/c1")'
106
+ )
107
+ assert (
108
+ records3.initiators[records3[0].initiator_id].label
109
+ == 'requests.get(httpbin.url + "/get/c1")'
110
+ )
111
+
112
+
113
+ @pytest.mark.cm
114
+ @pytest.mark.group
115
+ def test_context_manager_reentrant_group(httpbin):
116
+ requests.get(httpbin.url + "/get")
117
+
118
+ with httprecord() as records1:
119
+ requests.get(httpbin.url + "/get/a1")
120
+ with httprecord() as records2:
121
+ requests.get(httpbin.url + "/get/b1")
122
+ requests.get(httpbin.url + "/get/b2")
123
+ with httprecord() as records3:
124
+ requests.get(httpbin.url + "/get/c1")
125
+ requests.get(httpbin.url + "/get/a2")
126
+
127
+ requests.get(httpbin.url + "/get")
128
+
129
+ assert (
130
+ records1.groups[records1[0].group_id].label
131
+ == 'requests.get(httpbin.url + "/get/a1")'
132
+ )
133
+ assert (
134
+ records1.groups[records1[1].group_id].label
135
+ == 'requests.get(httpbin.url + "/get/b1")'
136
+ )
137
+ assert (
138
+ records1.groups[records1[2].group_id].label
139
+ == 'requests.get(httpbin.url + "/get/b2")'
140
+ )
141
+ assert (
142
+ records1.groups[records1[3].group_id].label
143
+ == 'requests.get(httpbin.url + "/get/c1")'
144
+ )
145
+ assert (
146
+ records1.groups[records1[4].group_id].label
147
+ == 'requests.get(httpbin.url + "/get/a2")'
148
+ )
149
+ assert (
150
+ records2.groups[records2[0].group_id].label
151
+ == 'requests.get(httpbin.url + "/get/b1")'
152
+ )
153
+ assert (
154
+ records2.groups[records2[1].group_id].label
155
+ == 'requests.get(httpbin.url + "/get/b2")'
156
+ )
157
+ assert (
158
+ records2.groups[records2[2].group_id].label
159
+ == 'requests.get(httpbin.url + "/get/c1")'
160
+ )
161
+ assert (
162
+ records3.groups[records3[0].group_id].label
163
+ == 'requests.get(httpbin.url + "/get/c1")'
164
+ )
@@ -1,56 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from httpdbg import httprecord
3
-
4
- import pytest
5
- import requests
6
-
7
-
8
- @pytest.mark.cm
9
- @pytest.mark.api
10
- def test_context_manager(httpbin):
11
- requests.get(httpbin.url + "/get")
12
-
13
- with httprecord() as records:
14
- requests.get(httpbin.url + "/get")
15
-
16
- assert len(records.requests) == 1
17
-
18
-
19
- @pytest.mark.cm
20
- @pytest.mark.api
21
- def test_context_manager_two_calls(httpbin):
22
- requests.get(httpbin.url + "/get")
23
-
24
- with httprecord() as records:
25
- requests.get(httpbin.url + "/get")
26
-
27
- assert len(records.requests) == 1
28
- for _, req in records.requests.items():
29
- assert req.method.lower() == "get"
30
-
31
- with httprecord() as records2:
32
- requests.post(httpbin.url + "/post")
33
-
34
- assert len(records2.requests) == 1
35
- for _, req in records2.requests.items():
36
- assert req.method.lower() == "post"
37
-
38
-
39
- @pytest.mark.cm
40
- def test_context_manager_reentrant(httpbin):
41
- requests.get(httpbin.url + "/get")
42
-
43
- with httprecord() as records1:
44
- requests.get(httpbin.url + "/get/a1")
45
- with httprecord() as records2:
46
- requests.get(httpbin.url + "/get/b1")
47
- requests.get(httpbin.url + "/get/b2")
48
- with httprecord() as records3:
49
- requests.get(httpbin.url + "/get/c1")
50
- requests.get(httpbin.url + "/get/a2")
51
-
52
- requests.get(httpbin.url + "/get")
53
-
54
- assert len(records1.requests) == 5
55
- assert len(records2.requests) == 3
56
- assert len(records3.requests) == 1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes