From e7387ceaf1149cf52c48260cae926d814cf884f6 Mon Sep 17 00:00:00 2001 From: cle-b Date: Sat, 28 Sep 2024 10:34:53 +0200 Subject: [PATCH] add configuration option for web interface address (#144) --- README.md | 1 + httpdbg/__init__.py | 2 +- httpdbg/__main__.py | 4 +-- httpdbg/args.py | 6 ++++ httpdbg/exception.py | 7 ++++ httpdbg/server.py | 16 ++++++--- pytest.ini | 1 + tests/test_api.py | 62 ++++++++++++++++---------------- tests/test_mode_console.py | 6 ++-- tests/test_mode_module_pytest.py | 20 +++++------ tests/test_mode_script.py | 18 +++++----- tests/test_server.py | 40 +++++++++++++++++++++ tests/ui/test_web.py | 4 +-- 13 files changed, 124 insertions(+), 63 deletions(-) create mode 100644 httpdbg/exception.py create mode 100644 tests/test_server.py diff --git a/README.md b/README.md index 5044268..574174a 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ httdbg - a very simple tool to debug HTTP(S) client requests options: -h, --help show this help message and exit + --host HOST the web interface host IP address --port PORT, -p PORT the web interface port --version, -v print the httpdbg version --initiator INITIATOR, -i INITIATOR diff --git a/httpdbg/__init__.py b/httpdbg/__init__.py index c392686..9ed1910 100644 --- a/httpdbg/__init__.py +++ b/httpdbg/__init__.py @@ -3,6 +3,6 @@ from httpdbg.records import HTTPRecords -__version__ = "0.22.0" +__version__ = "0.23.0" __all__ = ["httprecord", "HTTPRecords"] diff --git a/httpdbg/__main__.py b/httpdbg/__main__.py index 5dd5b0d..01dbe9f 100644 --- a/httpdbg/__main__.py +++ b/httpdbg/__main__.py @@ -18,13 +18,13 @@ def print_msg(msg): def pyhttpdbg(params, subparams, test_mode=False): - url = f"http://localhost:{params.port}/{'?hi=on' if params.console else ''}" + url = f"http://{params.host}:{params.port}/{'?hi=on' if params.console else ''}" print_msg(f" httpdbg - HTTP(S) requests available at {url}") sys.path.insert(0, "") # to mimic the default python command behavior - with httpdbg_srv(params.port) as records: + with httpdbg_srv(params.host, params.port) as records: with httprecord(records, params.initiator): if params.module: run_module(subparams) diff --git a/httpdbg/args.py b/httpdbg/args.py index de7917a..f1530a3 100644 --- a/httpdbg/args.py +++ b/httpdbg/args.py @@ -15,6 +15,12 @@ def read_args(args: List[str]) -> Tuple[argparse.Namespace, List[str]]: parser = argparse.ArgumentParser( description="httdbg - a very simple tool to debug HTTP(S) client requests" ) + parser.add_argument( + "--host", + type=str, + default="localhost", + help="the web interface host IP address", + ) parser.add_argument( "--port", "-p", type=int, default=4909, help="the web interface port" ) diff --git a/httpdbg/exception.py b/httpdbg/exception.py new file mode 100644 index 0000000..5907121 --- /dev/null +++ b/httpdbg/exception.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + + +class HttpdbgException(Exception): + """An issue occurred in httpdbg.""" + + pass diff --git a/httpdbg/server.py b/httpdbg/server.py index b4dcf20..3f394d5 100644 --- a/httpdbg/server.py +++ b/httpdbg/server.py @@ -4,17 +4,23 @@ import threading from typing import Generator +from httpdbg.exception import HttpdbgException from httpdbg.records import HTTPRecords from httpdbg.webapp import HttpbgHTTPRequestHandler @contextmanager -def httpdbg_srv(port: int) -> Generator[HTTPRecords, None, None]: +def httpdbg_srv(host: str, port: int) -> Generator[HTTPRecords, None, None]: server = None records = HTTPRecords() try: - server = ServerThread(port, records) - server.start() + try: + server = ServerThread(host, port, records) + server.start() + except Exception as ex_server_start: + raise HttpdbgException( + f"An issue occurred while starting the httpdbg web interface: [{str(ex_server_start)}]" + ) yield records @@ -26,14 +32,14 @@ def httpdbg_srv(port: int) -> Generator[HTTPRecords, None, None]: class ServerThread(threading.Thread): - def __init__(self, port: int, records: HTTPRecords) -> None: + def __init__(self, host: str, port: int, records: HTTPRecords) -> None: threading.Thread.__init__(self) self.port = port def http_request_handler(*args, **kwargs): HttpbgHTTPRequestHandler(records, *args, **kwargs) - self.srv = HTTPServer(("localhost", port), http_request_handler) + self.srv = HTTPServer((host, port), http_request_handler) def run(self): self.srv.serve_forever() diff --git a/pytest.ini b/pytest.ini index 7c04fc4..9b1de72 100644 --- a/pytest.ini +++ b/pytest.ini @@ -13,6 +13,7 @@ markers = urllib3: the hook for the "urllib3" package initiator: all tests related to the initiator ui: the web UI + server: the web server filterwarnings = ignore::DeprecationWarning:pytest_selenium.*: ignore::DeprecationWarning:: diff --git a/tests/test_api.py b/tests/test_api.py index 529b4f0..69982dd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -11,12 +11,12 @@ @pytest.mark.api -def test_api_requests_one_request(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_requests_one_request(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/get") - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -25,13 +25,13 @@ def test_api_requests_one_request(httpbin, httpdbg_port): @pytest.mark.api -def test_api_requests_two_requests(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_requests_two_requests(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/get?abc") requests.get(httpbin.url + "/get?def") - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -41,11 +41,11 @@ def test_api_requests_two_requests(httpbin, httpdbg_port): @pytest.mark.api -def test_api_requests_netloc(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_requests_netloc(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/get?abc") - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -56,8 +56,8 @@ def test_api_requests_netloc(httpbin, httpdbg_port): @pytest.mark.api -def test_api_request_by_id(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_request_by_id(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/get?abc") requests.get(httpbin.url + "/get?def") @@ -73,20 +73,20 @@ def test_api_request_by_id(httpbin, httpdbg_port): @pytest.mark.api -def test_api_request_by_id_not_exists(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_request_by_id_not_exists(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/get?abc") requests.get(httpbin.url + "/get?def") - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/request/999") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/request/999") assert ret.status_code == 404 @pytest.mark.api -def test_api_get_request_get(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_get_request_get(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/") requests.get(httpbin.url + "/get") @@ -95,7 +95,7 @@ def test_api_get_request_get(httpbin, httpdbg_port): path_to_content = ret.json()["response"]["body"]["path"] req_response_content = requests.get( - f"http://127.0.0.1:{httpdbg_port}{path_to_content}" + f"http://{httpdbg_host}:{httpdbg_port}{path_to_content}" ) # headers @@ -122,8 +122,8 @@ def test_api_get_request_get(httpbin, httpdbg_port): @pytest.mark.api -def test_api_get_request_post(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_get_request_post(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.get(httpbin.url + "/") requests.post(httpbin.url + "/post", data=b"data to post") @@ -132,12 +132,12 @@ def test_api_get_request_post(httpbin, httpdbg_port): path_to_content = ret.json()["request"]["body"]["path"] req_request_content = requests.get( - f"http://127.0.0.1:{httpdbg_port}{path_to_content}" + f"http://{httpdbg_host}:{httpdbg_port}{path_to_content}" ) path_to_content = ret.json()["response"]["body"]["path"] req_response_content = requests.get( - f"http://127.0.0.1:{httpdbg_port}{path_to_content}" + f"http://{httpdbg_host}:{httpdbg_port}{path_to_content}" ) # headers @@ -167,8 +167,8 @@ def test_api_get_request_post(httpbin, httpdbg_port): @pytest.mark.api -def test_api_get_request_get_status_404(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_get_request_get_status_404(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): ret = requests.get(httpbin.url + "/abc") assert ret.status_code == 404 @@ -181,10 +181,10 @@ def test_api_get_request_get_status_404(httpbin, httpdbg_port): @pytest.mark.api -def test_api_get_request_connection_error(httpbin, httpdbg_port): +def test_api_get_request_connection_error(httpbin, httpdbg_host, httpdbg_port): url_with_unknown_host = "http://f.q.d.1234.n.t.n.e/hello?a=b" - with httpdbg_srv(httpdbg_port) as records: + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): with pytest.raises(requests.exceptions.ConnectionError): requests.get(url_with_unknown_host) @@ -197,8 +197,8 @@ def test_api_get_request_connection_error(httpbin, httpdbg_port): @pytest.mark.api -def test_api_get_request_content_up_text(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_api_get_request_content_up_text(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): requests.post(httpbin.url + "/post", data={"a": 1, "b": 2}) requests.post(httpbin.url + "/post", data="hello") @@ -215,8 +215,8 @@ def test_api_get_request_content_up_text(httpbin, httpdbg_port): @pytest.mark.api @pytest.mark.cookies -def test_cookies_request(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_cookies_request(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): session = requests.session() session.cookies.set("COOKIE_NAME", "the-cookie-works") @@ -232,8 +232,8 @@ def test_cookies_request(httpbin, httpdbg_port): @pytest.mark.api @pytest.mark.cookies -def test_cookies_response(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_cookies_response(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): session = requests.session() session.get( diff --git a/tests/test_mode_console.py b/tests/test_mode_console.py index d46beeb..63dfb79 100644 --- a/tests/test_mode_console.py +++ b/tests/test_mode_console.py @@ -33,8 +33,8 @@ def test_run_console_from_pyhttpdbg_entry_point_default( assert "test_mode is on" in capsys.readouterr().out -def test_run_console(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_run_console(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): new_console = run_console(test_mode=True) new_console.push("import requests") @@ -42,7 +42,7 @@ def test_run_console(httpbin, httpdbg_port): with pytest.raises(SystemExit): new_console.push("exit()") - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] diff --git a/tests/test_mode_module_pytest.py b/tests/test_mode_module_pytest.py index eacd55e..a44fe1e 100644 --- a/tests/test_mode_module_pytest.py +++ b/tests/test_mode_module_pytest.py @@ -11,7 +11,7 @@ @pytest.mark.pytest -def test_run_pytest_from_pyhttpdbg_entry_point(httpbin, httpdbg_port, monkeypatch): +def test_run_pytest_from_pyhttpdbg_entry_point(httpbin, monkeypatch): os.environ["HTTPDBG_TEST_PYTEST_BASE_URL"] = httpbin.url script_to_run = os.path.join( os.path.dirname(os.path.realpath(__file__)), "demo_run_pytest.py" @@ -29,8 +29,8 @@ def test_run_pytest_from_pyhttpdbg_entry_point(httpbin, httpdbg_port, monkeypatc @pytest.mark.pytest -def test_run_pytest(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_run_pytest(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): os.environ["HTTPDBG_TEST_PYTEST_BASE_URL"] = httpbin.url script_to_run = os.path.join( @@ -38,7 +38,7 @@ def test_run_pytest(httpbin, httpdbg_port): ) run_module(["pytest", f"{script_to_run}::test_demo_pytest"]) - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -49,15 +49,15 @@ def test_run_pytest(httpbin, httpdbg_port): @pytest.mark.pytest -def test_run_pytest_with_exception(httpdbg_port, capsys): - with httpdbg_srv(httpdbg_port) as records: +def test_run_pytest_with_exception(httpdbg_host, httpdbg_port, capsys): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): script_to_run = os.path.join( os.path.dirname(os.path.realpath(__file__)), "demo_run_pytest.py" ) run_module(["pytest", f"{script_to_run}::test_demo_raise_exception"]) - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -68,8 +68,8 @@ def test_run_pytest_with_exception(httpdbg_port, capsys): @pytest.mark.api @pytest.mark.pytest -def test_run_pytest_initiator(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_run_pytest_initiator(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): os.environ["HTTPDBG_TEST_PYTEST_BASE_URL"] = httpbin.url script_to_run = os.path.join( @@ -77,7 +77,7 @@ def test_run_pytest_initiator(httpbin, httpdbg_port): ) run_module(["pytest", f"{script_to_run}::test_demo_pytest"]) - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] diff --git a/tests/test_mode_script.py b/tests/test_mode_script.py index aba429e..7d8e3fd 100644 --- a/tests/test_mode_script.py +++ b/tests/test_mode_script.py @@ -23,15 +23,15 @@ def test_run_script_from_pyhttpdbg_entry_point(httpbin, monkeypatch): @pytest.mark.script -def test_run_script(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_run_script(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): script_to_run = os.path.join( os.path.dirname(os.path.realpath(__file__)), "demo_run_script.py" ) run_script([script_to_run, httpbin.url]) - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -41,15 +41,15 @@ def test_run_script(httpbin, httpdbg_port): @pytest.mark.script -def test_run_script_with_exception(httpbin, httpdbg_port, capsys): - with httpdbg_srv(httpdbg_port) as records: +def test_run_script_with_exception(httpbin, httpdbg_host, httpdbg_port, capsys): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): script_to_run = os.path.join( os.path.dirname(os.path.realpath(__file__)), "demo_run_script.py" ) run_script([script_to_run, httpbin.url, "raise_exception"]) - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] @@ -73,15 +73,15 @@ def test_run_script_not_a_python_script(httpbin, capsys): @pytest.mark.api @pytest.mark.script -def test_run_script_initiator(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def test_run_script_initiator(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): script_to_run = os.path.join( os.path.dirname(os.path.realpath(__file__)), "demo_run_script.py" ) run_script([script_to_run, httpbin.url]) - ret = requests.get(f"http://127.0.0.1:{httpdbg_port}/requests") + ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests") reqs = ret.json()["requests"] diff --git a/tests/test_server.py b/tests/test_server.py new file mode 100644 index 0000000..7429576 --- /dev/null +++ b/tests/test_server.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +import requests + +import pytest + +from httpdbg.exception import HttpdbgException +from httpdbg.hooks.all import httprecord +from httpdbg.server import httpdbg_srv + + +@pytest.mark.server +@pytest.mark.parametrize("host", ["localhost", "0.0.0.0"]) +def test_server_host(httpbin, host, httpdbg_port): + with httpdbg_srv(host, httpdbg_port) as records: + with httprecord(records): + requests.get(httpbin.url + "/get") + + ret = requests.get(f"http://localhost:{httpdbg_port}/requests") + + reqs = ret.json()["requests"] + + assert len(reqs) == 1 + assert reqs[list(reqs.keys())[0]]["url"] == httpbin.url + "/get" + + +@pytest.mark.server +def test_server_host_exception_host(httpdbg_port): + host = "1.2.3.4" + with pytest.raises(HttpdbgException): + with httpdbg_srv(host, httpdbg_port): + pass + + +@pytest.mark.server +def test_server_host_exception_port(): + host = "127.0.0.1" + port = 123456789 + with pytest.raises(HttpdbgException): + with httpdbg_srv(host, port): + pass diff --git a/tests/ui/test_web.py b/tests/ui/test_web.py index 8ee2582..aafab6f 100644 --- a/tests/ui/test_web.py +++ b/tests/ui/test_web.py @@ -10,8 +10,8 @@ @pytest.fixture() -def records(httpbin, httpdbg_port): - with httpdbg_srv(httpdbg_port) as records: +def records(httpbin, httpdbg_host, httpdbg_port): + with httpdbg_srv(httpdbg_host, httpdbg_port) as records: with httprecord(records): for n in range(2, random.randint(5, 8)): requests.get(httpbin.url + f"/get?n={n}")