88import threading
99import time
1010import urllib .parse
11+ import urllib .request
1112from collections import defaultdict
1213from collections .abc import Callable
1314from collections .abc import Generator
1819from contextlib import suppress
1920from copy import copy
2021from enum import Enum
22+ from http import HTTPStatus
2123from re import Pattern
2224from typing import TYPE_CHECKING
2325from typing import Any
@@ -938,6 +940,9 @@ class HTTPServer(HTTPServerBase): # pylint: disable=too-many-instance-attribute
938940
939941 :param threaded: whether to handle concurrent requests in separate threads
940942
943+ :param startup_timeout: maximum time in seconds to wait for server readiness.
944+ By default, no readiness check is performed.
945+
941946 .. py:attribute:: no_handler_status_code
942947
943948 Attribute containing the http status code (int) which will be the response
@@ -956,6 +961,7 @@ def __init__(
956961 default_waiting_settings : WaitingSettings | None = None ,
957962 * ,
958963 threaded : bool = False ,
964+ startup_timeout : float | None = None ,
959965 ) -> None :
960966 """
961967 Initializes the instance.
@@ -972,6 +978,29 @@ def __init__(
972978 self .default_waiting_settings = WaitingSettings ()
973979 self ._waiting_settings = copy (self .default_waiting_settings )
974980 self ._waiting_result : queue .LifoQueue [bool ] = queue .LifoQueue (maxsize = 1 )
981+ self .startup_timeout = startup_timeout
982+ self ._readiness_check_pending = False
983+
984+ def start (self ) -> None :
985+ self ._readiness_check_pending = self .startup_timeout is not None
986+
987+ super ().start ()
988+ self .wait_for_server_ready ()
989+
990+ def wait_for_server_ready (self ) -> None :
991+ """
992+ Waits until the server is ready to serve requests.
993+ """
994+ if not self ._readiness_check_pending :
995+ return
996+
997+ url = self .url_for ("/" )
998+ if not url .startswith (("http://" , "https://" )):
999+ raise ValueError (f"Invalid URL generated for readiness check : { url } " ) # noqa: EM102
1000+
1001+ with urllib .request .urlopen (url , timeout = self .startup_timeout ) as resp : # noqa: S310
1002+ if resp .status != HTTPStatus .OK .value or resp .read () != b"OK" :
1003+ raise HTTPServerError ("Readiness check failed with status code: {}" .format (resp .status ))
9751004
9761005 def clear (self ) -> None :
9771006 """
@@ -1272,6 +1301,10 @@ def dispatch(self, request: Request) -> Response:
12721301 :param request: the request object from the werkzeug library
12731302 :return: the response object what the handler responded, or a response which contains the error
12741303 """
1304+ if self ._readiness_check_pending :
1305+ self ._readiness_check_pending = False
1306+
1307+ return Response (HTTPStatus .OK .phrase , status = HTTPStatus .OK .value )
12751308
12761309 if self .permanently_failed :
12771310 return self .respond_permanent_failure ()
0 commit comments