Skip to content

fix: API Endpoint Backend C_Migration#207

Open
Ashwal-Microsoft wants to merge 2 commits intodevfrom
fix--C-Migration-API-Fix
Open

fix: API Endpoint Backend C_Migration#207
Ashwal-Microsoft wants to merge 2 commits intodevfrom
fix--C-Migration-API-Fix

Conversation

@Ashwal-Microsoft
Copy link
Copy Markdown

@Ashwal-Microsoft Ashwal-Microsoft commented Apr 27, 2026

This pull request introduces a production-ready networking configuration for containerized applications, focusing on improved security by restricting backend API access to internal networks and implementing a frontend proxy pattern. The main changes include updating the infrastructure-as-code to support private networking, adjusting environment variables, and introducing an API proxy in the frontend server to route requests securely.

Infrastructure and Networking Changes:

  • The backend API container app's ingress is now internal-only when private networking is enabled, ensuring it is not publicly accessible. Only the frontend container app remains publicly reachable through the Application Gateway/WAF. [1] [2]
  • The output for the backend service URI now conditionally uses the internal domain if private networking is enabled, otherwise it uses the public FQDN.
  • Documentation was updated to clarify that only the frontend is publicly accessible in production WAF configuration, with backend and processor apps remaining internal.

Frontend Environment and API Proxy:

  • The frontend's environment variables are updated: API_URL is set to /api for same-origin calls, and a new BACKEND_API_URL variable is introduced to handle both internal and public backend URIs based on the networking configuration. [1] [2]
  • The frontend server (frontend_server.py) now includes an API proxy route (/api/{full_path:path}), forwarding API requests to the backend API using the BACKEND_API_URL environment variable. This ensures all API traffic passes through the frontend, supporting both internal and external networking scenarios. [1] [2]
  • Added httpx as a dependency to support asynchronous HTTP proxying in the frontend server.## Purpose
  • ...

Does this introduce a breaking change?

  • Yes
  • No

Golden Path Validation

  • I have tested the primary workflows (the "golden path") to ensure they function correctly without errors.

Deployment Validation

  • I have validated the deployment process successfully and all services are running as expected with this change.

What to Check

Verify that the following are valid

  • ...

Other Information

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a private-networking-friendly deployment pattern where the backend API Container App can be internal-only (when enablePrivateNetworking is enabled) and the frontend proxies API calls via same-origin /api, aligning backend exposure with an App Gateway/WAF front-door model.

Changes:

  • Add an /api/{full_path:path} proxy route to the FastAPI frontend server (using BACKEND_API_URL) and add httpx dependency.
  • Update Bicep to (a) make backend ingress internal-only when private networking is enabled and (b) set frontend env vars to use same-origin /api + a conditional BACKEND_API_URL.
  • Update deployment documentation to clarify public vs internal reachability in the production WAF configuration.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/frontend/requirements.txt Adds httpx dependency for the new proxy behavior.
src/frontend/frontend_server.py Implements the /api/* proxy endpoint and reads BACKEND_API_URL.
infra/main.bicep Makes backend ingress conditional on private networking; sets frontend API_URL and BACKEND_API_URL.
infra/main_custom.bicep Mirrors the backend ingress + frontend env var changes; updates backend URI output to be conditional.
docs/DeploymentGuide.md Documents that only the frontend remains publicly reachable in WAF/private networking mode.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread infra/main_custom.bicep
Comment thread src/frontend/frontend_server.py Outdated
Comment on lines +93 to +99
async with httpx.AsyncClient(timeout=120.0, follow_redirects=True) as client:
proxied = await client.request(
method=request.method,
url=target_url,
headers=headers,
content=body,
)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proxy_api doesn't handle httpx transport errors (timeouts, DNS failures, connection errors). Those exceptions will bubble up as 500s from the frontend rather than returning a clear 502/504 to callers; add try/except httpx.RequestError (and potentially httpx.TimeoutException) and map to an appropriate gateway error response.

Suggested change
async with httpx.AsyncClient(timeout=120.0, follow_redirects=True) as client:
proxied = await client.request(
method=request.method,
url=target_url,
headers=headers,
content=body,
)
try:
async with httpx.AsyncClient(timeout=120.0, follow_redirects=True) as client:
proxied = await client.request(
method=request.method,
url=target_url,
headers=headers,
content=body,
)
except httpx.TimeoutException:
return JSONResponse(
status_code=504,
content={"detail": "Upstream backend request timed out"},
)
except httpx.RequestError:
return JSONResponse(
status_code=502,
content={"detail": "Failed to reach upstream backend"},
)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread src/frontend/frontend_server.py Outdated
Comment on lines +101 to +114
passthrough_headers = {
key: value
for key, value in proxied.headers.items()
if key.lower() not in {"content-encoding", "transfer-encoding", "connection"}
}

return Response(
content=proxied.content,
status_code=proxied.status_code,
headers=passthrough_headers,
media_type=proxied.headers.get("content-type"),
)


Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proxy copies upstream response headers almost verbatim. This should drop content-length (it can become incorrect if httpx decompresses the body and you also remove content-encoding) and should preserve multi-value headers like set-cookie (a dict-comprehension over .items() can collapse duplicates), otherwise clients may see truncated responses or broken cookies.

Suggested change
passthrough_headers = {
key: value
for key, value in proxied.headers.items()
if key.lower() not in {"content-encoding", "transfer-encoding", "connection"}
}
return Response(
content=proxied.content,
status_code=proxied.status_code,
headers=passthrough_headers,
media_type=proxied.headers.get("content-type"),
)
excluded_headers = {
"content-encoding",
"transfer-encoding",
"connection",
"content-length",
"content-type",
}
response = Response(
content=proxied.content,
status_code=proxied.status_code,
media_type=proxied.headers.get("content-type"),
)
for key, value in proxied.headers.multi_items():
if key.lower() not in excluded_headers:
response.raw_headers.append((key.encode("latin-1"), value.encode("latin-1")))
return response

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread src/frontend/frontend_server.py Outdated
Comment on lines +93 to +99
async with httpx.AsyncClient(timeout=120.0, follow_redirects=True) as client:
proxied = await client.request(
method=request.method,
url=target_url,
headers=headers,
content=body,
)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new httpx.AsyncClient is created per request and follow_redirects=True changes redirect semantics (clients will never see 3xx responses). For better performance and more predictable proxy behavior, reuse a single AsyncClient (startup/shutdown lifespan) and consider leaving redirects to the caller (or only following redirects within the backend origin).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread infra/main.bicep
- Add try/except for httpx.TimeoutException (504) and httpx.RequestError (502)
- Drop content-length from passthrough headers to avoid truncated responses
- Use multi_items() to preserve multi-value headers like set-cookie

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants