Commit 7e51d088 authored by Nugine's avatar Nugine
Browse files

feat(s3s/examples): axum for custom route

parent 5c8df8ce
Loading
Loading
Loading
Loading
+83 −2
Original line number Diff line number Diff line
@@ -480,6 +480,60 @@ dependencies = [
 "tracing",
]

[[package]]
name = "axum"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
dependencies = [
 "axum-core",
 "bytes",
 "form_urlencoded",
 "futures-util",
 "http 1.2.0",
 "http-body 1.0.1",
 "http-body-util",
 "hyper 1.6.0",
 "hyper-util",
 "itoa",
 "matchit",
 "memchr",
 "mime",
 "percent-encoding",
 "pin-project-lite",
 "rustversion",
 "serde",
 "serde_json",
 "serde_path_to_error",
 "serde_urlencoded",
 "sync_wrapper",
 "tokio",
 "tower",
 "tower-layer",
 "tower-service",
 "tracing",
]

[[package]]
name = "axum-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
dependencies = [
 "bytes",
 "futures-util",
 "http 1.2.0",
 "http-body 1.0.1",
 "http-body-util",
 "mime",
 "pin-project-lite",
 "rustversion",
 "sync_wrapper",
 "tower-layer",
 "tower-service",
 "tracing",
]

[[package]]
name = "backtrace"
version = "0.3.74"
@@ -1335,6 +1389,7 @@ dependencies = [
 "hyper 1.6.0",
 "pin-project-lite",
 "tokio",
 "tower-service",
]

[[package]]
@@ -1584,6 +1639,12 @@ dependencies = [
 "regex-automata 0.1.10",
]

[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"

[[package]]
name = "md-5"
version = "0.10.6"
@@ -2052,11 +2113,13 @@ dependencies = [
 "arrayvec",
 "async-trait",
 "atoi",
 "axum",
 "base64-simd",
 "block-buffer 0.11.0-rc.3",
 "bytes",
 "bytestring",
 "chrono",
 "const-str",
 "crc32c",
 "crc32fast",
 "crc64fast-nvme",
@@ -2078,6 +2141,7 @@ dependencies = [
 "pin-project-lite",
 "quick-xml",
 "serde",
 "serde_json",
 "serde_urlencoded",
 "sha1 0.11.0-pre.4",
 "sha2 0.11.0-pre.4",
@@ -2329,9 +2393,9 @@ dependencies = [

[[package]]
name = "serde_json"
version = "1.0.139"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
 "indexmap",
 "itoa",
@@ -2340,6 +2404,16 @@ dependencies = [
 "serde",
]

[[package]]
name = "serde_path_to_error"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
dependencies = [
 "itoa",
 "serde",
]

[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -2656,8 +2730,14 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
 "futures-core",
 "futures-util",
 "pin-project-lite",
 "sync_wrapper",
 "tokio",
 "tower-layer",
 "tower-service",
 "tracing",
]

[[package]]
@@ -2678,6 +2758,7 @@ version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
 "log",
 "pin-project-lite",
 "tracing-attributes",
 "tracing-core",
+3 −0
Original line number Diff line number Diff line
@@ -67,7 +67,10 @@ std-next = "0.1.1"
sync_wrapper = { version = "1.0.1", default-features = false }
tokio = { version = "1.40.0", features = ["time"] }
crc64fast-nvme = "1.2.0"
const-str = "0.6.2"

[dev-dependencies]
axum = "0.8.1"
serde_json = "1.0.140"
tokio = { version = "1.40.0", features = ["full"] }
tokio-util = { version = "0.7.13", features = ["io"] }
+125 −0
Original line number Diff line number Diff line
use s3s::route::S3Route;
use s3s::{Body, S3Request, S3Response, S3Result};

use axum::http;
use http::{Extensions, HeaderMap, Method, StatusCode, Uri};
use tower::Service;

pub struct CustomRoute {
    router: axum::Router,
}

impl CustomRoute {
    #[must_use]
    pub fn build() -> Self {
        Self {
            router: self::handlers::register(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Extra {
    pub credentials: Option<s3s::auth::Credentials>,
    pub region: Option<String>,
    pub service: Option<String>,
}

fn convert_request(req: S3Request<Body>) -> http::Request<Body> {
    let (mut parts, _) = http::Request::new(Body::empty()).into_parts();
    parts.method = req.method;
    parts.uri = req.uri;
    parts.headers = req.headers;
    parts.extensions = req.extensions;
    parts.extensions.insert(Extra {
        credentials: req.credentials,
        region: req.region,
        service: req.service,
    });
    http::Request::from_parts(parts, req.input)
}

fn convert_response(resp: http::Response<axum::body::Body>) -> S3Response<(StatusCode, Body)> {
    let (parts, body) = resp.into_parts();
    let mut s3_resp = S3Response::new((parts.status, Body::http_body_unsync(body)));
    s3_resp.headers = parts.headers;
    s3_resp.extensions = parts.extensions;
    s3_resp
}

#[async_trait::async_trait]
impl S3Route for CustomRoute {
    fn is_match(&self, _method: &Method, uri: &Uri, _headers: &HeaderMap, _extensions: &mut Extensions) -> bool {
        let path = uri.path();
        let prefix = const_str::concat!(self::handlers::PREFIX, "/");
        path.starts_with(prefix)
    }

    async fn check_access(&self, req: &mut S3Request<Body>) -> S3Result<()> {
        if req.credentials.is_none() {
            tracing::debug!("anonymous access");
        }
        Ok(()) // allow all requests
    }

    async fn call(&self, req: S3Request<Body>) -> S3Result<S3Response<(StatusCode, Body)>> {
        let mut service = self.router.clone().into_service::<Body>();
        let req = convert_request(req);
        let result = service.call(req).await;
        match result {
            Ok(resp) => Ok(convert_response(resp)),
            Err(e) => match e {},
        }
    }
}

mod handlers {
    use std::collections::HashMap;

    use axum::Json;
    use axum::Router;
    use axum::body::Body;
    use axum::extract::Path;
    use axum::extract::Query;
    use axum::extract::Request;
    use axum::http::Response;
    use axum::response;
    use axum::routing::get;
    use axum::routing::post;

    pub async fn echo(req: Request) -> Response<Body> {
        Response::new(req.into_body())
    }

    pub async fn hello() -> &'static str {
        "Hello, World!"
    }

    pub async fn show_path(Path(path): Path<String>) -> String {
        path
    }

    pub async fn show_query(Query(query): Query<HashMap<String, String>>) -> String {
        format!("{query:?}")
    }

    pub async fn show_json(Json(json): Json<serde_json::Value>) -> response::Json<serde_json::Value> {
        tracing::debug!(?json);
        response::Json(json)
    }

    pub const PREFIX: &str = "/custom";

    pub fn register() -> Router {
        let router = Router::new()
            .route("/echo", post(echo))
            .route("/hello", get(hello))
            .route("/show_path/{*path}", get(show_path))
            .route("/show_query", get(show_query))
            .route("/show_json", post(show_json));

        Router::new().nest(PREFIX, router)
    }
}

fn main() {}
+2 −0
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@ use hyper::StatusCode;
use hyper::Uri;
use hyper::http::Extensions;

// TODO: Refactor S3Request and S3Response to support custom route better

#[async_trait::async_trait]
pub trait S3Route: Send + Sync + 'static {
    fn is_match(&self, method: &Method, uri: &Uri, headers: &HeaderMap, extensions: &mut Extensions) -> bool;