66
77from app .utils .config import CADDY_BASE_URL , LOAD_ENDPOINT , PROXY_PORT , get_active_config , get_yaml_value
88from app .utils .protocols import LoggerProtocol
9+ from app .utils .retry import retry_with_backoff , wait_for_condition
910
1011from .messages import (
1112 caddy_connection_failed ,
@@ -31,10 +32,45 @@ def _get_caddy_url(port: int, endpoint: str) -> str:
3132 return f"{ caddy_base_url .format (port = port )} { endpoint } "
3233
3334
35+ def _check_caddy_ready (proxy_port : int ) -> bool :
36+ """Check if Caddy is ready to accept connections."""
37+ url = _get_caddy_url (proxy_port , "/config/" )
38+ try :
39+ response = requests .get (url , timeout = 5 )
40+ # 200 = config exists, 404 = no config yet but Caddy is responding
41+ return response .status_code in (200 , 404 )
42+ except (requests .exceptions .ConnectionError , requests .exceptions .RequestException ):
43+ return False
44+
45+
46+ def _wait_for_caddy (
47+ proxy_port : int ,
48+ logger : Optional [LoggerProtocol ] = None ,
49+ ) -> tuple [bool , Optional [str ]]:
50+ """Wait for Caddy to be ready to accept connections with exponential backoff."""
51+
52+ def on_retry (attempt : int , delay : float ) -> None :
53+ if logger :
54+ logger .debug (f"Waiting for Caddy to be ready (attempt { attempt } , retrying in { delay :.1f} s)" )
55+
56+ success , error = wait_for_condition (
57+ check_func = lambda : _check_caddy_ready (proxy_port ),
58+ on_retry = on_retry ,
59+ timeout_message = "Caddy not ready" ,
60+ )
61+
62+ if success and logger :
63+ logger .debug ("Caddy is ready" )
64+
65+ return success , error
66+
67+
3468def load_config (
35- config_file : str , proxy_port : int = default_proxy_port , logger : Optional [LoggerProtocol ] = None
69+ config_file : str ,
70+ proxy_port : int = default_proxy_port ,
71+ logger : Optional [LoggerProtocol ] = None ,
3672) -> tuple [bool , Optional [str ]]:
37- """Load Caddy proxy configuration from a JSON file."""
73+ """Load Caddy proxy configuration from a JSON file with retry logic ."""
3874 if not config_file :
3975 return False , "Configuration file is required"
4076
@@ -47,29 +83,13 @@ def load_config(
4783 try :
4884 if logger :
4985 logger .debug (debug_loading_config_file .format (file = config_file ))
50-
86+
5187 with open (config_file , "r" ) as f :
5288 config_data = json .load (f )
53-
54- if logger :
55- logger .debug (debug_config_parsed )
5689
57- url = _get_caddy_url (proxy_port , caddy_load_endpoint )
5890 if logger :
59- logger .debug (debug_posting_config . format ( url = url ) )
91+ logger .debug (debug_config_parsed )
6092
61- response = requests .post (url , json = config_data , headers = {"Content-Type" : "application/json" }, timeout = 10 )
62-
63- if response .status_code == 200 :
64- if logger :
65- logger .debug (debug_config_loaded_success )
66- return True , None
67- else :
68- error_msg = response .text .strip () if response .text else http_error .format (code = response .status_code )
69- if logger :
70- logger .debug (f"Failed to load config: { error_msg } " )
71- return False , error_msg
72-
7393 except FileNotFoundError :
7494 error_msg = config_file_not_found .format (file = config_file )
7595 if logger :
@@ -80,19 +100,45 @@ def load_config(
80100 if logger :
81101 logger .debug (error_msg )
82102 return False , error_msg
83- except requests .exceptions .ConnectionError as e :
84- error_msg = caddy_connection_failed .format (error = str (e ))
85- if logger :
86- logger .debug (error_msg )
87- return False , error_msg
88- except requests .exceptions .RequestException as e :
89- error_msg = request_failed_error .format (error = str (e ))
90- if logger :
91- logger .debug (error_msg )
92- return False , error_msg
93- except Exception as e :
94- error_msg = unexpected_error .format (error = str (e ))
103+
104+ # Wait for Caddy to be ready before attempting to load config
105+ if logger :
106+ logger .debug ("Waiting for Caddy to be ready..." )
107+ ready , ready_error = _wait_for_caddy (proxy_port , logger )
108+ if not ready :
109+ return False , ready_error
110+
111+ url = _get_caddy_url (proxy_port , caddy_load_endpoint )
112+
113+ def post_config () -> tuple [bool , Optional [str ]]:
114+ """Attempt to post config to Caddy."""
115+ try :
116+ if logger :
117+ logger .debug (debug_posting_config .format (url = url ))
118+
119+ response = requests .post (url , json = config_data , headers = {"Content-Type" : "application/json" }, timeout = 10 )
120+
121+ if response .status_code == 200 :
122+ return True , None
123+ else :
124+ error_msg = response .text .strip () if response .text else http_error .format (code = response .status_code )
125+ return False , error_msg
126+
127+ except requests .exceptions .ConnectionError as e :
128+ return False , caddy_connection_failed .format (error = str (e ))
129+ except requests .exceptions .RequestException as e :
130+ return False , request_failed_error .format (error = str (e ))
131+ except Exception as e :
132+ return False , unexpected_error .format (error = str (e ))
133+
134+ def on_retry (attempt : int , delay : float , last_error : Optional [str ]) -> None :
95135 if logger :
96- logger .debug (error_msg )
97- return False , error_msg
136+ logger .debug (f"Failed to load config (attempt { attempt } ): { last_error } " )
137+ logger .debug (f"Retrying in { delay :.1f} s..." )
138+
139+ success , error = retry_with_backoff (func = post_config , on_retry = on_retry )
140+
141+ if success and logger :
142+ logger .debug (debug_config_loaded_success )
98143
144+ return success , error
0 commit comments