Skip to content

Commit 5bfe912

Browse files
committed
Add --root-path (and normalize_root_path)
Signed-off-by: Alvaro Bartolome <36760800+alvarobartt@users.noreply.github.com>
1 parent fc071b1 commit 5bfe912

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

router/src/http/server.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,7 @@ pub async fn run(
16321632
payload_limit: usize,
16331633
api_key: Option<String>,
16341634
cors_allow_origin: Option<Vec<String>>,
1635+
root_path: Option<String>,
16351636
) -> Result<(), anyhow::Error> {
16361637
// OpenAPI documentation
16371638
#[derive(OpenApi)]
@@ -1872,6 +1873,14 @@ pub async fn run(
18721873
.layer(DefaultBodyLimit::max(payload_limit))
18731874
.layer(cors_layer);
18741875

1876+
let app = match normalize_root_path(root_path.as_deref())? {
1877+
Some(root_path) => {
1878+
tracing::info!("Serving HTTP routes under root path: {root_path}");
1879+
Router::new().nest(&root_path, app)
1880+
}
1881+
None => app,
1882+
};
1883+
18751884
// Run server
18761885
let listener = tokio::net::TcpListener::bind(&addr)
18771886
.await
@@ -1888,6 +1897,24 @@ pub async fn run(
18881897
Ok(())
18891898
}
18901899

1900+
fn normalize_root_path(root_path: Option<&str>) -> Result<Option<String>, anyhow::Error> {
1901+
let Some(root_path) = root_path else {
1902+
return Ok(None);
1903+
};
1904+
1905+
let root_path = root_path.trim();
1906+
if root_path.is_empty() || root_path == "/" {
1907+
return Ok(None);
1908+
}
1909+
1910+
if root_path.contains('?') || root_path.contains('#') {
1911+
anyhow::bail!("`--root-path` must be a path and cannot contain `?` or `#`");
1912+
}
1913+
1914+
let root_path = format!("/{}", root_path.trim_matches('/'));
1915+
Ok(Some(root_path))
1916+
}
1917+
18911918
impl From<&ErrorType> for StatusCode {
18921919
fn from(value: &ErrorType) -> Self {
18931920
match value {
@@ -1932,3 +1959,34 @@ impl From<serde_json::Error> for ErrorResponse {
19321959
}
19331960
}
19341961
}
1962+
1963+
#[cfg(test)]
1964+
mod tests {
1965+
use super::normalize_root_path;
1966+
1967+
#[test]
1968+
fn normalize_root_path_ignores_empty_and_root() {
1969+
assert_eq!(normalize_root_path(None).unwrap(), None);
1970+
assert_eq!(normalize_root_path(Some("")).unwrap(), None);
1971+
assert_eq!(normalize_root_path(Some(" ")).unwrap(), None);
1972+
assert_eq!(normalize_root_path(Some("/")).unwrap(), None);
1973+
}
1974+
1975+
#[test]
1976+
fn normalize_root_path_adds_leading_slash_and_trims_trailing_slashes() {
1977+
assert_eq!(
1978+
normalize_root_path(Some("jinaai/jina-embeddings-v2-base-en")).unwrap(),
1979+
Some("/jinaai/jina-embeddings-v2-base-en".to_string())
1980+
);
1981+
assert_eq!(
1982+
normalize_root_path(Some("/jinaai/jina-embeddings-v2-base-en/")).unwrap(),
1983+
Some("/jinaai/jina-embeddings-v2-base-en".to_string())
1984+
);
1985+
}
1986+
1987+
#[test]
1988+
fn normalize_root_path_rejects_query_and_fragment() {
1989+
assert!(normalize_root_path(Some("/foo?bar")).is_err());
1990+
assert!(normalize_root_path(Some("/foo#bar")).is_err());
1991+
}
1992+
}

router/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub async fn run(
6262
hf_token: Option<String>,
6363
hostname: Option<String>,
6464
port: u16,
65+
root_path: Option<String>,
6566
uds_path: Option<String>,
6667
huggingface_hub_cache: Option<String>,
6768
payload_limit: usize,
@@ -390,15 +391,17 @@ pub async fn run(
390391
payload_limit,
391392
api_key,
392393
cors_allow_origin,
394+
root_path,
393395
)
394396
.await
395397
}
396398

397399
#[cfg(feature = "grpc")]
398400
{
399-
// cors_allow_origin and payload_limit are not used for gRPC servers
401+
// cors_allow_origin, payload_limit and root_path are not used for gRPC servers
400402
let _ = cors_allow_origin;
401403
let _ = payload_limit;
404+
let _ = root_path;
402405
grpc::server::run(infer, info, addr, prom_builder, api_key).await
403406
}
404407
}

router/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@ struct Args {
151151
#[clap(default_value = "3000", long, short, env)]
152152
port: u16,
153153

154+
/// Root path to mount all HTTP routes under.
155+
///
156+
/// For example, `--root-path jinaai/jina-embeddings-v2-base-en` serves the
157+
/// OpenAI-compatible embeddings route at `/jinaai/jina-embeddings-v2-base-en/v1/embeddings`.
158+
///
159+
/// Unused for gRPC servers.
160+
#[clap(long, env)]
161+
root_path: Option<String>,
162+
154163
/// The name of the unix socket some text-embeddings-inference backends will use as they
155164
/// communicate internally with gRPC.
156165
#[clap(default_value = "/tmp/text-embeddings-inference-server", long, env)]
@@ -258,6 +267,7 @@ async fn main() -> Result<()> {
258267
token,
259268
Some(args.hostname),
260269
args.port,
270+
args.root_path,
261271
Some(args.uds_path),
262272
args.huggingface_hub_cache,
263273
args.payload_limit,

0 commit comments

Comments
 (0)