From ab046e172713f0e05c642b01b0bbb2e003349d4f Mon Sep 17 00:00:00 2001 From: Stephen Belanger Date: Mon, 30 Jun 2025 15:21:21 +0800 Subject: [PATCH] feat: handle should be async --- Cargo.lock | 207 +++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 4 + src/handler.rs | 94 ++++++++++++---------- 3 files changed, 251 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86596f4..3a91878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,47 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + [[package]] name = "bitflags" version = "2.9.1" @@ -66,6 +107,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "http" version = "1.3.1" @@ -81,11 +128,13 @@ dependencies = [ name = "http-handler" version = "1.0.0" dependencies = [ + "async-trait", "bytes", "http", "napi", "napi-build", "napi-derive", + "tokio", ] [[package]] @@ -94,6 +143,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + [[package]] name = "libloading" version = "0.8.8" @@ -101,14 +156,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.53.2", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", ] [[package]] name = "napi" -version = "3.0.0-beta.8" +version = "3.0.0-beta.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c502f122fc89e92c6222810b3144411c6f945da5aa3b713ddfad3bdcae7c9bb4" +checksum = "e8a8bf588b2ea96bdf618cac8a81b2b6cb5e0f0f86a1ac4ac62859ab78fd79a8" dependencies = [ "bitflags", "ctor", @@ -126,9 +196,9 @@ checksum = "44e0e3177307063d3e7e55b7dd7b648cca9d7f46daa35422c0d98cc2bf48c2c1" [[package]] name = "napi-derive" -version = "3.0.0-beta.8" +version = "3.0.0-beta.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf1e732a67e934b069d6d527251d6288753a36840572abe132a7aed9e77f0bc" +checksum = "0cf56acb0b78c92cba806a559cfe62513f53cc4a7947807e2ff3c4ef865e9b3a" dependencies = [ "convert_case", "ctor", @@ -140,9 +210,9 @@ dependencies = [ [[package]] name = "napi-derive-backend" -version = "2.0.0-beta.8" +version = "2.0.0-beta.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462b775ba74791c98989fadc46c4bb2ec53016427be4d420d31c4bbaab34b308" +checksum = "6862700f1bdfe43767dc2fd3577306cf6342431f078b765c8336454dda382f1d" dependencies = [ "convert_case", "proc-macro2", @@ -166,6 +236,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -184,6 +269,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -207,6 +298,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -219,64 +332,128 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.0" diff --git a/Cargo.toml b/Cargo.toml index 286884c..c6946c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ napi-support = ["dep:napi", "dep:napi-derive", "dep:napi-build"] napi-build = { version = "2.2.1", optional = true } [dependencies] +async-trait = "0.1.88" bytes = "1.10.1" http = "1.0" # http-body = "1.0" @@ -32,3 +33,6 @@ napi = { version = "3.0.0-beta.8", features = ["napi4"], optional = true } napi-derive = { version = "3.0.0-beta.8", optional = true } # napi = { version = "2.12.2", default-features = false, features = ["napi4"], optional = true } # napi-derive = { version = "2.12.2", optional = true } + +[dev-dependencies] +tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros"] } diff --git a/src/handler.rs b/src/handler.rs index 3cb427d..527a3e1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -14,10 +14,11 @@ //! //! struct HelloHandler; //! +//! #[async_trait::async_trait] //! impl Handler for HelloHandler { //! type Error = std::convert::Infallible; //! -//! fn handle(&self, _request: http::Request) -> Result, Self::Error> { +//! async fn handle(&self, _request: http::Request) -> Result, Self::Error> { //! Ok(http::Response::builder() //! .status(200) //! .header("Content-Type", "text/plain") @@ -40,14 +41,16 @@ //! header_value: &'static str, //! } //! +//! #[async_trait::async_trait] //! impl Handler for AddHeaderHandler //! where -//! H: Handler, +//! H: Handler + std::marker::Sync, +//! for<'async_trait> B: std::marker::Send + 'async_trait //! { //! type Error = H::Error; //! -//! fn handle(&self, request: http::Request) -> Result, Self::Error> { -//! let mut response = self.inner.handle(request)?; +//! async fn handle(&self, request: http::Request) -> Result, Self::Error> { +//! let mut response = self.inner.handle(request).await?; //! response.headers_mut().insert( //! self.header_name, //! self.header_value.parse().unwrap() @@ -58,9 +61,11 @@ //! //! // Usage //! struct ApiHandler; +//! +//! #[async_trait::async_trait] //! impl Handler for ApiHandler { //! type Error = std::convert::Infallible; -//! fn handle(&self, _req: http::Request) -> Result, Self::Error> { +//! async fn handle(&self, _req: http::Request) -> Result, Self::Error> { //! Ok(http::Response::builder() //! .status(200) //! .body(BytesMut::from(r#"{"status": "ok"}"#)) @@ -96,10 +101,11 @@ use bytes::BytesMut; /// /// struct MyHandler; /// +/// #[async_trait::async_trait] /// impl Handler for MyHandler { /// type Error = std::convert::Infallible; /// -/// fn handle(&self, request: http::Request) -> Result, Self::Error> { +/// async fn handle(&self, request: http::Request) -> Result, Self::Error> { /// Ok(http::Response::builder() /// .status(200) /// .body(BytesMut::from("Hello, World!")) @@ -116,10 +122,11 @@ use bytes::BytesMut; /// /// struct StringHandler; /// +/// #[async_trait::async_trait] /// impl Handler for StringHandler { /// type Error = std::convert::Infallible; /// -/// fn handle(&self, request: http::Request) -> Result, Self::Error> { +/// async fn handle(&self, request: http::Request) -> Result, Self::Error> { /// let body = request.body(); /// let response_body = format!("You sent: {}", body); /// @@ -130,12 +137,13 @@ use bytes::BytesMut; /// } /// } /// ``` +#[async_trait::async_trait] pub trait Handler { /// The error type returned by the handler type Error; /// Handle an HTTP request and produce a response - fn handle(&self, request: http::Request) -> Result, Self::Error>; + async fn handle(&self, request: http::Request) -> Result, Self::Error>; } #[cfg(test)] @@ -149,10 +157,11 @@ mod tests { /// Example handler that echoes the request body pub struct EchoHandler; + #[async_trait::async_trait] impl Handler for EchoHandler { type Error = http::Error; - fn handle( + async fn handle( &self, request: http::Request, ) -> Result, Self::Error> { @@ -162,15 +171,15 @@ mod tests { } } - #[test] - fn test_echo_handler() { + #[tokio::test] + async fn test_echo_handler() { let handler = EchoHandler; let request = http::Request::builder() .uri("/echo") .body(Bytes::from("Hello, world!")) .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.status(), 200); assert_eq!(response.body(), &Bytes::from("Hello, world!")); } @@ -178,10 +187,11 @@ mod tests { /// Test handler that adds logging struct LoggingHandler; + #[async_trait::async_trait] impl Handler for LoggingHandler { type Error = String; - fn handle( + async fn handle( &self, request: http::Request, ) -> Result, Self::Error> { @@ -199,8 +209,8 @@ mod tests { } } - #[test] - fn test_logging_handler() { + #[tokio::test] + async fn test_logging_handler() { let handler = LoggingHandler; let request = http::Request::builder() .method("POST") @@ -208,7 +218,7 @@ mod tests { .body(Bytes::new()) .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.status(), 200); assert_eq!(response.body(), &Bytes::from("OK")); @@ -219,10 +229,11 @@ mod tests { /// Test handler that uses socket info struct SocketAwareHandler; + #[async_trait::async_trait] impl Handler for SocketAwareHandler { type Error = String; - fn handle( + async fn handle( &self, request: http::Request, ) -> Result, Self::Error> { @@ -242,8 +253,8 @@ mod tests { } } - #[test] - fn test_socket_aware_handler() { + #[tokio::test] + async fn test_socket_aware_handler() { let handler = SocketAwareHandler; // Test without socket info @@ -252,7 +263,7 @@ mod tests { .body(Bytes::new()) .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.body(), &Bytes::from("No socket info")); // Test with socket info @@ -265,7 +276,7 @@ mod tests { let remote = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 5000); request.set_socket_info(SocketInfo::new(Some(local), Some(remote))); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); let body_str = std::str::from_utf8(response.body()).unwrap(); assert!(body_str.contains("127.0.0.1:8080")); assert!(body_str.contains("192.168.1.1:5000")); @@ -274,10 +285,11 @@ mod tests { /// Test handler that returns errors struct ErrorHandler; + #[async_trait::async_trait] impl Handler for ErrorHandler { type Error = String; - fn handle( + async fn handle( &self, _request: http::Request, ) -> Result, Self::Error> { @@ -285,15 +297,15 @@ mod tests { } } - #[test] - fn test_error_handler() { + #[tokio::test] + async fn test_error_handler() { let handler = ErrorHandler; let request = http::Request::builder() .uri("/error") .body(Bytes::new()) .unwrap(); - let result = handler.handle(request); + let result = handler.handle(request).await; assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Something went wrong"); } @@ -301,10 +313,11 @@ mod tests { /// Test handler that sets an exception struct ExceptionHandler; + #[async_trait::async_trait] impl Handler for ExceptionHandler { type Error = std::convert::Infallible; - fn handle( + async fn handle( &self, _request: http::Request, ) -> Result, Self::Error> { @@ -319,15 +332,15 @@ mod tests { } } - #[test] - fn test_exception_handler() { + #[tokio::test] + async fn test_exception_handler() { let handler = ExceptionHandler; let request = http::Request::builder() .uri("/fail") .body(Bytes::new()) .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.status(), 500); assert_eq!(response.body(), &Bytes::from("Internal Server Error")); @@ -338,10 +351,11 @@ mod tests { /// Test handler that works with String bodies struct StringBodyHandler; + #[async_trait::async_trait] impl Handler for StringBodyHandler { type Error = std::convert::Infallible; - fn handle( + async fn handle( &self, request: http::Request, ) -> Result, Self::Error> { @@ -355,15 +369,15 @@ mod tests { } } - #[test] - fn test_string_body_handler() { + #[tokio::test] + async fn test_string_body_handler() { let handler = StringBodyHandler; let request = http::Request::builder() .uri("/string") .body("hello world".to_string()) .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.status(), 200); assert_eq!(response.body(), &Bytes::from("Received: HELLO WORLD")); } @@ -416,10 +430,11 @@ mod tests { /// Generic echo handler that works with any cloneable body type pub struct GenericEchoHandler; + #[async_trait::async_trait] impl Handler for GenericEchoHandler { type Error = http::Error; - fn handle( + async fn handle( &self, request: http::Request, ) -> Result, Self::Error> { @@ -429,10 +444,11 @@ mod tests { } } + #[async_trait::async_trait] impl Handler> for GenericEchoHandler { type Error = http::Error; - fn handle( + async fn handle( &self, request: http::Request>, ) -> Result>, Self::Error> { @@ -442,8 +458,8 @@ mod tests { } } - #[test] - fn test_generic_echo_handler() { + #[tokio::test] + async fn test_generic_echo_handler() { let handler = GenericEchoHandler; // Test with Bytes @@ -452,7 +468,7 @@ mod tests { .body(Bytes::from("echo bytes")) .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.body(), &Bytes::from("echo bytes")); // Test with Vec @@ -461,7 +477,7 @@ mod tests { .body(vec![72, 101, 108, 108, 111]) // "Hello" in ASCII .unwrap(); - let response = handler.handle(request).unwrap(); + let response = handler.handle(request).await.unwrap(); assert_eq!(response.body(), &Bytes::from("Hello")); } }