mirror of
https://github.com/vale981/ray
synced 2025-03-06 10:31:39 -05:00
[serve] add root_path setting (#21090)
Support hosting a serve instance under a path prefix. Some clean-up should still be done for the different overlapping HttpOptions that now exist (host, port, root_path, root_url).
This commit is contained in:
parent
559eefd06f
commit
b0700e676b
6 changed files with 49 additions and 8 deletions
|
@ -432,6 +432,9 @@ def start(
|
|||
"127.0.0.1". To expose Serve publicly, you probably want to set
|
||||
this to "0.0.0.0".
|
||||
- port(int): Port for HTTP server. Defaults to 8000.
|
||||
- root_path(str): Root path to mount the serve application
|
||||
(for example, "/serve"). All deployment routes will be prefixed
|
||||
with this path. Defaults to "".
|
||||
- middlewares(list): A list of Starlette middlewares that will be
|
||||
applied to the HTTP servers in the cluster. Defaults to [].
|
||||
- location(str, serve.config.DeploymentMode): The deployment
|
||||
|
|
|
@ -257,6 +257,7 @@ class HTTPOptions(pydantic.BaseModel):
|
|||
location: Optional[DeploymentMode] = DeploymentMode.HeadOnly
|
||||
num_cpus: int = 0
|
||||
root_url: str = ""
|
||||
root_path: str = ""
|
||||
fixed_number_replicas: Optional[int] = None
|
||||
fixed_number_selection_seed: int = 0
|
||||
|
||||
|
|
|
@ -270,7 +270,8 @@ class ServeController:
|
|||
if SERVE_ROOT_URL_ENV_KEY in os.environ:
|
||||
return os.environ[SERVE_ROOT_URL_ENV_KEY]
|
||||
else:
|
||||
return f"http://{http_config.host}:{http_config.port}"
|
||||
return (f"http://{http_config.host}:{http_config.port}"
|
||||
f"{http_config.root_path}")
|
||||
return http_config.root_url
|
||||
|
||||
async def shutdown(self) -> List[GoalId]:
|
||||
|
@ -336,9 +337,11 @@ class ServeController:
|
|||
|
||||
goal_id, updating = self.deployment_state_manager.deploy(
|
||||
name, deployment_info)
|
||||
|
||||
if route_prefix is not None:
|
||||
endpoint_info = EndpointInfo(route=route_prefix)
|
||||
self.endpoint_state.update_endpoint(name, endpoint_info)
|
||||
|
||||
return goal_id, updating
|
||||
|
||||
def delete_deployment(self, name: str) -> Optional[GoalId]:
|
||||
|
|
|
@ -276,17 +276,21 @@ class HTTPProxy:
|
|||
"""
|
||||
|
||||
assert scope["type"] == "http"
|
||||
route_path = scope["path"]
|
||||
self.request_counter.inc(tags={"route": scope["path"]})
|
||||
|
||||
if scope["path"] == "/-/routes":
|
||||
# only use the non-root part of the path for routing
|
||||
root_path = scope["root_path"]
|
||||
route_path = scope["path"][len(root_path):]
|
||||
|
||||
self.request_counter.inc(tags={"route": route_path})
|
||||
|
||||
if route_path == "/-/routes":
|
||||
return await starlette.responses.JSONResponse(self.route_info)(
|
||||
scope, receive, send)
|
||||
|
||||
route_prefix, handle = self.prefix_router.match_route(scope["path"])
|
||||
route_prefix, handle = self.prefix_router.match_route(route_path)
|
||||
if route_prefix is None:
|
||||
self.request_error_counter.inc(tags={
|
||||
"route": scope["path"],
|
||||
"route": route_path,
|
||||
"error_code": "404"
|
||||
})
|
||||
return await self._not_found(scope, receive, send)
|
||||
|
@ -296,8 +300,8 @@ class HTTPProxy:
|
|||
# changed without restarting the replicas.
|
||||
if route_prefix != "/":
|
||||
assert not route_prefix.endswith("/")
|
||||
scope["path"] = scope["path"].replace(route_prefix, "", 1)
|
||||
scope["root_path"] = route_prefix
|
||||
scope["path"] = route_path.replace(route_prefix, "", 1)
|
||||
scope["root_path"] = root_path + route_prefix
|
||||
|
||||
status_code = await _send_request_to_handle(handle, scope, receive,
|
||||
send)
|
||||
|
@ -315,6 +319,7 @@ class HTTPProxyActor:
|
|||
def __init__(self,
|
||||
host: str,
|
||||
port: int,
|
||||
root_path: str,
|
||||
controller_name: str,
|
||||
controller_namespace: str,
|
||||
http_middlewares: Optional[List[
|
||||
|
@ -324,6 +329,7 @@ class HTTPProxyActor:
|
|||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.root_path = root_path
|
||||
|
||||
self.setup_complete = asyncio.Event()
|
||||
|
||||
|
@ -383,6 +389,7 @@ Please make sure your http-host and http-port are specified correctly.""")
|
|||
self.wrapped_app,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
root_path=self.root_path,
|
||||
lifespan="off",
|
||||
access_log=False)
|
||||
server = uvicorn.Server(config=config)
|
||||
|
|
|
@ -112,6 +112,7 @@ class HTTPState:
|
|||
).remote(
|
||||
self._config.host,
|
||||
self._config.port,
|
||||
self._config.root_path,
|
||||
controller_name=self._controller_name,
|
||||
controller_namespace=self._controller_namespace,
|
||||
http_middlewares=self._config.middlewares)
|
||||
|
|
|
@ -310,6 +310,32 @@ def test_http_root_url(ray_shutdown):
|
|||
ray.shutdown()
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="Failing on Windows")
|
||||
def test_http_root_path(ray_shutdown):
|
||||
@serve.deployment
|
||||
def hello():
|
||||
return "hello"
|
||||
|
||||
port = new_port()
|
||||
root_path = "/serve"
|
||||
serve.start(http_options=dict(root_path=root_path, port=port))
|
||||
hello.deploy()
|
||||
|
||||
# check whether url is prefixed correctly
|
||||
assert hello.url == f"http://127.0.0.1:{port}{root_path}/hello"
|
||||
|
||||
# check routing works as expected
|
||||
resp = requests.get(hello.url)
|
||||
assert resp.status_code == 200
|
||||
assert resp.text == "hello"
|
||||
|
||||
# check advertized routes are prefixed correctly
|
||||
resp = requests.get(f"http://127.0.0.1:{port}{root_path}/-/routes")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"/hello": "hello"}
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="Failing on Windows")
|
||||
def test_http_proxy_fail_loudly(ray_shutdown):
|
||||
# Test that if the http server fail to start, serve.start should fail.
|
||||
with pytest.raises(ValueError):
|
||||
|
|
Loading…
Add table
Reference in a new issue