diff --git a/Cargo.lock b/Cargo.lock index 2da32bedf..7e112128f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.92" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", @@ -260,9 +260,9 @@ dependencies = [ [[package]] name = "aws-config" -version = "1.10.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b180a3c8b55960db3426d8964b8745e652466a1a49fe1a2eda828046d30b5e4" +checksum = "701418aa459dac33e50a0f8e818e5662a16bc018a6ac7423659b70f3799d67a8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.9.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" +checksum = "a6b50a43f3ccdf331521c6d6c68b7cc9668b6e09d439ebda9569df5722324d76" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -350,9 +350,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.105.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffd0fbe7873cb548a7aa60f9573c268fff94155397fd4f14dc9f1ecaaab8516" +checksum = "b53416d16c278234845392e38d93bd4481d2f09daa0f005a2277f0aa91f59c22" dependencies = [ "arc-swap", "aws-credential-types", @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.107.0" +version = "1.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175763eb222a46377df7aa257a3bca980ab3e96703fefc8f4d0b8da6ad2e254c" +checksum = "cc9b706c3305ed0285d5b1b696c747aa34950f830fb03e3e6c76890f99b9f188" dependencies = [ "arc-swap", "aws-credential-types", @@ -402,9 +402,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.110.0" +version = "1.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b14781dfbff48984017d57167b6ea0b6471c6920ec52b44a2677c7feb3c13" +checksum = "32d214cdfa5bbe17f117e76a7643fadf32a5234fb597322ef8b1fb4b2f17dbbd" dependencies = [ "arc-swap", "aws-credential-types", @@ -540,9 +540,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.12.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07505b34e8f4b3591a4fa69e9792b52289b95488dbbc68c3c0075b7bedb245e1" +checksum = "bea94a9ff8464016338c851e24b472d7131c388c88898a502e781815b2ee6045" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -656,15 +656,15 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.9" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ - "async-trait", "axum-core", "axum-macros", "base64 0.22.1", "bytes", + "form_urlencoded", "futures-util", "http 1.5.0", "http-body 1.0.1", @@ -677,15 +677,14 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", "sha1 0.10.6", "sync_wrapper", "tokio", - "tokio-tungstenite 0.24.0", + "tokio-tungstenite 0.29.0", "tower", "tower-layer", "tower-service", @@ -694,19 +693,17 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http 1.5.0", "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -715,9 +712,9 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ "proc-macro2", "quote", @@ -894,9 +891,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136" dependencies = [ "find-msvc-tools", "jobserver", @@ -2564,9 +2561,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.38.2" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" +checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db" dependencies = [ "cc", "pkg-config", @@ -2669,9 +2666,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "matrixmultiply" @@ -3214,7 +3211,7 @@ dependencies = [ "bitflags", "num-traits", "rand 0.9.4", - "rand_chacha 0.9.0", + "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -3393,24 +3390,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -3425,16 +3411,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -3445,15 +3421,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -3506,7 +3473,7 @@ dependencies = [ "paste", "profiling", "rand 0.9.4", - "rand_chacha 0.9.0", + "rand_chacha", "simd_helpers", "thiserror 2.0.20", "v_frame", @@ -3710,9 +3677,9 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.40.2" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" +checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323" dependencies = [ "bitflags", "fallible-iterator", @@ -4433,14 +4400,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.24.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" dependencies = [ "futures-util", "log", "tokio", - "tungstenite 0.24.0", + "tungstenite 0.29.0", ] [[package]] @@ -4770,20 +4737,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.24.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" dependencies = [ - "byteorder", "bytes", "data-encoding", "http 1.5.0", "httparse", "log", - "rand 0.8.6", + "rand 0.9.4", "sha1 0.10.6", - "thiserror 1.0.69", - "utf-8", + "thiserror 2.0.20", ] [[package]] @@ -4922,12 +4887,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8-zero" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 07c308e1a..32fd4aed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ tracing = { version = "0.1", features = ["log"] } anyhow = "1" clap = { version = "4", features = ["derive"] } tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] } -axum = "0.7" +axum = "0.8" tower = "0.5" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } pyo3 = { version = "0.29", features = ["abi3-py310"] } diff --git a/crates/headroom-proxy/src/proxy.rs b/crates/headroom-proxy/src/proxy.rs index 1005fb03f..0389e14c2 100644 --- a/crates/headroom-proxy/src/proxy.rs +++ b/crates/headroom-proxy/src/proxy.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use std::time::Instant; use axum::body::{to_bytes, Body}; -use axum::extract::{ConnectInfo, DefaultBodyLimit, State, WebSocketUpgrade}; +use axum::extract::{ConnectInfo, DefaultBodyLimit, FromRequestParts, State, WebSocketUpgrade}; use axum::http::{HeaderMap, HeaderName, Request, Response, StatusCode, Uri}; use axum::response::IntoResponse; use axum::routing::{any, get, post}; @@ -189,14 +189,14 @@ pub fn build_app(state: AppState) -> Router { // publisher endpoints look like // `POST /v1beta1/projects/{p}/locations/{l}/publishers/anthropic/models/{m}:rawPredict` // (and `:streamRawPredict`). The trailing `:` is awkward - // in axum's `:param` syntax, so we capture the entire trailing - // segment as `:model_action` and split on the last `:` inside + // in axum's `{param}` syntax, so we capture the entire trailing + // segment as `{model_action}` and split on the last `:` inside // the dispatcher. Both verbs share the same axum route shape // — matchit can't distinguish two patterns that overlap on the // literal parameter. The verb dispatch lives in // [`crate::vertex::handle_vertex_predict_dispatch`]. .route( - "/v1beta1/projects/:project/locations/:location/publishers/anthropic/models/:model_action", + "/v1beta1/projects/{project}/locations/{location}/publishers/anthropic/models/{model_action}", post(crate::vertex::handle_vertex_predict_dispatch), ); @@ -219,11 +219,11 @@ pub fn build_app(state: AppState) -> Router { // Bedrock handlers identically. let bedrock_router: Router = Router::new() .route( - "/model/:model_id/invoke", + "/model/{model_id}/invoke", post(crate::bedrock::invoke::handle_invoke), ) .route( - "/model/:model_id/converse", + "/model/{model_id}/converse", post(crate::bedrock::invoke::handle_invoke), ) // PR-D2/PR-D5: streaming counterparts. Bedrock's protocol is @@ -235,11 +235,11 @@ pub fn build_app(state: AppState) -> Router { // processing pipeline, so both route to the same handler. // See `bedrock::invoke_streaming`. .route( - "/model/:model_id/invoke-with-response-stream", + "/model/{model_id}/invoke-with-response-stream", post(crate::bedrock::invoke_streaming::handle_invoke_streaming), ) .route( - "/model/:model_id/converse-stream", + "/model/{model_id}/converse-stream", post(crate::bedrock::invoke_streaming::handle_invoke_streaming), ) .route_layer(axum::middleware::from_fn( @@ -281,18 +281,18 @@ pub fn build_app(state: AppState) -> Router { post(crate::handlers::conversations::handle_conversations_create), ) .route( - "/v1/conversations/:conversation_id", + "/v1/conversations/{conversation_id}", get(crate::handlers::conversations::handle_conversations_get) .post(crate::handlers::conversations::handle_conversations_update) .delete(crate::handlers::conversations::handle_conversations_delete), ) .route( - "/v1/conversations/:conversation_id/items", + "/v1/conversations/{conversation_id}/items", post(crate::handlers::conversations::handle_conversations_items_create) .get(crate::handlers::conversations::handle_conversations_items_list), ) .route( - "/v1/conversations/:conversation_id/items/:item_id", + "/v1/conversations/{conversation_id}/items/{item_id}", get(crate::handlers::conversations::handle_conversations_item_get) .delete(crate::handlers::conversations::handle_conversations_item_delete), ); @@ -315,17 +315,22 @@ pub fn build_app(state: AppState) -> Router { async fn catch_all( State(state): State, ConnectInfo(client_addr): ConnectInfo, - ws: Option, req: Request, ) -> Response { - if is_websocket_upgrade(req.headers()) { - if let Some(ws) = ws { + let (mut parts, body) = req.into_parts(); + if is_websocket_upgrade(&parts.headers) { + // axum 0.8 requires optional extractors to opt in explicitly, and + // WebSocketUpgrade intentionally does not. Extract it only after the + // upgrade headers have identified this as a WebSocket request. + if let Ok(ws) = WebSocketUpgrade::from_request_parts(&mut parts, &state).await { + let req = Request::from_parts(parts, body); return ws_handler(ws, state, client_addr, req).await; } // Header says websocket but axum didn't extract it (likely missing // Sec-WebSocket-Key) — fall through to HTTP forwarding which will // surface the upstream error. } + let req = Request::from_parts(parts, body); forward_http(state, client_addr, req) .await .unwrap_or_else(|e| e.into_response()) diff --git a/crates/headroom-proxy/src/websocket.rs b/crates/headroom-proxy/src/websocket.rs index 509cd6bfd..3a1be2e4f 100644 --- a/crates/headroom-proxy/src/websocket.rs +++ b/crates/headroom-proxy/src/websocket.rs @@ -232,10 +232,10 @@ fn ax_to_tg(m: AxMsg) -> Option { fn tg_to_ax(m: TgMsg) -> Option { Some(match m { - TgMsg::Text(t) => AxMsg::Text(t.as_str().to_string()), - TgMsg::Binary(b) => AxMsg::Binary(b.to_vec()), - TgMsg::Ping(p) => AxMsg::Ping(p.to_vec()), - TgMsg::Pong(p) => AxMsg::Pong(p.to_vec()), + TgMsg::Text(t) => AxMsg::Text(t.as_str().to_string().into()), + TgMsg::Binary(b) => AxMsg::Binary(b.to_vec().into()), + TgMsg::Ping(p) => AxMsg::Ping(p.to_vec().into()), + TgMsg::Pong(p) => AxMsg::Pong(p.to_vec().into()), TgMsg::Close(Some(cf)) => AxMsg::Close(Some(CloseFrame { code: cf.code.into(), reason: cf.reason.to_string().into(), diff --git a/crates/headroom-proxy/tests/integration_bedrock_authmode.rs b/crates/headroom-proxy/tests/integration_bedrock_authmode.rs index 81fe30085..6fa056889 100644 --- a/crates/headroom-proxy/tests/integration_bedrock_authmode.rs +++ b/crates/headroom-proxy/tests/integration_bedrock_authmode.rs @@ -104,7 +104,7 @@ async fn bedrock_classified_as_oauth() { auth_mode.as_str().to_string() } let app = Router::new() - .route("/model/:model_id/invoke", post(probe)) + .route("/model/{model_id}/invoke", post(probe)) .route_layer(axum::middleware::from_fn(classify_and_attach_auth_mode)); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();