Proxy backend requests on '/' to the frontend
If the optional extra `proxy` is installed, then the backend can handle all requests by proxy unrecognized routes to the frontend nextjs server.
This commit is contained in:
parent
1e9ccecea0
commit
7512afa949
@ -63,6 +63,7 @@ setuptools = ">=69.1.1,<70.0"
|
|||||||
httpx = ">=0.25.1,<1.0"
|
httpx = ">=0.25.1,<1.0"
|
||||||
twine = ">=4.0.0,<6.0"
|
twine = ">=4.0.0,<6.0"
|
||||||
tomlkit = ">=0.12.4,<1.0"
|
tomlkit = ">=0.12.4,<1.0"
|
||||||
|
asgiproxy = { version = "==0.1.1", optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = ">=7.1.2,<8.0"
|
pytest = ">=7.1.2,<8.0"
|
||||||
@ -90,6 +91,9 @@ pytest-benchmark = ">=4.0.0,<5.0"
|
|||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
reflex = "reflex.reflex:cli"
|
reflex = "reflex.reflex:cli"
|
||||||
|
|
||||||
|
[tool.poetry.extras]
|
||||||
|
proxy = ["asgiproxy"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.5.1"]
|
requires = ["poetry-core>=1.5.1"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
@ -13,6 +13,7 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
AsyncGenerator,
|
||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
Callable,
|
Callable,
|
||||||
Coroutine,
|
Coroutine,
|
||||||
@ -94,6 +95,23 @@ def default_overlay_component() -> Component:
|
|||||||
return Fragment.create(connection_pulser(), connection_modal())
|
return Fragment.create(connection_pulser(), connection_modal())
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.asynccontextmanager
|
||||||
|
async def lifespan(api: FastAPI) -> AsyncGenerator[None, None]:
|
||||||
|
"""Context manager to handle the lifespan of the app.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api: The FastAPI instance.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
# try to set up proxying if its enabled
|
||||||
|
from .proxy import proxy_middleware
|
||||||
|
|
||||||
|
async with proxy_middleware(api):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
class OverlayFragment(Fragment):
|
class OverlayFragment(Fragment):
|
||||||
"""Alias for Fragment, used to wrap the overlay_component."""
|
"""Alias for Fragment, used to wrap the overlay_component."""
|
||||||
|
|
||||||
@ -203,7 +221,7 @@ class App(Base):
|
|||||||
self.middleware.append(HydrateMiddleware())
|
self.middleware.append(HydrateMiddleware())
|
||||||
|
|
||||||
# Set up the API.
|
# Set up the API.
|
||||||
self.api = FastAPI()
|
self.api = FastAPI(lifespan=lifespan)
|
||||||
self._add_cors()
|
self._add_cors()
|
||||||
self._add_default_endpoints()
|
self._add_default_endpoints()
|
||||||
|
|
||||||
|
75
reflex/proxy.py
Normal file
75
reflex/proxy.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""Handle proxying frontend requests from the backend server."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import AsyncGenerator
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from starlette.types import ASGIApp
|
||||||
|
|
||||||
|
from .config import get_config
|
||||||
|
from .utils import console
|
||||||
|
|
||||||
|
try:
|
||||||
|
from asgiproxy.config import BaseURLProxyConfigMixin, ProxyConfig
|
||||||
|
from asgiproxy.context import ProxyContext
|
||||||
|
from asgiproxy.simple_proxy import make_simple_proxy_app
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def proxy_middleware(*args, **kwargs) -> AsyncGenerator[None, None]:
|
||||||
|
"""A no-op proxy middleware for when asgiproxy is not installed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*args: The positional arguments.
|
||||||
|
**kwargs: The keyword arguments.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
|
||||||
|
def _get_proxy_app_with_context(frontend_host: str) -> tuple[ProxyContext, ASGIApp]:
|
||||||
|
"""Get the proxy app with the given frontend host.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frontend_host: The frontend host to proxy requests to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The proxy context and app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class LocalProxyConfig(BaseURLProxyConfigMixin, ProxyConfig):
|
||||||
|
upstream_base_url = frontend_host
|
||||||
|
rewrite_host_header = urlparse(upstream_base_url).netloc
|
||||||
|
|
||||||
|
proxy_context = ProxyContext(LocalProxyConfig())
|
||||||
|
proxy_app = make_simple_proxy_app(proxy_context)
|
||||||
|
return proxy_context, proxy_app
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def proxy_middleware( # pyright: ignore[reportGeneralTypeIssues]
|
||||||
|
api: FastAPI,
|
||||||
|
) -> AsyncGenerator[None, None]:
|
||||||
|
"""A middleware to proxy requests to the separate frontend server.
|
||||||
|
|
||||||
|
The proxy is installed on the / endpoint of the FastAPI instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api: The FastAPI instance.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
config = get_config()
|
||||||
|
backend_port = config.backend_port
|
||||||
|
frontend_host = f"http://localhost:{config.frontend_port}"
|
||||||
|
proxy_context, proxy_app = _get_proxy_app_with_context(frontend_host)
|
||||||
|
api.mount("/", proxy_app)
|
||||||
|
console.debug(
|
||||||
|
f"Proxying '/' requests on port {backend_port} to {frontend_host}"
|
||||||
|
)
|
||||||
|
async with proxy_context:
|
||||||
|
yield
|
Loading…
Reference in New Issue
Block a user