httpdbg 2.0.2__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httpdbg-2.0.2 → httpdbg-2.1.0}/PKG-INFO +7 -1
- {httpdbg-2.0.2 → httpdbg-2.1.0}/README.md +6 -0
- httpdbg-2.1.0/httpdbg/__init__.py +20 -0
- httpdbg-2.1.0/httpdbg/__main__.py +93 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/args.py +9 -3
- httpdbg-2.1.0/httpdbg/export.py +109 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/app.py +19 -51
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/api.js +34 -16
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/index.htm +59 -46
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg.egg-info/PKG-INFO +7 -1
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg.egg-info/SOURCES.txt +1 -0
- httpdbg-2.0.2/httpdbg/__init__.py +0 -7
- httpdbg-2.0.2/httpdbg/__main__.py +0 -69
- {httpdbg-2.0.2 → httpdbg-2.1.0}/LICENSE +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/env.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/exception.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/aiohttp.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/all.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/external.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/fastapi.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/flask.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/h2.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/http.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/httpx.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/pytest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/record.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/recordhttp1.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/recordhttp2.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/requests.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/socket.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/starlette.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/template_sitecustomize.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/unittest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/urllib3.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/utils.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/hooks/uvicorn.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/http_status_code.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/initiator.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/log.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/mode_console.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/mode_module.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/mode_script.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/preview.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/records.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/server.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/utils.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/api.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/render.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/style.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/table.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/pyproject.toml +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/setup.cfg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_api.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_cli.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_context_manager.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_h2.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_httpx.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_pytest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_requests.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_unittest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_initiator.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_mode_console.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_mode_script.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_multiprocess.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_preview.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_pyhttpdbg_server.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_server.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_server_hook_fastapi.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.0}/tests/test_server_hook_flask.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A very simple tool to debug HTTP(S) client and server requests.
|
|
5
5
|
Author-email: cle-b <cle@tictac.pm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -192,6 +192,8 @@ options:
|
|
|
192
192
|
--only-client record only HTTP client requests
|
|
193
193
|
--keep-up, -k keep the server up even if the requests have been read
|
|
194
194
|
--force-quit, -q stop the server even if the requests have not been read
|
|
195
|
+
--export-html EXPORT_HTML
|
|
196
|
+
the path to the export file
|
|
195
197
|
--console run a python console (default)
|
|
196
198
|
--module MODULE, -m MODULE
|
|
197
199
|
run library module as a script (the next args are passed to pytest as is)
|
|
@@ -225,6 +227,10 @@ The requests:
|
|
|
225
227
|
* 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).
|
|
226
228
|
* are automatically cleaned if a new execution is detected.
|
|
227
229
|
|
|
230
|
+
## export
|
|
231
|
+
|
|
232
|
+
You can export the HTTP traces in a single HTML file. In that case, the web interface is not available during the execution of the python command.
|
|
233
|
+
|
|
228
234
|
## limitations
|
|
229
235
|
|
|
230
236
|
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
@@ -166,6 +166,8 @@ options:
|
|
|
166
166
|
--only-client record only HTTP client requests
|
|
167
167
|
--keep-up, -k keep the server up even if the requests have been read
|
|
168
168
|
--force-quit, -q stop the server even if the requests have not been read
|
|
169
|
+
--export-html EXPORT_HTML
|
|
170
|
+
the path to the export file
|
|
169
171
|
--console run a python console (default)
|
|
170
172
|
--module MODULE, -m MODULE
|
|
171
173
|
run library module as a script (the next args are passed to pytest as is)
|
|
@@ -199,6 +201,10 @@ The requests:
|
|
|
199
201
|
* 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).
|
|
200
202
|
* are automatically cleaned if a new execution is detected.
|
|
201
203
|
|
|
204
|
+
## export
|
|
205
|
+
|
|
206
|
+
You can export the HTTP traces in a single HTML file. In that case, the web interface is not available during the execution of the python command.
|
|
207
|
+
|
|
202
208
|
## limitations
|
|
203
209
|
|
|
204
210
|
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
__version__ = "2.1.0"
|
|
2
|
+
|
|
3
|
+
__all__ = ["export_html", "httprecord", "HTTPRecords"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# lazy loading to avoid circular import issue
|
|
7
|
+
def __getattr__(name):
|
|
8
|
+
if name == "export_html":
|
|
9
|
+
from httpdbg.export import export_html
|
|
10
|
+
|
|
11
|
+
return export_html
|
|
12
|
+
if name == "httprecord":
|
|
13
|
+
from httpdbg.hooks.all import httprecord
|
|
14
|
+
|
|
15
|
+
return httprecord
|
|
16
|
+
if name == "HTTPRecords":
|
|
17
|
+
from httpdbg.records import HTTPRecords
|
|
18
|
+
|
|
19
|
+
return HTTPRecords
|
|
20
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from httpdbg import __version__
|
|
8
|
+
from httpdbg.args import read_args
|
|
9
|
+
from httpdbg.export import export_html
|
|
10
|
+
from httpdbg.hooks.all import httprecord
|
|
11
|
+
from httpdbg.log import set_env_for_logging
|
|
12
|
+
from httpdbg.server import httpdbg_srv
|
|
13
|
+
from httpdbg.mode_console import run_console
|
|
14
|
+
from httpdbg.mode_module import run_module
|
|
15
|
+
from httpdbg.mode_script import run_script
|
|
16
|
+
from httpdbg.records import HTTPRecords
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def print_msg(msg):
|
|
20
|
+
sep = ".... - - .--. -.. -... --. .... - - .--. -.. -... --. .... - - .--. -.. -... --."
|
|
21
|
+
msg = f"{sep}\n{msg}\n{sep}"
|
|
22
|
+
print(msg)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def pyhttpdbg(
|
|
26
|
+
params: argparse.Namespace, subparams: list[str], test_mode: bool = False
|
|
27
|
+
):
|
|
28
|
+
|
|
29
|
+
set_env_for_logging(params.log_level, params.log)
|
|
30
|
+
|
|
31
|
+
url = f"http://{params.host}:{params.port}/{'?hi=on' if params.console else ''}"
|
|
32
|
+
|
|
33
|
+
if not params.no_banner:
|
|
34
|
+
print_msg(f" httpdbg - HTTP(S) requests available at {url}")
|
|
35
|
+
|
|
36
|
+
sys.path.insert(0, "") # to mimic the default python command behavior
|
|
37
|
+
|
|
38
|
+
def run_recorder(
|
|
39
|
+
records: HTTPRecords,
|
|
40
|
+
initiators: Union[list[str], None],
|
|
41
|
+
record_server: bool,
|
|
42
|
+
test_mode: bool,
|
|
43
|
+
):
|
|
44
|
+
records.server = record_server
|
|
45
|
+
with httprecord(records, initiators, server=records.server):
|
|
46
|
+
if params.module:
|
|
47
|
+
run_module(subparams)
|
|
48
|
+
elif params.script:
|
|
49
|
+
run_script(subparams)
|
|
50
|
+
else:
|
|
51
|
+
run_console(records, test_mode)
|
|
52
|
+
|
|
53
|
+
if params.export_html:
|
|
54
|
+
records = HTTPRecords()
|
|
55
|
+
|
|
56
|
+
run_recorder(records, params.initiator, not params.only_client, test_mode)
|
|
57
|
+
|
|
58
|
+
export_html(records, Path(params.export_html))
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
with httpdbg_srv(params.host, params.port) as records:
|
|
62
|
+
|
|
63
|
+
run_recorder(records, params.initiator, not params.only_client, test_mode)
|
|
64
|
+
|
|
65
|
+
if not (params.force_quit or test_mode):
|
|
66
|
+
if not params.no_banner:
|
|
67
|
+
print_msg(f" httpdbg - HTTP(S) requests available at {url}")
|
|
68
|
+
|
|
69
|
+
if params.keep_up:
|
|
70
|
+
input("Press enter to quit")
|
|
71
|
+
else:
|
|
72
|
+
# we keep the server up until all the requests have been loaded in the web interface
|
|
73
|
+
print(
|
|
74
|
+
"Waiting until all the requests have been loaded in the web interface."
|
|
75
|
+
)
|
|
76
|
+
print("Press Ctrl+C to quit.")
|
|
77
|
+
try:
|
|
78
|
+
while records.unread:
|
|
79
|
+
time.sleep(0.5)
|
|
80
|
+
except KeyboardInterrupt: # pragma: no cover
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def pyhttpdbg_entry_point(test_mode=False):
|
|
85
|
+
params, subparams = read_args(sys.argv[1:])
|
|
86
|
+
if params.version:
|
|
87
|
+
print(__version__)
|
|
88
|
+
else:
|
|
89
|
+
pyhttpdbg(params, subparams, test_mode=test_mode)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
pyhttpdbg_entry_point()
|
|
@@ -40,22 +40,28 @@ def read_args(args: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
|
|
40
40
|
help="record only HTTP client requests",
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
server_or_export = parser.add_mutually_exclusive_group()
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
server_or_export.add_argument(
|
|
46
46
|
"--keep-up",
|
|
47
47
|
"-k",
|
|
48
48
|
action="store_true",
|
|
49
49
|
help="keep the server up even if the requests have been read",
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
server_or_export.add_argument(
|
|
53
53
|
"--force-quit",
|
|
54
54
|
"-q",
|
|
55
55
|
action="store_true",
|
|
56
56
|
help="stop the server even if the requests have not been read",
|
|
57
57
|
)
|
|
58
58
|
|
|
59
|
+
server_or_export.add_argument(
|
|
60
|
+
"--export-html",
|
|
61
|
+
type=str,
|
|
62
|
+
help="the path to the export file",
|
|
63
|
+
)
|
|
64
|
+
|
|
59
65
|
parser.add_argument(
|
|
60
66
|
"--log-level",
|
|
61
67
|
type=LogLevel,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
|
|
7
|
+
import httpdbg
|
|
8
|
+
from httpdbg import HTTPRecords
|
|
9
|
+
from httpdbg.webapp.api import RequestListPayload, RequestPayload
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_html(records: HTTPRecords, for_export: bool = True) -> str:
|
|
13
|
+
|
|
14
|
+
current_dir = Path(__file__).resolve().parent
|
|
15
|
+
|
|
16
|
+
with open(Path(current_dir) / "webapp/static/index.htm") as findexhtml:
|
|
17
|
+
html = findexhtml.read()
|
|
18
|
+
|
|
19
|
+
html = html.replace("$**HTTPDBG_VERSION**$", httpdbg.__version__)
|
|
20
|
+
|
|
21
|
+
# favicon
|
|
22
|
+
with open(current_dir / "webapp/static/favicon.ico", "rb") as ffavicon:
|
|
23
|
+
b64_icon = base64.b64encode(ffavicon.read()).decode("utf-8")
|
|
24
|
+
data_uri = f"data:image/x-icon;base64,{b64_icon}"
|
|
25
|
+
html = html.replace(
|
|
26
|
+
'<link rel="shortcut icon" href="static/favicon.ico">',
|
|
27
|
+
f'<link rel="icon" type="image/x-icon" href="{data_uri}">',
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# icons
|
|
31
|
+
def svg_file_to_symbol(svg_path: Path, symbol_id: str) -> str:
|
|
32
|
+
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
|
33
|
+
root = ET.parse(svg_path).getroot()
|
|
34
|
+
symbol = ET.Element(
|
|
35
|
+
"symbol", {"id": symbol_id, "viewBox": root.attrib["viewBox"]}
|
|
36
|
+
)
|
|
37
|
+
for child in list(root):
|
|
38
|
+
symbol.append(child)
|
|
39
|
+
return ET.tostring(symbol, encoding="unicode")
|
|
40
|
+
|
|
41
|
+
icons_inline = ""
|
|
42
|
+
for icon_path in (current_dir / "webapp/static/icons").glob("*.svg"):
|
|
43
|
+
icons_inline += "\n " + svg_file_to_symbol(
|
|
44
|
+
icon_path, icon_path.name[:-4]
|
|
45
|
+
)
|
|
46
|
+
icons_inline = f'<svg width="0" height="0" style="position:absolute;visibility:hidden" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">{icons_inline}\n </svg>'
|
|
47
|
+
html = html.replace("$**PRELOAD_ICONS**$", icons_inline)
|
|
48
|
+
|
|
49
|
+
# css
|
|
50
|
+
stylesheet_pattern = (
|
|
51
|
+
r'<link\s+[^>]*rel=["\']stylesheet["\'][^>]*href=["\']([^"\']+)["\']'
|
|
52
|
+
)
|
|
53
|
+
for stylesheet in re.findall(stylesheet_pattern, html):
|
|
54
|
+
with open(Path(current_dir) / "webapp" / stylesheet) as fcss:
|
|
55
|
+
html = html.replace(
|
|
56
|
+
f'<link rel="stylesheet" href="{stylesheet}">',
|
|
57
|
+
f"<style>{fcss.read()}</style>",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# js
|
|
61
|
+
javascript_pattern = r'<script\s+[^>]*src=["\']([^"\']+)["\']'
|
|
62
|
+
for js in re.findall(javascript_pattern, html):
|
|
63
|
+
with open(Path(current_dir) / "webapp" / js) as fjs:
|
|
64
|
+
html = html.replace(
|
|
65
|
+
f'<script src="{js}"></script>', f"<script>{fjs.read()}</script>"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# static export of the requests data
|
|
69
|
+
if for_export:
|
|
70
|
+
static_all_requests = json.dumps(
|
|
71
|
+
records, cls=RequestListPayload, ensure_ascii=False
|
|
72
|
+
)
|
|
73
|
+
map_requests: dict[str, object] = dict()
|
|
74
|
+
for record in records:
|
|
75
|
+
map_requests[record.id] = json.loads(json.dumps(record, cls=RequestPayload))
|
|
76
|
+
static_requests: str = json.dumps(map_requests, ensure_ascii=False)
|
|
77
|
+
|
|
78
|
+
def safe_for_script_tag(s: str) -> str:
|
|
79
|
+
return s.replace("</script>", "<\\/script>")
|
|
80
|
+
|
|
81
|
+
html_export = f"""
|
|
82
|
+
<script id="all-requests" type="application/json">
|
|
83
|
+
{safe_for_script_tag(static_all_requests)}
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<script id="requests-map" type="application/json">
|
|
87
|
+
{safe_for_script_tag(static_requests)}
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<script>
|
|
91
|
+
global.static_all_requests = JSON.parse(
|
|
92
|
+
document.getElementById('all-requests').textContent
|
|
93
|
+
);
|
|
94
|
+
global.static_requests = JSON.parse(
|
|
95
|
+
document.getElementById('requests-map').textContent
|
|
96
|
+
);
|
|
97
|
+
</script>
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
html = html.replace("$**EXPORT**$", html_export)
|
|
101
|
+
else:
|
|
102
|
+
html = html.replace("$**EXPORT**$", "")
|
|
103
|
+
|
|
104
|
+
return html
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def export_html(records: HTTPRecords, filename: Path):
|
|
108
|
+
with open(filename, "w") as f:
|
|
109
|
+
f.write(generate_html(records))
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
from collections.abc import Callable
|
|
3
2
|
from contextlib import contextmanager
|
|
4
|
-
import glob
|
|
5
3
|
from http.server import BaseHTTPRequestHandler
|
|
6
4
|
import json
|
|
7
|
-
import
|
|
8
|
-
import os
|
|
5
|
+
from pathlib import Path
|
|
9
6
|
import re
|
|
10
7
|
from urllib.parse import ParseResult
|
|
11
8
|
from urllib.parse import parse_qs
|
|
12
9
|
from urllib.parse import urlparse
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
from httpdbg import __version__
|
|
16
11
|
from httpdbg.log import logger
|
|
17
12
|
from httpdbg.webapp.api import RequestListPayload, RequestPayload
|
|
18
13
|
from httpdbg.records import HTTPRecords
|
|
@@ -56,61 +51,34 @@ class HttpbgHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
|
56
51
|
break
|
|
57
52
|
|
|
58
53
|
def serve_static(self, url: ParseResult):
|
|
59
|
-
base_path = os.path.dirname(os.path.realpath(__file__))
|
|
60
|
-
|
|
61
|
-
if not (
|
|
62
|
-
(url.path.lower() in ["/", "index.htm", "index.html"])
|
|
63
|
-
or url.path.startswith("/static/")
|
|
64
|
-
):
|
|
65
|
-
return False
|
|
66
54
|
|
|
67
55
|
if url.path.lower() in ["/", "index.htm", "index.html"]:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# get real path
|
|
71
|
-
self.path = self.path.split("-+-")[0]
|
|
72
|
-
|
|
73
|
-
fullpath = os.path.realpath(os.path.join(base_path, self.path[1:]))
|
|
56
|
+
from httpdbg.export import generate_html
|
|
74
57
|
|
|
75
|
-
if not fullpath.startswith(base_path):
|
|
76
|
-
# if the file is not in the static directory, we don't serve it
|
|
77
|
-
return self.serve_not_found(url)
|
|
78
|
-
|
|
79
|
-
if not os.path.exists(fullpath):
|
|
80
|
-
return self.serve_not_found(url)
|
|
81
|
-
else:
|
|
82
58
|
self.send_response(200)
|
|
83
|
-
self.send_header(
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
if self.path == "/static/index.htm":
|
|
87
|
-
self.send_header_no_cache()
|
|
88
|
-
else:
|
|
89
|
-
self.send_header_with_cache(604800)
|
|
59
|
+
self.send_header("content-type", "text/html")
|
|
60
|
+
self.send_header_no_cache()
|
|
90
61
|
self.end_headers()
|
|
62
|
+
self.wfile.write(
|
|
63
|
+
generate_html(self.records, for_export=False).encode("utf-8")
|
|
64
|
+
)
|
|
65
|
+
return True
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for icon in glob.glob(
|
|
98
|
-
os.path.realpath(base_path) + "/static/icons/*.svg"
|
|
99
|
-
):
|
|
100
|
-
icon_path = icon.replace(os.path.realpath(base_path) + "/", "")
|
|
101
|
-
icons += f" <link rel='preload' href='{icon_path}-+-$**HTTPDBG_VERSION**$' as='image' type='image/svg+xml' />\n"
|
|
67
|
+
if url.path.lower() == "favicon.ico":
|
|
68
|
+
self.send_response(200)
|
|
69
|
+
self.send_header("content-type", "image/x-icon")
|
|
70
|
+
self.send_header_no_cache()
|
|
71
|
+
self.end_headers()
|
|
102
72
|
|
|
103
|
-
|
|
104
|
-
b"$**PRELOAD_ICONS**$", icons.encode("utf-8")
|
|
105
|
-
)
|
|
73
|
+
current_dir = Path(__file__).resolve().parent
|
|
106
74
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
75
|
+
with open(Path(current_dir) / "static/favicon.ico") as f:
|
|
76
|
+
filecontent = f.read()
|
|
77
|
+
self.wfile.write(filecontent.encode("utf-8"))
|
|
110
78
|
|
|
111
|
-
|
|
79
|
+
return True
|
|
112
80
|
|
|
113
|
-
return
|
|
81
|
+
return False
|
|
114
82
|
|
|
115
83
|
def serve_requests(self, url: ParseResult):
|
|
116
84
|
if not (url.path.lower() == "/requests"):
|
|
@@ -5,7 +5,7 @@ const global = {
|
|
|
5
5
|
sessions: {},
|
|
6
6
|
requests: {},
|
|
7
7
|
initiators: {},
|
|
8
|
-
groups: {},
|
|
8
|
+
groups: {},
|
|
9
9
|
connected: false,
|
|
10
10
|
group_collapse: [],
|
|
11
11
|
groupby: "default"
|
|
@@ -24,32 +24,35 @@ function save_request(request_id, request, session_id) {
|
|
|
24
24
|
}
|
|
25
25
|
request.session_id = session_id;
|
|
26
26
|
request.initiator = initiator;
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
if (request.in_progress) {
|
|
29
|
-
request.status_code_view = '<
|
|
29
|
+
request.status_code_view = '<svg class="icon" alt="loading"><use href="#wait-sandclock-icon"></use></svg>';
|
|
30
30
|
} else {
|
|
31
31
|
switch (request.status_code) {
|
|
32
32
|
case 0:
|
|
33
|
-
request.status_code_view = '<
|
|
33
|
+
request.status_code_view = '<svg class="icon" alt="loading"><use href="#wait-sandclock-icon"></use></svg>';
|
|
34
34
|
break;
|
|
35
35
|
case -1:
|
|
36
|
-
request.status_code_view = '<
|
|
36
|
+
request.status_code_view = '<svg class="icon" alt="load failed"><use href="#math-multiplication-icon"></use></svg>';
|
|
37
37
|
break;
|
|
38
38
|
default:
|
|
39
39
|
request.status_code_view = request.status_code;
|
|
40
40
|
break;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
global.requests[request_id] = request;
|
|
45
|
-
|
|
43
|
+
|
|
44
|
+
global.requests[request_id] = request;
|
|
45
|
+
|
|
46
46
|
if (!request.pin) {
|
|
47
47
|
get_request(request_id);
|
|
48
|
-
}
|
|
48
|
+
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
async function
|
|
52
|
+
async function load_all_requests() {
|
|
53
|
+
if (typeof global.static_all_requests !== "undefined") {
|
|
54
|
+
return global.static_all_requests;
|
|
55
|
+
}
|
|
53
56
|
|
|
54
57
|
var requests_already_loaded = 0
|
|
55
58
|
for (const [request_id, request] of Object.entries(global.requests)) {
|
|
@@ -62,15 +65,22 @@ async function get_all_requests() {
|
|
|
62
65
|
"requests_already_loaded": requests_already_loaded,
|
|
63
66
|
})
|
|
64
67
|
|
|
65
|
-
await fetch(url)
|
|
66
|
-
|
|
68
|
+
const res = await fetch(url);
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async function get_all_requests() {
|
|
75
|
+
|
|
76
|
+
await load_all_requests()
|
|
67
77
|
.then(data => {
|
|
68
78
|
global.connected = true;
|
|
69
79
|
|
|
70
80
|
if (data.session.id != global.session) {
|
|
71
|
-
clean();
|
|
81
|
+
clean();
|
|
72
82
|
global.session = data.session.id;
|
|
73
|
-
global.sessions[data.session.id] = data.session;
|
|
83
|
+
global.sessions[data.session.id] = data.session;
|
|
74
84
|
};
|
|
75
85
|
|
|
76
86
|
// for the initiators and the groups, we can just save them without any verification
|
|
@@ -93,13 +103,21 @@ async function get_all_requests() {
|
|
|
93
103
|
.catch((error) => {
|
|
94
104
|
global.connected = false;
|
|
95
105
|
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function load_request(request_id) {
|
|
109
|
+
if (typeof global.static_requests !== "undefined") {
|
|
110
|
+
return global.static_requests[request_id];
|
|
111
|
+
}
|
|
96
112
|
|
|
113
|
+
const res = await fetch("/request/" + request_id);
|
|
114
|
+
const data = await res.json();
|
|
115
|
+
return data;
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
async function get_request(request_id) {
|
|
100
119
|
|
|
101
|
-
await
|
|
102
|
-
.then(res => res.json())
|
|
120
|
+
await load_request(request_id)
|
|
103
121
|
.then(data => {
|
|
104
122
|
global.connected = true;
|
|
105
123
|
global.requests[request_id].filter = prepare_for_filter(global.requests[request_id].url);
|
|
@@ -6,25 +6,25 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>httpdbg</title>
|
|
8
8
|
|
|
9
|
-
<link rel="shortcut icon" href="static/favicon.ico
|
|
9
|
+
<link rel="shortcut icon" href="static/favicon.ico">
|
|
10
10
|
|
|
11
11
|
$**PRELOAD_ICONS**$
|
|
12
12
|
|
|
13
|
-
<link rel="stylesheet" href="static/style.css
|
|
14
|
-
<link rel="stylesheet" href="static/table.css
|
|
15
|
-
<link rel="stylesheet" href="static/column.css
|
|
16
|
-
<link rel="stylesheet" href="static/detail.css
|
|
17
|
-
<link rel="stylesheet" href="static/configuration.css
|
|
13
|
+
<link rel="stylesheet" href="static/style.css">
|
|
14
|
+
<link rel="stylesheet" href="static/table.css">
|
|
15
|
+
<link rel="stylesheet" href="static/column.css">
|
|
16
|
+
<link rel="stylesheet" href="static/detail.css">
|
|
17
|
+
<link rel="stylesheet" href="static/configuration.css">
|
|
18
18
|
<style type="text/css" id="filter"></style>
|
|
19
19
|
<style type="text/css" id="configurationcss"></style>
|
|
20
20
|
<style type="text/css" id="serverstatuscss"></style>
|
|
21
21
|
<style type="text/css" id="collapsegroupcss"></style>
|
|
22
|
-
<script src="static/mustache.min.js
|
|
23
|
-
<script src="static/main.js
|
|
24
|
-
<script src="static/api.js
|
|
25
|
-
<script src="static/render.js
|
|
26
|
-
<script src="static/filter.js
|
|
27
|
-
<script src="static/configuration.js
|
|
22
|
+
<script src="static/mustache.min.js"></script>
|
|
23
|
+
<script src="static/main.js"></script>
|
|
24
|
+
<script src="static/api.js"></script>
|
|
25
|
+
<script src="static/render.js"></script>
|
|
26
|
+
<script src="static/filter.js"></script>
|
|
27
|
+
<script src="static/configuration.js"></script>
|
|
28
28
|
</head>
|
|
29
29
|
|
|
30
30
|
<body>
|
|
@@ -32,15 +32,16 @@
|
|
|
32
32
|
<nav id="navbar">
|
|
33
33
|
<div class="menu-left">
|
|
34
34
|
<button id="trash" type="button" onclick="clean(true)" title="Delete all unpinned requests.">
|
|
35
|
-
<
|
|
36
|
-
|
|
35
|
+
<svg class="icon">
|
|
36
|
+
<use href="#recycle-bin-line-icon"></use>
|
|
37
|
+
</svg>
|
|
37
38
|
</button>
|
|
38
39
|
<form id="configform" onsubmit="return false;">
|
|
39
40
|
-
|
|
40
41
|
<input type="checkbox" id="ckeepsession" name="kp" onchange="load_config_from_form(true)">
|
|
41
42
|
<label for="ckeepsession"
|
|
42
|
-
title="If you choose to keep
|
|
43
|
-
keep
|
|
43
|
+
title="If you choose to keep hisotry, the requests will not be deleted when starting a new session.">
|
|
44
|
+
keep history
|
|
44
45
|
</label>
|
|
45
46
|
-
|
|
46
47
|
<label for="groupby-select">group by</label>
|
|
@@ -55,40 +56,48 @@
|
|
|
55
56
|
onchange="load_config_from_form(true)">
|
|
56
57
|
<label for="cinitiator"
|
|
57
58
|
title="Hide the groups. Be aware that the order of the requests will not be affected.">
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
<svg class="icon config-checkbox-unchecked">
|
|
60
|
+
<use href="#eye-icon"></use>
|
|
61
|
+
</svg>
|
|
62
|
+
<svg class="icon config-checkbox-checked">
|
|
63
|
+
<use href="#eye-blind-icon"></use>
|
|
64
|
+
</svg>
|
|
62
65
|
group
|
|
63
66
|
</label>
|
|
64
67
|
-
|
|
65
68
|
<input class="config-checkbox" type="checkbox" id="ctag" name="ht"
|
|
66
69
|
onchange="load_config_from_form(true)">
|
|
67
70
|
<label for="ctag" title="Hide the tags.">
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
<svg class="icon config-checkbox-unchecked">
|
|
72
|
+
<use href="#eye-icon"></use>
|
|
73
|
+
</svg>
|
|
74
|
+
<svg class="icon config-checkbox-checked">
|
|
75
|
+
<use href="#eye-blind-icon"></use>
|
|
76
|
+
</svg>
|
|
72
77
|
tag
|
|
73
78
|
</label>
|
|
74
79
|
-
|
|
75
80
|
<input class="config-checkbox" type="checkbox" id="curl" name="hn"
|
|
76
81
|
onchange="load_config_from_form(true)">
|
|
77
82
|
<label for="curl" title="Hide the network location and the scheme in the url.">
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
<svg class="icon config-checkbox-unchecked">
|
|
84
|
+
<use href="#eye-icon"></use>
|
|
85
|
+
</svg>
|
|
86
|
+
<svg class="icon config-checkbox-checked">
|
|
87
|
+
<use href="#eye-blind-icon"></use>
|
|
88
|
+
</svg>
|
|
82
89
|
netloc
|
|
83
90
|
</label>
|
|
84
91
|
-
|
|
85
92
|
<input class="config-checkbox" type="checkbox" id="cwrap" name="wl"
|
|
86
93
|
onchange="load_config_from_form(true)">
|
|
87
94
|
<label for="cwrap" title="Wrap lines on the request/response payload content.">
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
<svg class="icon config-checkbox-unchecked">
|
|
96
|
+
<use href="#lock-open-line-icon"></use>
|
|
97
|
+
</svg>
|
|
98
|
+
<svg class="icon config-checkbox-checked">
|
|
99
|
+
<use href="#lock-line-icon"></use>
|
|
100
|
+
</svg>
|
|
92
101
|
wrap lines
|
|
93
102
|
</label>
|
|
94
103
|
-
|
|
@@ -96,10 +105,12 @@
|
|
|
96
105
|
onchange="load_config_from_form(true)">
|
|
97
106
|
<label for="craw"
|
|
98
107
|
title="Avoid formatting the request/response payload content according to its content type.">
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
<svg class="icon config-checkbox-unchecked">
|
|
109
|
+
<use href="#lock-open-line-icon"></use>
|
|
110
|
+
</svg>
|
|
111
|
+
<svg class="icon config-checkbox-checked">
|
|
112
|
+
<use href="#lock-line-icon"></use>
|
|
113
|
+
</svg>
|
|
103
114
|
raw data
|
|
104
115
|
</label>
|
|
105
116
|
</form>
|
|
@@ -107,8 +118,9 @@
|
|
|
107
118
|
<div class="menu-right">
|
|
108
119
|
<span title="httpdbg v$**HTTPDBG_VERSION**$">httpdbg</span>
|
|
109
120
|
<button onclick="help()" title="open help">
|
|
110
|
-
<
|
|
111
|
-
|
|
121
|
+
<svg class="icon">
|
|
122
|
+
<use href="#question-mark-round-line-icon"></use>
|
|
123
|
+
</svg>
|
|
112
124
|
</button>
|
|
113
125
|
</div>
|
|
114
126
|
</nav>
|
|
@@ -165,9 +177,9 @@
|
|
|
165
177
|
<th class="url">
|
|
166
178
|
url
|
|
167
179
|
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
<svg class="icon icon-filter">
|
|
181
|
+
<use href="#filter-outline-icon"></use>
|
|
182
|
+
</svg>
|
|
171
183
|
|
|
172
184
|
<label for="filter-url" class="visuallyhidden">
|
|
173
185
|
filter for url
|
|
@@ -186,7 +198,7 @@
|
|
|
186
198
|
<tr class="group" data-orderby="{{tbegin}}">
|
|
187
199
|
<td class="collapse" onclick="event.stopPropagation();">
|
|
188
200
|
<input class="collapse-checkbox" type="checkbox" id="collapse{{id}}" onclick="collapse_group('{{id}}',this.checked)">
|
|
189
|
-
<label class="collapse-icon" for="collapse{{id}}"><
|
|
201
|
+
<label class="collapse-icon" for="collapse{{id}}"><svg class="icon"><use href="#chevron-direction-top-outline-icon"></use></svg></label>
|
|
190
202
|
</td>
|
|
191
203
|
<td colspan="3" class="group-label" title="{{full_label}}">
|
|
192
204
|
{{label}}
|
|
@@ -207,7 +219,7 @@
|
|
|
207
219
|
data-qa-request="{{id}}">
|
|
208
220
|
<td class="pin" onclick="event.stopPropagation();">
|
|
209
221
|
<input class="pin-checkbox" type="checkbox" id="pin{{id}}" onclick="pin_request('{{id}}',this.checked)" {{pin}}>
|
|
210
|
-
<label class="pin-icon" for="pin{{id}}"><
|
|
222
|
+
<label class="pin-icon" for="pin{{id}}"><svg class="icon"><use href="#push-pin-green-icon.svg"></use></svg></label>
|
|
211
223
|
</td>
|
|
212
224
|
<td class="status"><span title="{{reason}}" data-qa-request-status>{{{status_code_view}}}</span></td>
|
|
213
225
|
<td class="method"><span title="{{verb}}" data-qa-request-method>{{verb}}</span></td>
|
|
@@ -348,14 +360,14 @@
|
|
|
348
360
|
|
|
349
361
|
{{#body}}
|
|
350
362
|
{{#body.path}}
|
|
351
|
-
<a href="{{body.path}}" download="{{body.filename}}" class="need-server"><button title="download"><
|
|
352
|
-
<
|
|
363
|
+
<a href="{{body.path}}" download="{{body.filename}}" class="need-server"><button title="download"><svg class="icon"><use href="#download-install-line-icon"></use></svg></button></a>
|
|
364
|
+
<svg class="icon need-server-info"><use href="#information-mark-circle-outline-icon"></use></svg>
|
|
353
365
|
{{/body.path}}
|
|
354
366
|
|
|
355
367
|
{{#body.text}}
|
|
356
368
|
|
|
357
369
|
{{#body.raw_text}}
|
|
358
|
-
<button title="copy to clipboard" onclick="navigator.clipboard.writeText({{body.raw_text}});"><
|
|
370
|
+
<button title="copy to clipboard" onclick="navigator.clipboard.writeText({{body.raw_text}});"><svg class="icon"><use href="#files-papers-outline-icon"></use></svg></button>
|
|
359
371
|
{{/body.raw_text}}
|
|
360
372
|
|
|
361
373
|
<label><input type="checkbox" name="ckwraptext" onclick='show_wrap_lines(this.parentNode.parentNode.querySelector(".previewcode"));'>Wrap lines</label>
|
|
@@ -418,6 +430,7 @@
|
|
|
418
430
|
</table>
|
|
419
431
|
</div>
|
|
420
432
|
</div>
|
|
433
|
+
$**EXPORT**$
|
|
421
434
|
<script>
|
|
422
435
|
load_config_from_url(true);
|
|
423
436
|
update_filter_url();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A very simple tool to debug HTTP(S) client and server requests.
|
|
5
5
|
Author-email: cle-b <cle@tictac.pm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -192,6 +192,8 @@ options:
|
|
|
192
192
|
--only-client record only HTTP client requests
|
|
193
193
|
--keep-up, -k keep the server up even if the requests have been read
|
|
194
194
|
--force-quit, -q stop the server even if the requests have not been read
|
|
195
|
+
--export-html EXPORT_HTML
|
|
196
|
+
the path to the export file
|
|
195
197
|
--console run a python console (default)
|
|
196
198
|
--module MODULE, -m MODULE
|
|
197
199
|
run library module as a script (the next args are passed to pytest as is)
|
|
@@ -225,6 +227,10 @@ The requests:
|
|
|
225
227
|
* 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).
|
|
226
228
|
* are automatically cleaned if a new execution is detected.
|
|
227
229
|
|
|
230
|
+
## export
|
|
231
|
+
|
|
232
|
+
You can export the HTTP traces in a single HTML file. In that case, the web interface is not available during the execution of the python command.
|
|
233
|
+
|
|
228
234
|
## limitations
|
|
229
235
|
|
|
230
236
|
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import time
|
|
3
|
-
|
|
4
|
-
from httpdbg import __version__
|
|
5
|
-
from httpdbg.args import read_args
|
|
6
|
-
from httpdbg.hooks.all import httprecord
|
|
7
|
-
from httpdbg.log import set_env_for_logging
|
|
8
|
-
from httpdbg.server import httpdbg_srv
|
|
9
|
-
from httpdbg.mode_console import run_console
|
|
10
|
-
from httpdbg.mode_module import run_module
|
|
11
|
-
from httpdbg.mode_script import run_script
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def print_msg(msg):
|
|
15
|
-
sep = ".... - - .--. -.. -... --. .... - - .--. -.. -... --. .... - - .--. -.. -... --."
|
|
16
|
-
msg = f"{sep}\n{msg}\n{sep}"
|
|
17
|
-
print(msg)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def pyhttpdbg(params, subparams, test_mode=False):
|
|
21
|
-
|
|
22
|
-
set_env_for_logging(params.log_level, params.log)
|
|
23
|
-
|
|
24
|
-
url = f"http://{params.host}:{params.port}/{'?hi=on' if params.console else ''}"
|
|
25
|
-
|
|
26
|
-
if not params.no_banner:
|
|
27
|
-
print_msg(f" httpdbg - HTTP(S) requests available at {url}")
|
|
28
|
-
|
|
29
|
-
sys.path.insert(0, "") # to mimic the default python command behavior
|
|
30
|
-
|
|
31
|
-
with httpdbg_srv(params.host, params.port) as records:
|
|
32
|
-
records.server = not params.only_client
|
|
33
|
-
with httprecord(records, params.initiator, server=records.server):
|
|
34
|
-
if params.module:
|
|
35
|
-
run_module(subparams)
|
|
36
|
-
elif params.script:
|
|
37
|
-
run_script(subparams)
|
|
38
|
-
else:
|
|
39
|
-
run_console(records, test_mode)
|
|
40
|
-
|
|
41
|
-
if not (params.force_quit or test_mode):
|
|
42
|
-
if not params.no_banner:
|
|
43
|
-
print_msg(f" httpdbg - HTTP(S) requests available at {url}")
|
|
44
|
-
|
|
45
|
-
if params.keep_up:
|
|
46
|
-
input("Press enter to quit")
|
|
47
|
-
else:
|
|
48
|
-
# we keep the server up until all the requests have been loaded in the web interface
|
|
49
|
-
print(
|
|
50
|
-
"Waiting until all the requests have been loaded in the web interface."
|
|
51
|
-
)
|
|
52
|
-
print("Press Ctrl+C to quit.")
|
|
53
|
-
try:
|
|
54
|
-
while records.unread:
|
|
55
|
-
time.sleep(0.5)
|
|
56
|
-
except KeyboardInterrupt: # pragma: no cover
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def pyhttpdbg_entry_point(test_mode=False):
|
|
61
|
-
params, subparams = read_args(sys.argv[1:])
|
|
62
|
-
if params.version:
|
|
63
|
-
print(__version__)
|
|
64
|
-
else:
|
|
65
|
-
pyhttpdbg(params, subparams, test_mode=test_mode)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if __name__ == "__main__":
|
|
69
|
-
pyhttpdbg_entry_point()
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{httpdbg-2.0.2 → httpdbg-2.1.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg
RENAMED
|
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
|
|
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
|