Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions scripts/post_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ def get_api_headers(config: ResourceConfig) -> Dict[str, str]:
return headers


async def check_admin_api_health(config: ResourceConfig, max_retries: int = 5, retry_delay: int = 10) -> bool:
"""Check if the admin API is available with retry logic for cold starts."""
async def check_admin_api_health(config: ResourceConfig, max_retries: int = 8, retry_delay: int = 20) -> bool:
"""Check if the admin API is available with retry logic for cold starts and container restarts."""
Comment thread
Ragini-Microsoft marked this conversation as resolved.
print_step("Checking admin API health...")

async with httpx.AsyncClient(timeout=30.0) as client:
Expand All @@ -256,8 +256,32 @@ async def check_admin_api_health(config: ResourceConfig, max_retries: int = 5, r
headers=get_api_headers(config)
)
if response.status_code == 200:
print_success("Admin API is healthy")
return True
# Validate the response is actually from the backend admin API,
# not the frontend catch-all serving index.html
try:
data = response.json()
if data.get("status") == "healthy":
print_success("Admin API is healthy")
return True
else:
print_warning(
f"Attempt {attempt}/{max_retries}: Admin API returned unexpected JSON: {data}"
Comment thread
Ragini-Microsoft marked this conversation as resolved.
)
except (ValueError, json.JSONDecodeError):
# Response is not JSON.
# This means the API proxy is not forwarding requests to the backend.
content_preview = response.text[:200]
is_html = "<html" in content_preview.lower() or "<!doctype" in content_preview.lower()
Comment thread
Ragini-Microsoft marked this conversation as resolved.
if is_html:
print_warning(
f"Attempt {attempt}/{max_retries}: Got HTML instead of JSON from /api/admin/health. "
"The frontend proxy may not be forwarding requests to the backend. "
"Check that BACKEND_URL is set in the App Service and that the backend container is running."
)
else:
print_warning(
f"Attempt {attempt}/{max_retries}: Admin API returned non-JSON response: {content_preview}"
)
else:
print_warning(f"Attempt {attempt}/{max_retries}: Admin API returned {response.status_code}")
except Exception as e:
Expand Down
75 changes: 43 additions & 32 deletions src/App/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,51 @@ const httpAgent = new http.Agent({

// Proxy API requests to backend
if (BACKEND_URL) {
app.use('/api', createProxyMiddleware({
target: BACKEND_URL,
changeOrigin: true,
pathRewrite: {
'^/': '/api/'
},
agent: httpAgent,
// Increase timeout for long-running requests (10 minutes)
proxyTimeout: 600000,
timeout: 600000,
// Support streaming responses (SSE)
onProxyRes: (proxyRes, req, res) => {
// Disable buffering for streaming responses
if (proxyRes.headers['content-type']?.includes('text/event-stream')) {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
const apiProxy = createProxyMiddleware({
target: BACKEND_URL,
changeOrigin: true,
pathRewrite: {
'^/': '/api/'
},
agent: httpAgent,
// Increase timeout for long-running requests (10 minutes)
proxyTimeout: 600000,
timeout: 600000,
// Support streaming responses (SSE)
onProxyRes: (proxyRes, req, res) => {
// Disable buffering for streaming responses
if (proxyRes.headers['content-type']?.includes('text/event-stream')) {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
}
// Log response for debugging
console.log(`Proxy response: ${req.method} ${req.path} -> ${proxyRes.statusCode}`);
},
onProxyReq: (proxyReq, req, res) => {
// Log request for debugging
console.log(`Proxy request: ${req.method} ${req.path}`);
},
onError: (err, req, res) => {
console.error('Proxy error:', err.message);
if (!res.headersSent) {
res.status(502).json({ error: 'Backend service unavailable', details: err.message });
}
}
// Log response for debugging
console.log(`Proxy response: ${req.method} ${req.path} -> ${proxyRes.statusCode}`);
},
onProxyReq: (proxyReq, req, res) => {
// Log request for debugging
console.log(`Proxy request: ${req.method} ${req.path}`);
},
onError: (err, req, res) => {
console.error('Proxy error:', err.message);
if (!res.headersSent) {
res.status(502).json({ error: 'Backend service unavailable', details: err.message });
}
}
}));
});
app.use('/api', apiProxy);
} else {
// When BACKEND_URL is not set, return a clear error for API requests
// instead of letting them fall through to the SPA catch-all (which returns index.html with 200)
app.use('/api', (req, res) => {
res.status(503).json({
error: 'Backend not configured',
message: 'BACKEND_URL environment variable is not set. The API proxy is not available.',
});
});
Comment thread
Ragini-Microsoft marked this conversation as resolved.
}

// Serve static files from the build directory
app.use(express.static(path.join(__dirname, 'static')));

Expand Down
Loading