From a8720a87211637cb335637b96e3487d0788104c1 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 14 Apr 2025 15:02:08 -0400 Subject: [PATCH 1/5] feat: cors example --- Cargo.toml | 2 ++ examples/cors.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/cors.rs diff --git a/Cargo.toml b/Cargo.toml index 2b004f5..4a1f0a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ futures-util = { version = "0.3.31", optional = true } tempfile = "3.15.0" tracing-subscriber = "0.3.19" axum = { version = "0.8.1", features = ["macros"] } +tower-http = { version = "0.6.2", features = ["cors"] } +tokio = { version = "1.43.0", features = ["full"] } [features] default = ["axum", "ws", "ipc"] diff --git a/examples/cors.rs b/examples/cors.rs new file mode 100644 index 0000000..71795c2 --- /dev/null +++ b/examples/cors.rs @@ -0,0 +1,44 @@ +use axum::http::{HeaderValue, Method}; +use std::{future::IntoFuture, net::SocketAddr}; +use tower_http::cors::{AllowOrigin, Any, CorsLayer}; + +fn make_cors(cors: Option<&str>) -> tower_http::cors::CorsLayer { + let cors = cors + .unwrap_or("*") + .parse::() + .map(Into::::into) + .unwrap_or_else(|_| AllowOrigin::any()); + + CorsLayer::new() + .allow_methods([Method::GET, Method::POST]) + .allow_origin(cors) + .allow_headers(Any) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cors = std::env::args().nth(1); + let port = std::env::args() + .nth(2) + .map(|src| u16::from_str_radix(&src, 10).ok()) + .flatten() + .unwrap_or(8080); + + let router = ajj::Router::<()>::new() + .route("helloWorld", || async { + tracing::info!("serving hello world"); + Ok::<_, ()>("Hello, world!") + }) + .route("addNumbers", |(a, b): (u32, u32)| async move { + tracing::info!("serving addNumbers"); + Ok::<_, ()>(a + b) + }) + .into_axum("/") + .layer(make_cors(cors.as_deref())); + + let addr = SocketAddr::from(([127, 0, 0, 1], port)); + let listener = tokio::net::TcpListener::bind(addr).await?; + + axum::serve(listener, router).into_future().await?; + Ok(()) +} From d06028345f2af9803082448f23d216343129a19c Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Apr 2025 08:15:07 -0400 Subject: [PATCH 2/5] fix: clippy and feature checks --- Cargo.toml | 3 +++ examples/cors.rs | 5 ++--- src/lib.rs | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a1f0a1..1825ea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,12 +39,15 @@ tokio-tungstenite = { version = "0.26.1", features = ["rustls-tls-webpki-roots"] futures-util = { version = "0.3.31", optional = true } [dev-dependencies] +ajj = { path = "./", features = ["axum", "ws", "ipc"] } + tempfile = "3.15.0" tracing-subscriber = "0.3.19" axum = { version = "0.8.1", features = ["macros"] } tower-http = { version = "0.6.2", features = ["cors"] } tokio = { version = "1.43.0", features = ["full"] } + [features] default = ["axum", "ws", "ipc"] axum = ["dep:axum", "dep:mime"] diff --git a/examples/cors.rs b/examples/cors.rs index 71795c2..1173b71 100644 --- a/examples/cors.rs +++ b/examples/cors.rs @@ -20,9 +20,8 @@ async fn main() -> Result<(), Box> { let cors = std::env::args().nth(1); let port = std::env::args() .nth(2) - .map(|src| u16::from_str_radix(&src, 10).ok()) - .flatten() - .unwrap_or(8080); + .and_then(|src| src.parse().ok()) + .unwrap_or(8080u16); let router = ajj::Router::<()>::new() .route("helloWorld", || async { diff --git a/src/lib.rs b/src/lib.rs index cb67dc9..a5da1e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,7 +136,6 @@ //! //! [`axum`]: https://docs.rs/axum/latest/axum/index.html //! [`axum::Router`]: https://docs.rs/axum/latest/axum/routing/struct.Router.html -//! [`ResponsePayload`]: alloy::rpc::json_rpc::ResponsePayload //! [`interprocess::local_socket::ListenerOptions`]: https://docs.rs/interprocess/latest/interprocess/local_socket/struct.ListenerOptions.html #![warn( From 8217b74c83a6086acea2521e4dc559da5bf429e1 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 19 May 2025 09:57:44 -0400 Subject: [PATCH 3/5] fix: better corsing --- Cargo.toml | 1 + examples/cors.rs | 36 +++++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1825ea9..b87e3a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ tracing-subscriber = "0.3.19" axum = { version = "0.8.1", features = ["macros"] } tower-http = { version = "0.6.2", features = ["cors"] } tokio = { version = "1.43.0", features = ["full"] } +eyre = "0.6.12" [features] diff --git a/examples/cors.rs b/examples/cors.rs index 1173b71..0fe48e4 100644 --- a/examples/cors.rs +++ b/examples/cors.rs @@ -1,22 +1,36 @@ use axum::http::{HeaderValue, Method}; +use eyre::{ensure, Context}; use std::{future::IntoFuture, net::SocketAddr}; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; -fn make_cors(cors: Option<&str>) -> tower_http::cors::CorsLayer { - let cors = cors - .unwrap_or("*") - .parse::() - .map(Into::::into) - .unwrap_or_else(|_| AllowOrigin::any()); +fn make_cors(cors: Option<&str>) -> eyre::Result { + let origins = match cors { + None | Some("*") => AllowOrigin::any(), + Some(cors) => { + ensure!( + !cors.split(',').any(|o| o == "*"), + "Wildcard '*' is not allowed in CORS domains" + ); - CorsLayer::new() + cors.split(',') + .map(|domain| { + domain + .parse::() + .wrap_err_with(|| format!("Invalid CORS domain: {}", domain)) + }) + .collect::, _>>()? + .into() + } + }; + + Ok(CorsLayer::new() .allow_methods([Method::GET, Method::POST]) - .allow_origin(cors) - .allow_headers(Any) + .allow_origin(origins) + .allow_headers(Any)) } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> eyre::Result<()> { let cors = std::env::args().nth(1); let port = std::env::args() .nth(2) @@ -33,7 +47,7 @@ async fn main() -> Result<(), Box> { Ok::<_, ()>(a + b) }) .into_axum("/") - .layer(make_cors(cors.as_deref())); + .layer(make_cors(cors.as_deref())?); let addr = SocketAddr::from(([127, 0, 0, 1], port)); let listener = tokio::net::TcpListener::bind(addr).await?; From cc6e8c2a215fddb15680857362c743bcf9fcc261 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 19 May 2025 09:59:01 -0400 Subject: [PATCH 4/5] chore: make arg optional --- examples/cors.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/cors.rs b/examples/cors.rs index 0fe48e4..37d5f91 100644 --- a/examples/cors.rs +++ b/examples/cors.rs @@ -3,10 +3,10 @@ use eyre::{ensure, Context}; use std::{future::IntoFuture, net::SocketAddr}; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; -fn make_cors(cors: Option<&str>) -> eyre::Result { +fn make_cors(cors: &str) -> eyre::Result { let origins = match cors { - None | Some("*") => AllowOrigin::any(), - Some(cors) => { + "*" => AllowOrigin::any(), + cors => { ensure!( !cors.split(',').any(|o| o == "*"), "Wildcard '*' is not allowed in CORS domains" @@ -31,7 +31,8 @@ fn make_cors(cors: Option<&str>) -> eyre::Result { #[tokio::main] async fn main() -> eyre::Result<()> { - let cors = std::env::args().nth(1); + let cors = std::env::args().nth(1).unwrap_or("*".to_string()); + ensure!(cors != "*", "Wildcard '*' is not allowed in CORS domains"); let port = std::env::args() .nth(2) .and_then(|src| src.parse().ok()) @@ -47,7 +48,7 @@ async fn main() -> eyre::Result<()> { Ok::<_, ()>(a + b) }) .into_axum("/") - .layer(make_cors(cors.as_deref())?); + .layer(make_cors(&cors)?); let addr = SocketAddr::from(([127, 0, 0, 1], port)); let listener = tokio::net::TcpListener::bind(addr).await?; From f6a514df1479b7ae7d69340fed7b41e2c756ddc6 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 19 May 2025 10:05:05 -0400 Subject: [PATCH 5/5] refactor: improve the example and comment --- examples/cors.rs | 71 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/examples/cors.rs b/examples/cors.rs index 37d5f91..a55d6a5 100644 --- a/examples/cors.rs +++ b/examples/cors.rs @@ -1,27 +1,51 @@ +//! Basic example of using ajj with CORS. +//! +//! This example demonstrates how to set up a simple HTTP server using `axum` +//! and `tower_http` for CORS support. +//! +//! ## Inovking this example +//! +//! ```ignore +//! // Invoking with cors domains +//! cargo run --example cors -- "https://example.com,https://example.org" +//! // Invoking with wildcard +//! cargo run --example cors +//! ``` + use axum::http::{HeaderValue, Method}; use eyre::{ensure, Context}; use std::{future::IntoFuture, net::SocketAddr}; use tower_http::cors::{AllowOrigin, Any, CorsLayer}; +fn get_allowed(cors: &str) -> eyre::Result { + // Wildcard `*` means any origin is allowed. + if cors == "*" { + return Ok(AllowOrigin::any()); + } + + // Check if the cors string contains a wildcard + ensure!( + !cors.split(',').any(|o| o == "*"), + "Wildcard '*' is not allowed in CORS domains" + ); + + // Split the cors string by commas and parse each domain into a HeaderValue + // Then convert the Vec into AllowOrigin + cors.split(',') + .map(|domain| { + // Parse each substring into a HeaderValue + // We print parsing errors to stderr, because this is an example :) + domain + .parse::() + .inspect_err(|e| eprintln!("Failed to parse domain {}: {}", domain, e)) + .wrap_err_with(|| format!("Invalid CORS domain: {}", domain)) + }) + .collect::, _>>() + .map(Into::into) +} + fn make_cors(cors: &str) -> eyre::Result { - let origins = match cors { - "*" => AllowOrigin::any(), - cors => { - ensure!( - !cors.split(',').any(|o| o == "*"), - "Wildcard '*' is not allowed in CORS domains" - ); - - cors.split(',') - .map(|domain| { - domain - .parse::() - .wrap_err_with(|| format!("Invalid CORS domain: {}", domain)) - }) - .collect::, _>>()? - .into() - } - }; + let origins = get_allowed(cors)?; Ok(CorsLayer::new() .allow_methods([Method::GET, Method::POST]) @@ -32,12 +56,8 @@ fn make_cors(cors: &str) -> eyre::Result { #[tokio::main] async fn main() -> eyre::Result<()> { let cors = std::env::args().nth(1).unwrap_or("*".to_string()); - ensure!(cors != "*", "Wildcard '*' is not allowed in CORS domains"); - let port = std::env::args() - .nth(2) - .and_then(|src| src.parse().ok()) - .unwrap_or(8080u16); + // Setting up an AJJ router is easy and fun! let router = ajj::Router::<()>::new() .route("helloWorld", || async { tracing::info!("serving hello world"); @@ -47,10 +67,13 @@ async fn main() -> eyre::Result<()> { tracing::info!("serving addNumbers"); Ok::<_, ()>(a + b) }) + // Convert to an axum router .into_axum("/") + // And then layer on your CORS settings .layer(make_cors(&cors)?); - let addr = SocketAddr::from(([127, 0, 0, 1], port)); + // Now we can serve the router on a TCP listener + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); let listener = tokio::net::TcpListener::bind(addr).await?; axum::serve(listener, router).into_future().await?;