fix: strip /agent/<name>/ via rewrite in named location
nginx forbids a URI part on proxy_pass inside named locations, so the split-mode `@<name>_dynamic` fallback (introduced when the gateway started serving static dist directly) failed config-test with: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location Render two upstream forms: prefix locations keep the trailing slash so nginx auto-strips the location prefix; the named-location path strips `/agent/<name>/` via `rewrite ... break` and uses a bare upstream.
This commit is contained in:
parent
fe62db8917
commit
f9de183f4e
1 changed files with 51 additions and 7 deletions
|
|
@ -76,13 +76,22 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String {
|
||||||
);
|
);
|
||||||
for name in names {
|
for name in names {
|
||||||
let port = lifecycle::agent_web_port(name);
|
let port = lifecycle::agent_web_port(name);
|
||||||
let upstream = if agent_sockets::ready_marker_for(name).exists() {
|
// Two upstream forms because named locations (split mode's
|
||||||
format!(
|
// `@<name>_dynamic`) forbid a URI part on `proxy_pass`. The
|
||||||
"http://unix:{}:/",
|
// legacy prefix-location path keeps the trailing `/` so nginx
|
||||||
agent_sockets::socket_path_for(name).display()
|
// strips `/agent/<name>/` automatically; the named-location
|
||||||
|
// path strips the prefix via `rewrite` and uses a bare upstream.
|
||||||
|
let (upstream_prefix, upstream_bare) = if agent_sockets::ready_marker_for(name).exists() {
|
||||||
|
let sock = agent_sockets::socket_path_for(name).display().to_string();
|
||||||
|
(
|
||||||
|
format!("http://unix:{sock}:/"),
|
||||||
|
format!("http://unix:{sock}:"),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("http://127.0.0.1:{port}/")
|
(
|
||||||
|
format!("http://127.0.0.1:{port}/"),
|
||||||
|
format!("http://127.0.0.1:{port}"),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(frontend) = frontend_dir {
|
if let Some(frontend) = frontend_dir {
|
||||||
|
|
@ -124,7 +133,8 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String {
|
||||||
}}\n\
|
}}\n\
|
||||||
\nlocation @{name}_dynamic {{\n\
|
\nlocation @{name}_dynamic {{\n\
|
||||||
{PROXY_HEADER_BLOCK}\n\
|
{PROXY_HEADER_BLOCK}\n\
|
||||||
\n proxy_pass {upstream};\n\
|
\n rewrite ^/agent/{name}/(.*)$ /$1 break;\n\
|
||||||
|
\n proxy_pass {upstream_bare};\n\
|
||||||
\n proxy_set_header X-Forwarded-Prefix /agent/{name};\n\
|
\n proxy_set_header X-Forwarded-Prefix /agent/{name};\n\
|
||||||
\n proxy_buffering off;\n\
|
\n proxy_buffering off;\n\
|
||||||
\n proxy_read_timeout 1d;\n\
|
\n proxy_read_timeout 1d;\n\
|
||||||
|
|
@ -138,7 +148,7 @@ fn render(names: &[String], frontend_dir: Option<&str>) -> String {
|
||||||
out,
|
out,
|
||||||
"\nlocation /agent/{name}/ {{\n\
|
"\nlocation /agent/{name}/ {{\n\
|
||||||
{PROXY_HEADER_BLOCK}\n\
|
{PROXY_HEADER_BLOCK}\n\
|
||||||
\n proxy_pass {upstream};\n\
|
\n proxy_pass {upstream_prefix};\n\
|
||||||
\n proxy_set_header X-Forwarded-Prefix /agent/{name};\n\
|
\n proxy_set_header X-Forwarded-Prefix /agent/{name};\n\
|
||||||
\n proxy_buffering off;\n\
|
\n proxy_buffering off;\n\
|
||||||
\n proxy_read_timeout 1d;\n\
|
\n proxy_read_timeout 1d;\n\
|
||||||
|
|
@ -381,6 +391,40 @@ mod tests {
|
||||||
assert!(body.contains("__hive_agent_unreachable"));
|
assert!(body.contains("__hive_agent_unreachable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_mode_named_location_strips_prefix_without_uri_part() {
|
||||||
|
// Named locations forbid a URI part on proxy_pass — nginx rejects
|
||||||
|
// `proxy_pass http://host/` inside `location @name`. Must use
|
||||||
|
// bare upstream (no trailing `/` or path) plus a `rewrite` to
|
||||||
|
// strip the /agent/<name>/ prefix.
|
||||||
|
let names = vec!["iris".to_owned()];
|
||||||
|
let body = render(&names, Some(FAKE_FRONTEND));
|
||||||
|
assert!(
|
||||||
|
body.contains("rewrite ^/agent/iris/(.*)$ /$1 break;"),
|
||||||
|
"expected prefix-strip rewrite, got:\n{body}"
|
||||||
|
);
|
||||||
|
// The named-location proxy_pass must have no URI part (no
|
||||||
|
// trailing slash or path). Extract the `@iris_dynamic { ... }`
|
||||||
|
// block and check every proxy_pass directive in it.
|
||||||
|
let block_start = body.find("location @iris_dynamic {").expect("named location");
|
||||||
|
let block = &body[block_start..];
|
||||||
|
let block_end = block.find("\n}\n").expect("block close");
|
||||||
|
let block = &block[..block_end];
|
||||||
|
for line in block.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if let Some(rest) = line.strip_prefix("proxy_pass ") {
|
||||||
|
let target = rest.trim_end_matches(';');
|
||||||
|
// No URI part means: TCP form `http://host:port` (no
|
||||||
|
// trailing `/`), UDS form `http://unix:/path:` (trailing
|
||||||
|
// colon, nothing after). Both cases: must not end in `/`.
|
||||||
|
assert!(
|
||||||
|
!target.ends_with('/'),
|
||||||
|
"named-location proxy_pass must have no URI part, got: {target}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn split_mode_includes_manager() {
|
fn split_mode_includes_manager() {
|
||||||
let names: Vec<String> = [MANAGER_NAME, "iris"]
|
let names: Vec<String> = [MANAGER_NAME, "iris"]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue