httpdbg 2.0.2__tar.gz → 2.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httpdbg-2.0.2 → httpdbg-2.1.1}/PKG-INFO +7 -1
- {httpdbg-2.0.2 → httpdbg-2.1.1}/README.md +6 -0
- httpdbg-2.1.1/httpdbg/__init__.py +20 -0
- httpdbg-2.1.1/httpdbg/__main__.py +93 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/args.py +9 -3
- httpdbg-2.1.1/httpdbg/export.py +109 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/app.py +19 -51
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/api.js +34 -16
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/index.htm +97 -49
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/render.js +15 -3
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg.egg-info/PKG-INFO +7 -1
- {httpdbg-2.0.2 → httpdbg-2.1.1}/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.1}/LICENSE +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/env.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/exception.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/aiohttp.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/all.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/external.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/fastapi.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/flask.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/h2.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/http.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/httpx.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/pytest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/record.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/recordhttp1.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/recordhttp2.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/requests.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/socket.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/starlette.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/template_sitecustomize.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/unittest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/urllib3.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/utils.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/hooks/uvicorn.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/http_status_code.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/initiator.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/log.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/mode_console.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/mode_module.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/mode_script.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/preview.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/records.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/server.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/utils.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/api.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/style.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg/webapp/static/table.css +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/pyproject.toml +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/setup.cfg +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_api.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_cli.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_context_manager.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_h2.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_httpx.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_pytest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_requests.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_unittest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_initiator.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_mode_console.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_mode_script.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_multiprocess.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_preview.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_pyhttpdbg_server.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_server.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/tests/test_server_hook_fastapi.py +0 -0
- {httpdbg-2.0.2 → httpdbg-2.1.1}/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.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: A very simple tool to debug HTTP(S) client and server requests.
|
|
5
5
|
Author-email: cle-b <cle@tictac.pm>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -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.1"
|
|
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);
|