diff --git a/python/ray/serve/_private/http_util.py b/python/ray/serve/_private/http_util.py index 02bf6d62c1f6..f8bac5956a65 100644 --- a/python/ray/serve/_private/http_util.py +++ b/python/ray/serve/_private/http_util.py @@ -685,48 +685,6 @@ def _apply_middlewares(app: ASGIApp, middlewares: List[Callable]) -> ASGIApp: return app -def _inject_root_path(app: ASGIApp, root_path: str): - """Middleware to inject root_path to the ASGI app.""" - if not root_path: - return app - - async def scope_root_path_middleware(scope, receive, send): - if scope["type"] in ("http", "websocket"): - scope["root_path"] = root_path - await app(scope, receive, send) - - return scope_root_path_middleware - - -def _apply_root_path(app: ASGIApp, root_path: str): - """Handle root_path parameter across different uvicorn versions. - - For uvicorn >= 0.26.0, root_path must be injected into the ASGI scope - rather than passed to uvicorn.Config, as uvicorn changed its behavior - in version 0.26.0. - - Reference: https://uvicorn.dev/release-notes/#0260-january-16-2024 - - Args: - app: The ASGI application - root_path: The root path prefix for all routes - - Returns: - Tuple of (app, root_path) where: - - app may be wrapped with middleware (for uvicorn >= 0.26.0) - - root_path is "" for uvicorn >= 0.26.0, unchanged otherwise - """ - if not root_path: - return app, root_path - - uvicorn_version = version.parse(uvicorn.__version__) - if uvicorn_version < version.parse("0.26.0"): - return app, root_path - else: - app = _inject_root_path(app, root_path) - return app, "" - - async def start_asgi_http_server( app: ASGIApp, http_options: HTTPOptions, @@ -739,7 +697,6 @@ async def start_asgi_http_server( Returns a task that blocks until the server exits (e.g., due to error). """ app = _apply_middlewares(app, http_options.middlewares) - app, root_path = _apply_root_path(app, http_options.root_path) sock = socket.socket( socket.AF_INET6 if is_ipv6(http_options.host) else socket.AF_INET, @@ -786,7 +743,7 @@ async def start_asgi_http_server( factory=True, host=http_options.host, port=http_options.port, - root_path=root_path, + root_path=http_options.root_path, timeout_keep_alive=http_options.keep_alive_timeout_s, loop=event_loop, lifespan="off", diff --git a/python/ray/serve/tests/test_fastapi.py b/python/ray/serve/tests/test_fastapi.py index 09bac3996568..43b2697bff3c 100644 --- a/python/ray/serve/tests/test_fastapi.py +++ b/python/ray/serve/tests/test_fastapi.py @@ -29,7 +29,6 @@ from ray.exceptions import GetTimeoutError from ray.serve._private.client import ServeControllerClient from ray.serve._private.constants import ( - RAY_SERVE_ENABLE_DIRECT_INGRESS, SERVE_DEFAULT_APP_NAME, ) from ray.serve._private.http_util import make_fastapi_class_based_view @@ -1227,107 +1226,6 @@ def direct_endpoint(self): assert resp.json() == {"level": "direct"} -@pytest.mark.parametrize( - "app_root_path,serve_root_path,expected_params_1,expected_params_2", - [ - ("", "", ["/hello", "", "/hello"], []), - ( - "/app_root_path", - "", - ["/hello", "/app_root_path", "/hello"], - ["/app_root_path/hello", "/app_root_path", "/app_root_path/hello"], - ), - ( - "", - "/serve_root_path", - ["/hello", "/serve_root_path", "/serve_root_path/hello"], - [], - ), - ("/app_root_path", "/serve_root_path", [], []), - ("/root_path", "/root_path", ["/hello", "/root_path", "/root_path/hello"], []), - ], -) -def test_root_path( - ray_shutdown, app_root_path, serve_root_path, expected_params_1, expected_params_2 -): - # serve_root_path is a proxy-dependent feature that doesn't apply to direct ingress - if RAY_SERVE_ENABLE_DIRECT_INGRESS: - pytest.skip( - "serve_root_path is handled by proxy, not applicable for direct ingress" - ) - - """ - The test works across uvicorn versions (before and after uvicorn 0.26.0 version which introduces breaking changes for the scope root_path). - - Reference: https://github.com/Kludex/uvicorn/pull/2213 - - | Case | `app_root_path` | `serve_root_path` | Expected Working URL #1 (suffix) | `root_path` (req.scope) | `path` (req.scope) | Expected Working URL #2 (suffix) | `root_path` #2 | `path` #2 | - | ---: | ---------------- | ------------------ | -------------------------------- | ----------------------- | ------------------------ | -------------------------------- | ---------------- | ---------------------- | - | 1 | `""` | `""` | `/hello` | `""` | `/hello` | — | — | — | - | 2 | `/app_root_path` | `""` | `/hello` | `/app_root_path` | `/hello` | `/app_root_path/hello` | `/app_root_path` | `/app_root_path/hello` | - | 3 | `""` | `/serve_root_path` | `/hello` | `/serve_root_path` | `/serve_root_path/hello` | — | — | — | - | 4 | `/app_root_path` | `/serve_root_path` | *(none)* | — | — | — | — | — | - | 5 | `/root_path` | `/root_path` | `/hello` | `/root_path` | `/root_path/hello` | — | — | — | - """ - app = FastAPI(root_path=app_root_path) - - @app.get("/hello") - def func(request: Request): - return { - "root_path": request.scope.get("root_path"), - "path": request.scope.get("path"), - } - - @serve.deployment - @serve.ingress(app) - class App: - pass - - serve.start(http_options={"root_path": serve_root_path}) - serve.run(App.bind()) - - base = get_application_url("HTTP") - test_urls = [ - f"{base}/hello", - f"{base}{app_root_path}/hello", - f"{base}{serve_root_path}/hello", - f"{base}{app_root_path}{serve_root_path}/hello", - f"{base}{serve_root_path}{app_root_path}/hello", - f"{base}{app_root_path}{app_root_path}/hello", - f"{base}{serve_root_path}{serve_root_path}/hello", - ] - - tested = set() - working_url_params = [] - for test_url in test_urls: - if test_url not in tested: - response = httpx.get(test_url) - if response.status_code == 200: - body = response.json() - params = {} - params["url"] = test_url - params["root_path"] = body["root_path"] - params["path"] = body["path"] - working_url_params.append(params) - tested.add(test_url) - if expected_params_1: - assert ( - len(working_url_params) > 0 - ), "working urls array is expected to have at least 1 item!" - assert working_url_params[0]["url"].endswith(expected_params_1[0]) - assert working_url_params[0]["root_path"] == expected_params_1[1] - assert working_url_params[0]["path"] == expected_params_1[2] - if expected_params_2: - assert ( - len(working_url_params) > 1 - ), "working urls array is expected to have at least 2 items!" - assert working_url_params[1]["url"].endswith(expected_params_2[0]) - assert working_url_params[1]["root_path"] == expected_params_2[1] - assert working_url_params[1]["path"] == expected_params_2[2] - else: - assert len(working_url_params) == 0 - - if __name__ == "__main__": import sys