Skip to content

Commit 7aea597

Browse files
committed
Implement http readiness checking
Send a HTTP request to the server if user specified readiness timeout.
1 parent 334b87f commit 7aea597

3 files changed

Lines changed: 59 additions & 0 deletions

File tree

pytest_httpserver/httpserver.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import threading
99
import time
1010
import urllib.parse
11+
import urllib.request
1112
from collections import defaultdict
1213
from collections.abc import Callable
1314
from collections.abc import Generator
@@ -938,6 +939,9 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
938939
939940
:param threaded: whether to handle concurrent requests in separate threads
940941
942+
:param startup_timeout: maximum time in seconds to wait for server readiness.
943+
By default, no readiness check is performed.
944+
941945
.. py:attribute:: no_handler_status_code
942946
943947
Attribute containing the http status code (int) which will be the response
@@ -956,6 +960,7 @@ def __init__(
956960
default_waiting_settings: WaitingSettings | None = None,
957961
*,
958962
threaded: bool = False,
963+
startup_timeout: float | None = None,
959964
) -> None:
960965
"""
961966
Initializes the instance.
@@ -972,6 +977,32 @@ def __init__(
972977
self.default_waiting_settings = WaitingSettings()
973978
self._waiting_settings = copy(self.default_waiting_settings)
974979
self._waiting_result: queue.LifoQueue[bool] = queue.LifoQueue(maxsize=1)
980+
self.startup_timeout = startup_timeout
981+
self._readiness_check_pending = False
982+
983+
def start(self) -> None:
984+
if self.startup_timeout is None:
985+
self._readiness_check_pending = False
986+
else:
987+
self._readiness_check_pending = True
988+
989+
super().start()
990+
self.wait_for_server_ready()
991+
992+
def wait_for_server_ready(self) -> None:
993+
"""
994+
Waits until the server is ready to serve requests.
995+
"""
996+
if not self._readiness_check_pending:
997+
return
998+
999+
url = self.url_for("/")
1000+
if not url.startswith(("http://", "https://")):
1001+
raise ValueError(f"Invalid URL generated for readiness check check: {url}") # noqa: EM102
1002+
1003+
with urllib.request.urlopen(url, timeout=self.startup_timeout) as resp: # noqa: S310
1004+
if resp.status != 200 or resp.read() != b"OK":
1005+
raise HTTPServerError("Readiness check failed with status code: {}".format(resp.status))
9751006

9761007
def clear(self) -> None:
9771008
"""
@@ -1272,6 +1303,9 @@ def dispatch(self, request: Request) -> Response:
12721303
:param request: the request object from the werkzeug library
12731304
:return: the response object what the handler responded, or a response which contains the error
12741305
"""
1306+
if self._readiness_check_pending:
1307+
self._readiness_check_pending = False
1308+
return Response("OK", status=200)
12751309

12761310
if self.permanently_failed:
12771311
return self.respond_permanent_failure()

tests/test_readiness.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from collections.abc import Generator
2+
3+
import pytest
4+
import requests
5+
6+
from pytest_httpserver.httpserver import HTTPServer
7+
8+
9+
@pytest.fixture
10+
def httpserver() -> Generator[HTTPServer, None, None]:
11+
server = HTTPServer(startup_timeout=10)
12+
server.start()
13+
yield server
14+
server.clear()
15+
if server.is_running():
16+
server.stop()
17+
18+
19+
def test_httpserver_readiness(httpserver: HTTPServer):
20+
assert httpserver.startup_timeout == 10
21+
httpserver.expect_request("/").respond_with_data("Hello, world!")
22+
resp = requests.get(httpserver.url_for("/"))
23+
assert resp.status_code == 200
24+
assert resp.text == "Hello, world!"

tests/test_release.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ def test_sdist_contents(build: Build, version: str):
231231
"test_port_changing.py",
232232
"test_querymatcher.py",
233233
"test_querystring.py",
234+
"test_readiness.py",
234235
"test_release.py",
235236
"test_ssl.py",
236237
"test_thread_type.py",

0 commit comments

Comments
 (0)