httpdbg 0.28.3__tar.gz → 0.29.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {httpdbg-0.28.3 → httpdbg-0.29.0}/PKG-INFO +2 -2
  2. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/__init__.py +1 -1
  3. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/env.py +1 -0
  4. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/external.py +3 -1
  5. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/pytest.py +29 -1
  6. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/socket.py +5 -5
  7. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/initiator.py +55 -16
  8. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/records.py +20 -23
  9. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/api.py +16 -3
  10. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/api.js +11 -2
  11. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/configuration.js +3 -3
  12. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/filter.js +14 -14
  13. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/index.htm +10 -10
  14. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/main.js +1 -1
  15. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/render.js +17 -11
  16. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/style.css +1 -1
  17. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/table.css +6 -6
  18. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg.egg-info/PKG-INFO +2 -2
  19. httpdbg-0.29.0/httpdbg.egg-info/requires.txt +3 -0
  20. {httpdbg-0.28.3 → httpdbg-0.29.0}/pyproject.toml +1 -1
  21. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_hook_aiohttp.py +3 -15
  22. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_hook_httpx.py +6 -19
  23. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_hook_requests.py +3 -6
  24. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_hook_urllib3.py +32 -30
  25. httpdbg-0.29.0/tests/test_initiator.py +167 -0
  26. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_mode_module_pytest.py +54 -5
  27. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_mode_script.py +5 -12
  28. httpdbg-0.28.3/httpdbg.egg-info/requires.txt +0 -3
  29. httpdbg-0.28.3/tests/test_initiator.py +0 -172
  30. {httpdbg-0.28.3 → httpdbg-0.29.0}/LICENSE +0 -0
  31. {httpdbg-0.28.3 → httpdbg-0.29.0}/README.md +0 -0
  32. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/__main__.py +0 -0
  33. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/args.py +0 -0
  34. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/exception.py +0 -0
  35. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/__init__.py +0 -0
  36. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/aiohttp.py +0 -0
  37. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/all.py +0 -0
  38. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/generic.py +0 -0
  39. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/httpx.py +0 -0
  40. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/requests.py +0 -0
  41. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/urllib3.py +0 -0
  42. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/hooks/utils.py +0 -0
  43. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/log.py +0 -0
  44. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/mode_console.py +0 -0
  45. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/mode_module.py +0 -0
  46. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/mode_script.py +0 -0
  47. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/preview.py +0 -0
  48. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/server.py +0 -0
  49. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/utils.py +0 -0
  50. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/__init__.py +0 -0
  51. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/app.py +0 -0
  52. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/column.css +0 -0
  53. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/detail.css +0 -0
  54. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/favicon.ico +0 -0
  55. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
  56. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
  57. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
  58. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
  59. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
  60. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
  61. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
  62. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
  63. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
  64. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
  65. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
  66. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg/webapp/static/mustache.min.js +0 -0
  67. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg.egg-info/SOURCES.txt +0 -0
  68. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg.egg-info/dependency_links.txt +0 -0
  69. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg.egg-info/entry_points.txt +0 -0
  70. {httpdbg-0.28.3 → httpdbg-0.29.0}/httpdbg.egg-info/top_level.txt +0 -0
  71. {httpdbg-0.28.3 → httpdbg-0.29.0}/setup.cfg +0 -0
  72. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_api.py +0 -0
  73. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_cli.py +0 -0
  74. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_context_manager.py +0 -0
  75. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_mode_console.py +0 -0
  76. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_preview.py +0 -0
  77. {httpdbg-0.28.3 → httpdbg-0.29.0}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: httpdbg
3
- Version: 0.28.3
3
+ Version: 0.29.0
4
4
  Summary: A very simple tool to debug HTTP(S) client requests
5
5
  Author-email: cle-b <cle@tictac.pm>
6
6
  License: Apache-2.0
@@ -24,7 +24,7 @@ Requires-Python: >=3.9
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Provides-Extra: pytest
27
- Requires-Dist: pytest-httpdbg>=0.3.1; extra == "pytest"
27
+ Requires-Dist: pytest-httpdbg>=0.6.0; extra == "pytest"
28
28
 
29
29
  # httpdbg
30
30
 
@@ -3,6 +3,6 @@ from httpdbg.hooks.all import httprecord
3
3
  from httpdbg.records import HTTPRecords
4
4
 
5
5
 
6
- __version__ = "0.28.3"
6
+ __version__ = "0.29.0"
7
7
 
8
8
  __all__ = ["httprecord", "HTTPRecords"]
@@ -1,3 +1,4 @@
1
+ HTTPDBG_CURRENT_GROUP = "HTTPDBG_CURRENT_GROUP"
1
2
  HTTPDBG_CURRENT_INITIATOR = "HTTPDBG_CURRENT_INITIATOR"
2
3
  HTTPDBG_CURRENT_TAG = "HTTPDBG_CURRENT_TAG"
3
4
  HTTPDBG_SUBPROCESS_DIR = "HTTPDBG_SUBPROCESS_DIR"
@@ -46,8 +46,10 @@ class WatcherSubprocessDirThread(threading.Thread):
46
46
  def load_dump(self):
47
47
  for dump in glob.glob(f"{os.environ[HTTPDBG_SUBPROCESS_DIR]}/*.httpdbgrecords"):
48
48
  with open(dump, "rb") as dumpfile:
49
- newrecords = pickle.load(dumpfile)
49
+ newrecords: HTTPRecords = pickle.load(dumpfile)
50
50
  self.records.requests.update(newrecords.requests)
51
+ self.records.initiators.update(newrecords.initiators)
52
+ self.records.groups.update(newrecords.groups)
51
53
 
52
54
  def run(self):
53
55
  while not self.stop:
@@ -1,16 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
+ from collections.abc import Callable
2
3
  from contextlib import contextmanager
3
4
  from typing import Generator
4
5
 
5
6
  from httpdbg.hooks.utils import decorate
6
7
  from httpdbg.hooks.utils import getcallargs
7
8
  from httpdbg.hooks.utils import undecorate
9
+ from httpdbg.initiator import httpdbg_group
8
10
  from httpdbg.initiator import httpdbg_tag
11
+ from httpdbg.log import logger
9
12
  from httpdbg.records import HTTPRecords
10
13
 
11
14
 
12
- def set_hook_for_pytest_fixture(_, method):
15
+ def set_hook_for_pytest_fixture(_: HTTPRecords, method: Callable):
13
16
  def hook(*args, **kwargs):
17
+ logger().info("SET_HOOK_FOR_PYTEST_FIXTURE")
14
18
  callargs = getcallargs(method, *args, **kwargs)
15
19
 
16
20
  if "fixturefunc" in callargs:
@@ -22,11 +26,28 @@ def set_hook_for_pytest_fixture(_, method):
22
26
  return hook
23
27
 
24
28
 
29
+ def set_hook_for_pytest_runtest(records: HTTPRecords, method: Callable):
30
+ def hook(*args, **kwargs):
31
+ logger().info("SET_HOOK_FOR_PYTEST_RUNTEST")
32
+ callargs = getcallargs(method, *args, **kwargs)
33
+
34
+ if "item" in callargs:
35
+ full_label = getattr(callargs["item"], "nodeid", "unknown test")
36
+ label = getattr(callargs["item"], "name", "unknown test")
37
+ with httpdbg_group(records, label, full_label):
38
+ return method(*args, **kwargs)
39
+ else:
40
+ return method(*args, **kwargs)
41
+
42
+ return hook
43
+
44
+
25
45
  @contextmanager
26
46
  def hook_pytest(records: HTTPRecords) -> Generator[None, None, None]:
27
47
  hooks = False
28
48
  try:
29
49
  import _pytest.fixtures
50
+ import _pytest.runner
30
51
 
31
52
  _pytest.fixtures.call_fixture_func = decorate(
32
53
  records, _pytest.fixtures.call_fixture_func, set_hook_for_pytest_fixture
@@ -36,6 +57,11 @@ def hook_pytest(records: HTTPRecords) -> Generator[None, None, None]:
36
57
  _pytest.fixtures._teardown_yield_fixture,
37
58
  set_hook_for_pytest_fixture,
38
59
  )
60
+
61
+ _pytest.runner.runtestprotocol = decorate(
62
+ records, _pytest.runner.runtestprotocol, set_hook_for_pytest_runtest
63
+ )
64
+
39
65
  hooks = True
40
66
  except ImportError:
41
67
  pass
@@ -49,3 +75,5 @@ def hook_pytest(records: HTTPRecords) -> Generator[None, None, None]:
49
75
  _pytest.fixtures._teardown_yield_fixture = undecorate(
50
76
  _pytest.fixtures._teardown_yield_fixture
51
77
  )
78
+
79
+ _pytest.runner.runtestprotocol = undecorate(_pytest.runner.runtestprotocol)
@@ -206,7 +206,7 @@ def set_hook_for_socket_sendall(records: HTTPRecords, method: Callable):
206
206
  if http_detected:
207
207
  logger().info("SENDALL - http detected")
208
208
  socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
209
- socketdata.record.initiator = records.get_initiator()
209
+ socketdata.record.initiator_id = records.get_initiator()
210
210
  socketdata.record.address = socketdata.address
211
211
  socketdata.record.ssl = socketdata.ssl
212
212
  socketdata.record.request.rawdata = socketdata.rawdata
@@ -251,7 +251,7 @@ def set_hook_for_socket_send(records: HTTPRecords, method: Callable):
251
251
  http_detected = socketdata.http_detected()
252
252
  if http_detected:
253
253
  socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
254
- socketdata.record.initiator = records.get_initiator()
254
+ socketdata.record.initiator_id = records.get_initiator()
255
255
  socketdata.record.address = socketdata.address
256
256
  socketdata.record.ssl = socketdata.ssl
257
257
  socketdata.record.request.rawdata = socketdata.rawdata
@@ -315,7 +315,7 @@ def set_hook_for_sslobject_write(records: HTTPRecords, method: Callable):
315
315
  http_detected = socketdata.http_detected()
316
316
  if http_detected:
317
317
  socketdata.record = HTTPRecord(tbegin=socketdata.tbegin)
318
- socketdata.record.initiator = records.get_initiator()
318
+ socketdata.record.initiator_id = records.get_initiator()
319
319
  socketdata.record.address = socketdata.address
320
320
  socketdata.record.ssl = socketdata.ssl
321
321
  socketdata.record.request.rawdata = socketdata.rawdata
@@ -413,7 +413,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
413
413
  )
414
414
 
415
415
  # only for async HTTP requests (not HTTPS) on Windows
416
- if (platform.system().lower() == "windows") and (sys.version_info >= (3, 7)):
416
+ if platform.system().lower() == "windows":
417
417
  asyncio.proactor_events._ProactorReadPipeTransport._data_received = decorate( # type: ignore
418
418
  records,
419
419
  asyncio.proactor_events._ProactorReadPipeTransport._data_received, # type: ignore
@@ -455,7 +455,7 @@ def hook_socket(records: HTTPRecords) -> Generator[None, None, None]:
455
455
  )
456
456
 
457
457
  # only for async HTTP requests (not HTTPS) on Windows
458
- if (platform.system().lower() == "windows") and (sys.version_info >= (3, 7)):
458
+ if platform.system().lower() == "windows":
459
459
  asyncio.proactor_events._ProactorReadPipeTransport._data_received = undecorate( # type: ignore
460
460
  asyncio.proactor_events._ProactorReadPipeTransport._data_received # type: ignore
461
461
  )
@@ -7,7 +7,12 @@ from typing import Generator
7
7
  from typing import List
8
8
  from typing import Tuple
9
9
  from typing import Union
10
+ from typing import TYPE_CHECKING
10
11
 
12
+ if TYPE_CHECKING:
13
+ from httpdbg.records import HTTPRecords
14
+
15
+ from httpdbg.env import HTTPDBG_CURRENT_GROUP
11
16
  from httpdbg.env import HTTPDBG_CURRENT_INITIATOR
12
17
  from httpdbg.env import HTTPDBG_CURRENT_TAG
13
18
  from httpdbg.hooks.utils import getcallargs
@@ -15,26 +20,23 @@ from httpdbg.utils import get_new_uuid
15
20
  from httpdbg.log import logger
16
21
 
17
22
 
18
- class Initiator(object):
23
+ class Initiator:
19
24
  def __init__(
20
25
  self,
21
26
  id: str,
22
- short_label: str,
23
- long_label: Union[str, None],
27
+ label: str,
24
28
  short_stack: str,
25
29
  stack: List[str],
26
30
  ):
27
31
  self.id = id
28
- self.short_label = short_label
29
- self.long_label = long_label
32
+ self.label = label
30
33
  self.short_stack = short_stack
31
34
  self.stack = stack
32
35
 
33
36
  def __eq__(self, other) -> bool:
34
37
  if type(other) is Initiator:
35
38
  return (
36
- self.short_label == other.short_label
37
- and self.long_label == other.long_label
39
+ self.label == other.label
38
40
  and self.short_stack == other.short_stack
39
41
  and self.stack == other.stack
40
42
  )
@@ -45,21 +47,29 @@ class Initiator(object):
45
47
  if full:
46
48
  json = {
47
49
  "id": self.id,
48
- "short_label": self.short_label,
49
- "long_label": self.long_label,
50
+ "label": self.label,
50
51
  "short_stack": self.short_stack,
51
52
  "stack": "\n".join(self.stack),
52
53
  }
53
54
  else:
54
55
  json = {
55
56
  "id": self.id,
56
- "short_label": self.short_label,
57
- "long_label": self.long_label,
57
+ "label": self.label,
58
58
  "short_stack": self.short_stack,
59
59
  }
60
60
  return json
61
61
 
62
62
 
63
+ class Group:
64
+ def __init__(self, id: str, label: str, full_label: str):
65
+ self.id = id
66
+ self.label = label
67
+ self.full_label = full_label
68
+
69
+ def to_json(self) -> dict:
70
+ return {"id": self.id, "label": self.label, "full_label": self.full_label}
71
+
72
+
63
73
  def compatible_path(path: str) -> str:
64
74
  p = path
65
75
  if platform.system().lower() == "windows":
@@ -205,15 +215,16 @@ def httpdbg_initiator(
205
215
  short_stack += f" {k}={v}\n"
206
216
  short_stack += ")"
207
217
 
208
- current_initiator = Initiator(
209
- get_new_uuid(), instruction, None, short_stack, stack
210
- )
211
- records._initiators[current_initiator.id] = current_initiator
218
+ current_initiator = Initiator(get_new_uuid(), instruction, short_stack, stack)
219
+ records.initiators[current_initiator.id] = current_initiator
212
220
 
213
221
  os.environ[envname] = current_initiator.id
214
222
 
215
223
  try:
216
- yield current_initiator
224
+ with httpdbg_group(
225
+ records, instruction, short_stack
226
+ ): # by default we group the requests by initiator
227
+ yield current_initiator
217
228
  except Exception:
218
229
  del os.environ[envname]
219
230
  raise
@@ -233,6 +244,7 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
233
244
  os.environ[HTTPDBG_CURRENT_TAG] = tag
234
245
 
235
246
  try:
247
+ logger().info(f"httpdbg_tag {tag}")
236
248
  yield
237
249
  except Exception:
238
250
  if (not tag_already_set) and (HTTPDBG_CURRENT_TAG in os.environ):
@@ -241,3 +253,30 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
241
253
 
242
254
  if (not tag_already_set) and (HTTPDBG_CURRENT_TAG in os.environ):
243
255
  del os.environ[HTTPDBG_CURRENT_TAG]
256
+
257
+
258
+ @contextmanager
259
+ def httpdbg_group(
260
+ records: "HTTPRecords", label: str, full_label: str
261
+ ) -> Generator[None, None, None]:
262
+
263
+ group_already_set = HTTPDBG_CURRENT_GROUP in os.environ
264
+
265
+ if not group_already_set:
266
+ logger().info("httpdbg_group (new)")
267
+ group_id = get_new_uuid()
268
+ records.groups[group_id] = Group(group_id, label, full_label)
269
+ os.environ[HTTPDBG_CURRENT_GROUP] = group_id
270
+
271
+ try:
272
+ logger().info(
273
+ f"httpdbg_group {records.groups} group_id={os.environ[HTTPDBG_CURRENT_GROUP]} label={label} full_label={full_label}"
274
+ )
275
+ yield
276
+ except Exception:
277
+ if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
278
+ del os.environ[HTTPDBG_CURRENT_GROUP]
279
+ raise
280
+
281
+ if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
282
+ del os.environ[HTTPDBG_CURRENT_GROUP]
@@ -9,11 +9,13 @@ import traceback
9
9
  from urllib.parse import urlparse
10
10
  from typing import Dict, List, Tuple, Union
11
11
 
12
+ from httpdbg.env import HTTPDBG_CURRENT_GROUP
12
13
  from httpdbg.env import HTTPDBG_CURRENT_INITIATOR
13
14
  from httpdbg.env import HTTPDBG_CURRENT_TAG
14
15
  from httpdbg.utils import HTTPDBGCookie
15
16
  from httpdbg.utils import HTTPDBGHeader
16
17
  from httpdbg.initiator import in_lib
18
+ from httpdbg.initiator import Group
17
19
  from httpdbg.initiator import Initiator
18
20
  from httpdbg.preview import generate_preview
19
21
  from httpdbg.utils import get_new_uuid
@@ -214,13 +216,14 @@ class HTTPRecord:
214
216
  self.id = get_new_uuid()
215
217
  self.address: Tuple[str, int] = ("", 0)
216
218
  self._url: Union[str, None] = None
217
- self.initiator: Initiator = Initiator("", "", None, "", [])
219
+ self.initiator_id: Union[str, None] = None
218
220
  self.exception: Union[Exception, None] = None
219
221
  self.request: HTTPRecordRequest = HTTPRecordRequest()
220
222
  self.response: HTTPRecordResponse = HTTPRecordResponse()
221
223
  self.ssl: Union[bool, None] = None
222
224
  self.tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
223
- self.tag = os.environ.get(HTTPDBG_CURRENT_TAG)
225
+ self.tag: Union[str, None] = os.environ.get(HTTPDBG_CURRENT_TAG)
226
+ self.group_id: Union[str, None] = os.environ.get(HTTPDBG_CURRENT_GROUP)
224
227
  if tbegin:
225
228
  self.tbegin = tbegin
226
229
 
@@ -295,10 +298,12 @@ class HTTPRecords:
295
298
  self.reset()
296
299
 
297
300
  def reset(self) -> None:
301
+ logger().info("HTTPRecords.reset")
298
302
  self.id = get_new_uuid()
299
303
  self.requests: Dict[str, HTTPRecord] = {}
300
304
  self.requests_already_loaded = 0
301
- self._initiators: Dict[str, Initiator] = {}
305
+ self.initiators: Dict[str, Initiator] = {}
306
+ self.groups: Dict[str, Group] = {}
302
307
  self._sockets: Dict[int, SocketRawData] = {}
303
308
 
304
309
  @property
@@ -311,11 +316,11 @@ class HTTPRecords:
311
316
  def __len__(self) -> int:
312
317
  return len(self.requests)
313
318
 
314
- def get_initiator(self) -> Initiator:
319
+ def get_initiator(self) -> str:
315
320
  envname = f"{HTTPDBG_CURRENT_INITIATOR}_{self.id}"
316
321
 
317
322
  if envname in os.environ:
318
- initiator = self._initiators[os.environ[envname]]
323
+ initiator = self.initiators[os.environ[envname]]
319
324
  else:
320
325
  fullstack = traceback.format_stack()
321
326
  stack: List[str] = []
@@ -324,23 +329,13 @@ class HTTPRecords:
324
329
  break
325
330
  stack.append(line)
326
331
  long_label = stack[-1]
327
- short_label = long_label.split("\n")[1]
328
- initiator = Initiator(get_new_uuid(), short_label, None, long_label, stack)
329
-
330
- if ("PYTEST_CURRENT_TEST" in os.environ) and (
331
- "HTTPDBG_PYTEST_PLUGIN" not in os.environ
332
- ):
333
- long_label = " ".join(os.environ["PYTEST_CURRENT_TEST"].split(" ")[:-1])
334
- short_label = long_label.split("::")[-1]
335
- initiator = Initiator(
336
- f"{long_label}{self.id}",
337
- short_label,
338
- long_label,
339
- initiator.short_stack,
340
- initiator.stack,
341
- )
342
-
343
- return initiator
332
+ label = long_label.split("\n")[1]
333
+ initiator = Initiator(get_new_uuid(), label, long_label, stack)
334
+
335
+ if initiator.id not in self.initiators:
336
+ self.initiators[initiator.id] = initiator
337
+
338
+ return initiator.id
344
339
 
345
340
  def get_socket_data(
346
341
  self, obj, extra_sock=None, force_new=False, request=None
@@ -417,9 +412,11 @@ class HTTPRecords:
417
412
  def add_new_record_exception(
418
413
  self, initiator: Initiator, url: str, exception: Exception
419
414
  ) -> HTTPRecord:
415
+ if initiator.id not in self.initiators:
416
+ self.initiators[initiator.id] = initiator
420
417
  new_record = HTTPRecord()
421
418
  new_record.url = url
422
- new_record.initiator = initiator
419
+ new_record.initiator_id = initiator.id
423
420
  new_record.exception = exception
424
421
  self.requests[new_record.id] = new_record
425
422
  return new_record
@@ -21,7 +21,8 @@ class RequestPayload(JSONEncoder):
21
21
  "response": None,
22
22
  "exception": None,
23
23
  "tag": req.tag,
24
- "initiator": req.initiator.to_json(),
24
+ "initiator_id": req.initiator_id,
25
+ "group_id": req.group_id,
25
26
  "in_progress": req.in_progress,
26
27
  }
27
28
 
@@ -64,7 +65,12 @@ class RequestListPayload(JSONEncoder):
64
65
  records, HTTPRecords
65
66
  ), "This encoder works only for HTTPRecords object."
66
67
 
67
- payload = {"id": records.id, "requests": {}}
68
+ payload = {
69
+ "id": records.id,
70
+ "requests": {},
71
+ "initiators": {},
72
+ "groups": {},
73
+ }
68
74
 
69
75
  for id, req in records.requests.items():
70
76
  payload["requests"][id] = {
@@ -76,10 +82,17 @@ class RequestListPayload(JSONEncoder):
76
82
  "reason": req.reason,
77
83
  "verb": req.method,
78
84
  "tag": req.tag,
79
- "initiator": req.initiator.to_json(full=False),
85
+ "initiator_id": req.initiator_id,
86
+ "group_id": req.group_id,
80
87
  "in_progress": req.in_progress,
81
88
  "tbegin": req.tbegin.isoformat(),
82
89
  "last_update": req.last_update.isoformat(),
83
90
  }
84
91
 
92
+ for id, initiator in records.initiators.items():
93
+ payload["initiators"][id] = initiator.to_json()
94
+
95
+ for id, group in records.groups.items():
96
+ payload["groups"][id] = group.to_json()
97
+
85
98
  return payload
@@ -3,8 +3,10 @@
3
3
  const global = {
4
4
  "k7": null,
5
5
  "requests": {},
6
+ "initiators": {},
7
+ "groups": {},
6
8
  "connected": false,
7
- "initiator_collapse": []
9
+ "group_collapse": []
8
10
  }
9
11
 
10
12
  function save_request(request_id, request) {
@@ -18,6 +20,8 @@ function save_request(request_id, request) {
18
20
  }
19
21
  global.requests[request_id] = request;
20
22
 
23
+ global.requests[request_id].initiator = global.initiators[request.initiator_id];
24
+
21
25
  if (global.requests[request_id].in_progress) {
22
26
  global.requests[request_id].status_code_view = '<img class="icon" src="static/icons/wait-sandclock-icon.svg-+-$**HTTPDBG_VERSION**$" alt="loading"/>';
23
27
  } else {
@@ -62,6 +66,11 @@ async function get_all_requests() {
62
66
  clean();
63
67
  };
64
68
 
69
+ // for the initiators and the groups, we can just save them without any verification
70
+ Object.assign(global.initiators, data.initiators);
71
+ Object.assign(global.groups, data.groups);
72
+
73
+ // for the requests, we may have to update them
65
74
  for (const [request_id, request] of Object.entries(data.requests)) {
66
75
  if (!(request_id in global.requests)) {
67
76
  // this is a new request
@@ -109,7 +118,7 @@ async function get_request(request_id) {
109
118
  }
110
119
 
111
120
  // the full stack is not present in request summary
112
- global.requests[request_id].initiator = data.initiator;
121
+ global.requests[request_id].initiator_id = data.initiator_id;
113
122
 
114
123
  global.requests[request_id].to_refresh = true;
115
124
 
@@ -33,7 +33,7 @@ function apply_config() {
33
33
  }
34
34
 
35
35
  apply_config_rule_css(httpdbgApp.config.hide_netloc, sheet);
36
- apply_config_rule_css(httpdbgApp.config.hide_initiator, sheet);
36
+ apply_config_rule_css(httpdbgApp.config.hide_group, sheet);
37
37
  apply_config_rule_css(httpdbgApp.config.hide_tag, sheet);
38
38
 
39
39
  apply_config_rule_click(httpdbgApp.config.details_wrap_default);
@@ -48,7 +48,7 @@ function load_config_from_url(apply) {
48
48
  const params = new URLSearchParams(window.location.search);
49
49
 
50
50
  load_config_rule_from_url(httpdbgApp.config.hide_netloc, params);
51
- load_config_rule_from_url(httpdbgApp.config.hide_initiator, params);
51
+ load_config_rule_from_url(httpdbgApp.config.hide_group, params);
52
52
  load_config_rule_from_url(httpdbgApp.config.hide_tag, params);
53
53
  load_config_rule_from_url(httpdbgApp.config.details_wrap_default, params);
54
54
  load_config_rule_from_url(httpdbgApp.config.details_raw_default, params);
@@ -66,7 +66,7 @@ function load_config_from_form(apply) {
66
66
  const params = new URLSearchParams(window.location.search);
67
67
 
68
68
  load_config_rule_from_form(httpdbgApp.config.hide_netloc);
69
- load_config_rule_from_form(httpdbgApp.config.hide_initiator);
69
+ load_config_rule_from_form(httpdbgApp.config.hide_group);
70
70
  load_config_rule_from_form(httpdbgApp.config.hide_tag);
71
71
  load_config_rule_from_form(httpdbgApp.config.details_wrap_default);
72
72
  load_config_rule_from_form(httpdbgApp.config.details_raw_default);
@@ -12,46 +12,46 @@ function update_filter_url() {
12
12
  if (value == "") {
13
13
  // if there is no filter, all rows are visible
14
14
  sheet.insertRule("[data-filter] {display: table-row;}");
15
- sheet.insertRule(".initiator-header {display: table-row-group;}");
15
+ sheet.insertRule(".group-header {display: table-row-group;}");
16
16
  } else {
17
17
  // if there is a filter, only the requests that match the filter are visible
18
18
  sheet.insertRule('[data-filter*="' + value + '"] {display: table-row;}');
19
19
  if (CSS.supports("selector(:has(*))")) {
20
- // an initiator row is visible only if it contains at least one visible request
21
- sheet.insertRule('.initiator-header:has([data-filter*="' + value + '"]) {display: table-row-group;}');
20
+ // a group row is visible only if it contains at least one visible request
21
+ sheet.insertRule('.group-header:has([data-filter*="' + value + '"]) {display: table-row-group;}');
22
22
  // if there is a filter and no request visible, the filter icon is red
23
23
  sheet.insertRule('.requests-table:not(:has([data-filter*="' + value + '"])):has(tbody) .icon-filter {filter: grayscale(0%) !important;}');
24
24
  } else {
25
- // in case :has is not supported, the initiator rows are always visible
26
- sheet.insertRule(".initiator-header {display: table-row-group;}");
25
+ // in case :has is not supported, the group rows are always visible
26
+ sheet.insertRule(".group-header {display: table-row-group;}");
27
27
  }
28
28
  }
29
29
 
30
30
  filter_requests_count();
31
31
  }
32
32
 
33
- function collapse_initiator(initiator_id, collapse) {
33
+ function collapse_group(group_id, collapse) {
34
34
  if (collapse) {
35
- global.initiator_collapse.push(initiator_id);
35
+ global.group_collapse.push(group_id);
36
36
  } else {
37
- let index = global.initiator_collapse.indexOf(initiator_id);
37
+ let index = global.group_collapse.indexOf(group_id);
38
38
  if (index !== -1) {
39
- global.initiator_collapse.splice(index, 1);
39
+ global.group_collapse.splice(index, 1);
40
40
  }
41
41
  }
42
42
 
43
- update_collapse_initiator();
43
+ update_collapse_group();
44
44
  }
45
45
 
46
- function update_collapse_initiator() {
47
- var sheet = document.getElementById("collapseinitiatorcss").sheet;
46
+ function update_collapse_group() {
47
+ var sheet = document.getElementById("collapsegroupcss").sheet;
48
48
 
49
49
  while (sheet.cssRules.length > 0) {
50
50
  sheet.deleteRule(0);
51
51
  }
52
52
 
53
- for (let initiator_id of global.initiator_collapse) {
54
- sheet.insertRule('[data-initiator="' + initiator_id + '"] {display: none;}');
53
+ for (let group_id of global.group_collapse) {
54
+ sheet.insertRule('[data-group="' + group_id + '"] {display: none;}');
55
55
  }
56
56
  }
57
57
 
@@ -17,7 +17,7 @@
17
17
  <style type="text/css" id="filter"></style>
18
18
  <style type="text/css" id="configurationcss"></style>
19
19
  <style type="text/css" id="serverstatuscss"></style>
20
- <style type="text/css" id="collapseinitiatorcss"></style>
20
+ <style type="text/css" id="collapsegroupcss"></style>
21
21
  <script src="static/mustache.min.js-+-$**HTTPDBG_VERSION**$"></script>
22
22
  <script src="static/main.js-+-$**HTTPDBG_VERSION**$"></script>
23
23
  <script src="static/api.js-+-$**HTTPDBG_VERSION**$"></script>
@@ -67,7 +67,7 @@
67
67
  <br>
68
68
 
69
69
  <input type="checkbox" id="cinitiator" name="hi" onchange="load_config_from_form(true)">
70
- <label for="cinitiator">Hide the initiator rows</label>
70
+ <label for="cinitiator">Hide the group rows</label>
71
71
 
72
72
  <br>
73
73
 
@@ -158,15 +158,15 @@
158
158
  </tr>
159
159
  </thead>
160
160
 
161
- <script id="template_initiator" type="x-tmpl-mustache">
162
- <tbody id="initiator-{{initiator.id}}" name="initiator" class="initiator-header">
163
- <tr class="initiator">
161
+ <script id="template_group" type="x-tmpl-mustache">
162
+ <tbody id="group-{{id}}" name="group" class="group-header">
163
+ <tr class="group">
164
164
  <td class="collapse" onclick="event.stopPropagation();">
165
- <input class="collapse-checkbox" type="checkbox" id="collapse{{initiator.id}}" onclick="collapse_initiator('{{initiator.id}}',this.checked)">
166
- <label class="collapse-icon" for="collapse{{initiator.id}}"><img class="icon" src="static/icons/chevron-direction-top-outline-icon.svg-+-$**HTTPDBG_VERSION**$" alt="click to collapse/expand"/></label>
165
+ <input class="collapse-checkbox" type="checkbox" id="collapse{{id}}" onclick="collapse_group('{{id}}',this.checked)">
166
+ <label class="collapse-icon" for="collapse{{id}}"><img class="icon" src="static/icons/chevron-direction-top-outline-icon.svg-+-$**HTTPDBG_VERSION**$" alt="click to collapse/expand"/></label>
167
167
  </td>
168
- <td colspan="3" class="initiator-label" title="{{initiator.long_label}}">
169
- {{initiator.short_label}}
168
+ <td colspan="3" class="group-label" title="{{full_label}}">
169
+ {{label}}
170
170
  </td>
171
171
  </tr>
172
172
  </tbody>
@@ -175,7 +175,7 @@
175
175
  <script id="template_request" type="x-tmpl-mustache">
176
176
  <tr id="request-{{id}}"
177
177
  data-filter="{{filter}}"
178
- data-initiator="{{initiator.id}}"
178
+ data-group="{{group_id}}"
179
179
  onclick="select_request(arguments[0], '{{id}}')"
180
180
  onkeypress="shortcut_request(event, '{{id}}')"
181
181
  class="request"
@@ -2,7 +2,7 @@
2
2
 
3
3
  let httpdbgApp = {
4
4
  "config": {
5
- "hide_initiator": {
5
+ "hide_group": {
6
6
  "checkbox": "cinitiator",
7
7
  "param": "hi",
8
8
  "css": ".initiator {display: none;}",