[Dashboard] Make requests sent by the dashboard reverse proxy compatible (#14012)

This commit is contained in:
niole 2021-02-24 18:31:59 -08:00 committed by GitHub
parent ef96193b8b
commit 488f63efe3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 97 additions and 24 deletions

View file

@ -0,0 +1 @@
PUBLIC_URL="."

View file

@ -16,7 +16,6 @@
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*

View file

@ -1,4 +1,4 @@
const base = window.location.origin;
import { formatUrl } from "./service/requestHandlers";
type APIResponse<T> = {
result: boolean;
@ -7,12 +7,12 @@ type APIResponse<T> = {
};
// TODO(mitchellstern): Add JSON schema validation for the responses.
const get = async <T>(path: string, params: { [key: string]: any }) => {
const url = new URL(path, base);
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
const formattedParams = Object.entries(params)
.map((pair) => pair.join("="))
.join("&");
const url = [formatUrl(path), formattedParams].filter((x) => x!!).join("?");
const response = await fetch(url.toString());
const response = await fetch(url);
const json: APIResponse<T> = await response.json();
const { result, msg, data } = json;
@ -323,10 +323,12 @@ export const checkProfilingStatus = (profilingId: string) =>
profiling_id: profilingId,
});
export const getProfilingResultURL = (profilingId: string) =>
`${base}/speedscope/index.html#profileURL=${encodeURIComponent(
`${base}/api/get_profiling_info?profiling_id=${profilingId}`,
)}`;
export const getProfilingResultURL = (profilingId: string) => {
const uriComponent = encodeURIComponent(
formatUrl(`/api/get_profiling_info?profiling_id=${profilingId}`),
);
return formatUrl(`/speedscope/index.html#profileURL=${uriComponent}`);
};
export const launchKillActor = (
actorId: string,

View file

@ -1,8 +1,8 @@
import axios from "axios";
import { Actor } from "../type/actor";
import { get } from "./requestHandlers";
export const getActors = () => {
return axios.get<{
return get<{
result: boolean;
message: string;
data: {

View file

@ -1,6 +1,6 @@
import axios from "axios";
import { RayConfigRsp } from "../type/config";
import { get } from "./requestHandlers";
export const getRayConfig = () => {
return axios.get<RayConfigRsp>("api/ray_config");
return get<RayConfigRsp>("api/ray_config");
};

View file

@ -1,10 +1,10 @@
import axios from "axios";
import { JobDetailRsp, JobListRsp } from "../type/job";
import { get } from "./requestHandlers";
export const getJobList = () => {
return axios.get<JobListRsp>("jobs?view=summary");
return get<JobListRsp>("jobs?view=summary");
};
export const getJobDetail = (id: string) => {
return axios.get<JobDetailRsp>(`jobs/${id}`);
return get<JobDetailRsp>(`jobs/${id}`);
};

View file

@ -1,4 +1,4 @@
import axios from "axios";
import { get } from "./requestHandlers";
export const getLogDetail = async (url: string) => {
if (window.location.pathname !== "/" && url !== "log_index") {
@ -12,7 +12,7 @@ export const getLogDetail = async (url: string) => {
}
}
}
const rsp = await axios.get(
const rsp = await get(
url === "log_index" ? url : `log_proxy?url=${encodeURIComponent(url)}`,
);
if (rsp.headers["content-type"]?.includes("html")) {

View file

@ -1,10 +1,10 @@
import axios from "axios";
import { NodeDetailRsp, NodeListRsp } from "../type/node";
import { get } from "./requestHandlers";
export const getNodeList = async () => {
return await axios.get<NodeListRsp>("nodes?view=summary");
return await get<NodeListRsp>("nodes?view=summary");
};
export const getNodeDetail = async (id: string) => {
return await axios.get<NodeDetailRsp>(`nodes/${id}`);
return await get<NodeDetailRsp>(`nodes/${id}`);
};

View file

@ -0,0 +1,34 @@
/**
* This utility file formats and sends HTTP requests such that
* they fullfill the requirements expected by users of the dashboard.
*
* All HTTP requests should be sent using the helpers in this file.
*
* More HTTP Methods helpers should be added to this file when the need
* arises.
*/
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
/**
* This function formats URLs such that the user's browser
* sets the HTTP request's Request URL relative to the path at
* which the dashboard is served.
* This works behind a reverse proxy.
*
* @param {String} url The URL to be hit
* @return {String} The reverse proxy compatible URL
*/
export const formatUrl = (url: string): string => {
if (url.startsWith("/")) {
return url.slice(1);
}
return url;
};
export const get = <T = any, R = AxiosResponse<T>>(
url: string,
config?: AxiosRequestConfig,
): Promise<R> => {
return axios.get<T, R>(formatUrl(url), config);
};

View file

@ -6,6 +6,6 @@ pillow==7.2.0
alabaster>=0.7,<0.8,!=0.7.5
commonmark==0.8.1
recommonmark==0.5.0
sphinx<2
sphinx==3.0.4
readthedocs-sphinx-ext<1.1
sphinx-book-theme

View file

@ -169,6 +169,43 @@ More information on how to interpret the flamegraph is available at https://gith
.. image:: https://raw.githubusercontent.com/ray-project/images/master/docs/dashboard/dashboard-profiling.png
:align: center
Running Behind a Reverse Proxy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In ray 2.0.0, the dashboard works out-of-the-box when accessed via a reverse proxy. API requests no
longer need to be proxied individually.
Always access the dashboard with a trailing ``/`` at the end of the URL.
For example, if your proxy is set up to handle requests to ``/ray/dashboard``, view the dashboard at ``www.my-website.com/ray/dashboard/``.
The dashboard now sends HTTP requests with relative URL paths. Browsers will handle these requests as expected when the ``window.location.href`` ends in a trailing ``/``.
This is a peculiarity of how many browsers handle requests with relative URLs, despite what `MDN <https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL#examples_of_relative_urls>`_
defines as the expected behavior.
Make your dashboard visible without a trailing ``/`` by including a rule in your reverse proxy that
redirects the user's browser to ``/``, i.e. ``/ray/dashboard`` --> ``/ray/dashboard/``.
Below is an example with a `traefik <https://doc.traefik.io/traefik/getting-started/quick-start/>`_ TOML file that accomplishes this:
.. code-block:: yaml
[http]
[http.routers]
[http.routers.to-dashboard]
rule = "PathPrefix(`/ray/dashboard`)"
middlewares = ["test-redirectregex", "strip"]
service = "dashboard"
[http.middlewares]
[http.middlewares.test-redirectregex.redirectRegex]
regex = "^(.*)/ray/dashboard$"
replacement = "${1}/ray/dashboard/"
[http.middlewares.strip.stripPrefix]
prefixes = ["/ray/dashboard"]
[http.services]
[http.services.dashboard.loadBalancer]
[[http.services.dashboard.loadBalancer.servers]]
url = "http://localhost:8265"
References
----------