From 0d99611155162ec2a6c584920ffc572fbeecd8ab Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Tue, 11 Jun 2024 20:05:19 +0100 Subject: [PATCH 1/2] Add adapter for Axum -> Vercel --- Cargo.lock | 106 ++++++++++++++++++++++++-- Cargo.toml | 1 + crates/vercel_axum/Cargo.toml | 23 ++++++ crates/vercel_axum/src/lib.rs | 90 ++++++++++++++++++++++ crates/vercel_runtime/Cargo.toml | 1 + crates/vercel_runtime/src/response.rs | 15 +--- 6 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 crates/vercel_axum/Cargo.toml create mode 100644 crates/vercel_axum/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fce4295..58eee37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,61 @@ dependencies = [ "serde_json", ] +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -604,9 +659,9 @@ dependencies = [ [[package]] name = "http-serde" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1133cafcce27ea69d35e56b3a8772e265633e04de73c5f4e1afdffc1d19b5419" +checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" dependencies = [ "http 1.1.0", "serde", @@ -660,6 +715,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -900,6 +956,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.2" @@ -1317,6 +1379,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "rvs_derive" version = "0.3.2" @@ -1387,18 +1455,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -1644,6 +1712,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "thiserror" version = "1.0.61" @@ -2009,6 +2089,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vercel_axum" +version = "1.1.2" +dependencies = [ + "axum", + "base64 0.22.1", + "http-body-util", + "serde_json", + "tower", + "tower-service", + "vercel_runtime 1.1.3", +] + [[package]] name = "vercel_runtime" version = "1.1.3" @@ -2016,6 +2109,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", + "http-serde", "lambda_http", "lambda_runtime", "serde", diff --git a/Cargo.toml b/Cargo.toml index 34fa8fe..7d7d1ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/vercel_runtime", "crates/vercel_runtime_macro", "crates/vercel_runtime_router", + "crates/vercel_axum", "examples/cron", "examples/nextjs", "examples/simple", diff --git a/crates/vercel_axum/Cargo.toml b/crates/vercel_axum/Cargo.toml new file mode 100644 index 0000000..329e8df --- /dev/null +++ b/crates/vercel_axum/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "vercel_axum" +version = "1.1.2" +edition = "2021" +authors = ["Vercel "] +description = "Vercel Rust Axum Adapter" +keywords = ["Vercel", "Rust", "Serverless", "Functions", "Axum"] +license = "MIT" +homepage = "https://github.com/vercel-community/rust" +repository = "https://github.com/vercel-community/rust" +documentation = "https://docs.rs/vercel_lambda" +include = ["src/*.rs", "Cargo.toml"] +exclude = ["tests/*"] + +[dependencies] +axum = "0.7" +base64 = "0.22" +http-body-util = "0.1" +tower = "0.4" +tower-service = "0.3" +serde_json = "1.0" +# vercel_runtime = "1.1.3" +vercel_runtime = { version = "1.1.3", path = "../vercel_runtime" } diff --git a/crates/vercel_axum/src/lib.rs b/crates/vercel_axum/src/lib.rs new file mode 100644 index 0000000..bd9603b --- /dev/null +++ b/crates/vercel_axum/src/lib.rs @@ -0,0 +1,90 @@ +use axum::response::IntoResponse; +use base64::prelude::*; +use http_body_util::BodyExt; +use std::{future::Future, pin::Pin}; +use tower::Layer; +use tower_service::Service; + +use vercel_runtime::request::{Event, VercelRequest}; +use vercel_runtime::response::EventResponse; + +#[derive(Clone, Copy)] +pub struct VercelLayer; + +impl Layer for VercelLayer { + type Service = VercelService; + + fn layer(&self, inner: S) -> Self::Service { + VercelService { inner } + } +} + +pub struct VercelService { + inner: S, +} + +impl Service> for VercelService +where + S: Service>, + S::Response: axum::response::IntoResponse + Send + 'static, + S::Error: std::error::Error + Send + Sync + 'static, + S::Future: Send + 'static, +{ + type Response = EventResponse; + type Error = vercel_runtime::Error; + type Future = + Pin> + Send + 'static>>; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.inner.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, event: Event) -> Self::Future { + let (event, _context) = event.into_parts(); + let request = serde_json::from_str::(&event.body).unwrap_or_default(); + + let mut builder = axum::http::request::Builder::new() + .method(request.method) + .uri(format!("https://{}{}", request.host, request.path)); + for (key, value) in request.headers { + if let Some(k) = key { + builder = builder.header(k, value); + } + } + + let request: axum::http::Request = match (request.body, request.encoding) + { + (Some(b), Some(encoding)) if encoding == "base64" => { + let engine = base64::prelude::BASE64_STANDARD; + let body = axum::body::Body::from(engine.decode(b.as_ref()).unwrap_or_default()); + builder.body(body).unwrap_or_default() + } + (Some(b), _) => builder.body(axum::body::Body::from(b)).unwrap_or_default(), + (None, _) => builder.body(axum::body::Body::default()).unwrap_or_default(), + }; + + let fut = self.inner.call(request); + let fut = async move { + let resp = fut.await?; + let (parts, body) = resp.into_response().into_parts(); + let bytes = body.into_data_stream().collect().await?.to_bytes(); + let bytes: &[u8] = &bytes; + let body = std::str::from_utf8(bytes).unwrap_or_default(); + let body: Option = match body { + "" => None, + _ => Some(body.into()), + }; + Ok(EventResponse { + status_code: parts.status.as_u16(), + body, + headers: parts.headers, + encoding: None, + }) + }; + + Box::pin(fut) + } +} diff --git a/crates/vercel_runtime/Cargo.toml b/crates/vercel_runtime/Cargo.toml index 6360c84..8d81a2d 100644 --- a/crates/vercel_runtime/Cargo.toml +++ b/crates/vercel_runtime/Cargo.toml @@ -26,6 +26,7 @@ serde = { version = "1.0.188", features = ["derive"] } serde_json = { version = "1.0.106", features = ["raw_value"] } tower-http = { version = "0.5", features = ["cors"] } tower-service = "0.3.2" +http-serde = "2.1.1" base64 = "0.22" bytes = "1.5.0" async-trait = "0.1.73" diff --git a/crates/vercel_runtime/src/response.rs b/crates/vercel_runtime/src/response.rs index 3669050..33af701 100644 --- a/crates/vercel_runtime/src/response.rs +++ b/crates/vercel_runtime/src/response.rs @@ -3,7 +3,6 @@ use lambda_http::http::{ Response, }; use lambda_http::Body; -use serde::ser::{Error as SerError, SerializeMap, Serializer}; use serde::Serialize; #[derive(Serialize, Debug)] @@ -12,7 +11,7 @@ pub struct EventResponse { pub status_code: u16, #[serde( skip_serializing_if = "HeaderMap::is_empty", - serialize_with = "serialize_headers" + with = "http_serde::header_map" )] pub headers: HeaderMap, #[serde(skip_serializing_if = "Option::is_none")] @@ -32,18 +31,6 @@ impl Default for EventResponse { } } -fn serialize_headers(headers: &HeaderMap, serializer: S) -> Result -where - S: Serializer, -{ - let mut map = serializer.serialize_map(Some(headers.keys_len()))?; - for key in headers.keys() { - let map_value = headers[key].to_str().map_err(S::Error::custom)?; - map.serialize_entry(key.as_str(), map_value)?; - } - map.end() -} - impl From> for EventResponse where T: Into, From d2e0591c4b3080443713cece8e963fac6edf7959 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Wed, 12 Jun 2024 09:11:28 +0100 Subject: [PATCH 2/2] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Florentin / 珞辰 --- crates/vercel_axum/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/vercel_axum/Cargo.toml b/crates/vercel_axum/Cargo.toml index 329e8df..665fbaf 100644 --- a/crates/vercel_axum/Cargo.toml +++ b/crates/vercel_axum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vercel_axum" -version = "1.1.2" +version = "1.1.3" edition = "2021" authors = ["Vercel "] description = "Vercel Rust Axum Adapter" @@ -19,5 +19,5 @@ http-body-util = "0.1" tower = "0.4" tower-service = "0.3" serde_json = "1.0" -# vercel_runtime = "1.1.3" -vercel_runtime = { version = "1.1.3", path = "../vercel_runtime" } +vercel_runtime = "1.1.3" +# vercel_runtime = { version = "1.1.3", path = "../vercel_runtime" }