├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── boluo-core ├── CHANGELOG.md ├── Cargo.toml ├── README.md └── src │ ├── body.rs │ ├── extract.rs │ ├── handler.rs │ ├── lib.rs │ ├── middleware │ ├── around.rs │ ├── middleware_fn.rs │ └── mod.rs │ ├── request.rs │ ├── response │ ├── into_response.rs │ └── mod.rs │ ├── service │ ├── and_then.rs │ ├── boxed.rs │ ├── ext.rs │ ├── map_err.rs │ ├── map_request.rs │ ├── map_response.rs │ ├── map_result.rs │ ├── mod.rs │ ├── or_else.rs │ ├── service_fn.rs │ └── then.rs │ ├── upgrade.rs │ └── util.rs ├── boluo-macros ├── CHANGELOG.md ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ └── route.rs ├── boluo ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── benches │ └── bench.rs ├── doc │ └── route │ │ ├── route.md │ │ └── scope.md └── src │ ├── data.rs │ ├── extract │ ├── extension.rs │ ├── form.rs │ ├── header.rs │ ├── json.rs │ ├── mod.rs │ ├── path │ │ ├── de.rs │ │ └── mod.rs │ └── query.rs │ ├── lib.rs │ ├── listener.rs │ ├── middleware │ ├── extension.rs │ └── mod.rs │ ├── multipart.rs │ ├── response │ ├── extension.rs │ ├── form.rs │ ├── html.rs │ ├── json.rs │ ├── mod.rs │ ├── redirect.rs │ └── sse │ │ ├── event.rs │ │ ├── keep_alive.rs │ │ └── mod.rs │ ├── route │ ├── error.rs │ ├── method.rs │ ├── mod.rs │ ├── params.rs │ └── router.rs │ ├── server │ ├── compat.rs │ ├── graceful_shutdown.rs │ └── mod.rs │ ├── static_file.rs │ └── ws │ ├── message.rs │ ├── mod.rs │ └── util.rs └── examples ├── .gitignore ├── Cargo.toml ├── README.md ├── compat-tower ├── Cargo.toml └── src │ ├── compat.rs │ └── main.rs ├── custom-listener ├── .gitignore ├── Cargo.toml ├── http │ ├── 1 │ └── 2 └── src │ ├── listener.rs │ └── main.rs ├── custom-middleware ├── Cargo.toml └── src │ └── main.rs ├── extract-path ├── Cargo.toml └── src │ └── main.rs ├── graceful-shutdown ├── Cargo.toml └── src │ └── main.rs ├── handle-error ├── Cargo.toml └── src │ └── main.rs ├── hello ├── Cargo.toml └── src │ └── main.rs ├── log ├── Cargo.toml └── src │ └── main.rs ├── route ├── Cargo.toml └── src │ └── main.rs ├── sse ├── Cargo.toml └── src │ └── main.rs ├── state ├── Cargo.toml └── src │ └── main.rs ├── static-file ├── Cargo.toml ├── assets │ ├── hello.txt │ └── index.html └── src │ └── main.rs ├── tls ├── Cargo.toml ├── cert │ ├── cert.pem │ └── key.pem └── src │ ├── main.rs │ └── tls_listener.rs └── ws ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = ["boluo", "boluo-*"] 4 | 5 | [workspace.package] 6 | edition = "2024" 7 | license = "MIT" 8 | homepage = "https://github.com/dakai-chen/boluo" 9 | repository = "https://github.com/dakai-chen/boluo" 10 | rust-version = "1.85" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 chen-dk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | boluo 3 |

4 | 5 |

6 | 简单易用的高性能异步网络框架 7 |

8 | 9 | ## 介绍 10 | 11 | `boluo` 是一个基于 `tokio` 和 `hyper` 开发的轻量级路由层,几乎没有额外的性能开销,拥有极快的运行速度。 12 | 13 | ## 特点 14 | 15 | - 简单清晰的路由定义,支持嵌套路由、宏定义路由和路由合并。 16 | - 提供核心特征 `Service` 和 `Middleware`,灵活且易于扩展。 17 | - 提供集中化的错误处理和错误传播机制。 18 | 19 | ## 可选功能 20 | 21 | | 功能名 | 描述 | 默认启用 | 22 | | ----------- | ------------------------------------- | -------- | 23 | | http1 | 启用HTTP1服务器 | 是 | 24 | | http2 | 启用HTTP2服务器 | | 25 | | multipart | 添加对 `multipart/form-data` 格式的支持 | | 26 | | sse | 添加对服务器发送事件的支持 | | 27 | | ws | 添加对网络套接字的支持 | | 28 | | static-file | 添加对静态文件的支持 | | 29 | 30 | ## 快速开始 31 | 32 | 新建项目: 33 | 34 | ```bash 35 | cargo new demo && cd demo 36 | ``` 37 | 38 | 添加依赖: 39 | 40 | ```toml 41 | [dependencies] 42 | boluo = "0.7" 43 | tokio = { version = "1", features = ["full"] } 44 | ``` 45 | 46 | 用以下内容覆盖 `src/main.rs`: 47 | 48 | ```rust 49 | use boluo::response::IntoResponse; 50 | use boluo::route::Router; 51 | use boluo::server::Server; 52 | use tokio::net::TcpListener; 53 | 54 | #[tokio::main] 55 | async fn main() { 56 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 57 | 58 | let app = Router::new().mount(hello); 59 | 60 | Server::new(listener).run(app).await.unwrap(); 61 | } 62 | 63 | #[boluo::route("/", method = "GET")] 64 | async fn hello() -> impl IntoResponse { 65 | "Hello, World!" 66 | } 67 | ``` 68 | 69 | 运行项目: 70 | 71 | ```bash 72 | cargo run 73 | ``` 74 | 75 | 访问服务: 76 | 77 | ```bash 78 | curl http://127.0.0.1:3000/ 79 | ``` 80 | 81 | ## 更多示例 82 | 83 | [在这里](./examples/)可以找到更多的示例代码。在示例目录中,你可以通过以下命令运行示例: 84 | 85 | ```bash 86 | cargo run --bin hello 87 | ``` 88 | 89 | ## 支持的最低 Rust 版本(MSRV) 90 | 91 | 支持的最低 Rust 版本为 `1.85.0`。 92 | -------------------------------------------------------------------------------- /boluo-core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # unreleased 2 | 3 | # 0.6.0 4 | 5 | ## 破坏 6 | 7 | - 重命名 `Body::from_stream` 为 `Body::from_data_stream`。 8 | - 将 `simple_middleware_fn_with_state` 和 `simple_middleware_fn` 分别重命名为 `around_with_state_fn` 和 `around_fn`,并重构相关代码。 9 | 10 | # 0.5.2 11 | 12 | ## 新增 13 | 14 | - 新增方法 `Body::to_bytes`。 15 | 16 | ## 变化 17 | 18 | - 使用 `Body::to_bytes` 实现 `Bytes` 的 `FromRequest`。 19 | 20 | # 0.5.1 21 | 22 | ## 修复 23 | 24 | - 修复 `Upgraded::downcast` 无法向下转型。 25 | 26 | # 0.5.0 27 | 28 | ## 破坏 29 | 30 | - 迁移到 rust 2024 (1.85.0) 版本。 31 | - 使用 `ServiceExt::with` 挂载中间件,结果必须是一个 `Service`。 32 | 33 | ## 新增 34 | 35 | - 新增模块 upgrade。 36 | 37 | # 0.4.1 38 | 39 | ## 新增 40 | 41 | - 添加 `simple_middleware_fn` 和 `simple_middleware_fn_with_state` 函数。 42 | 43 | # 0.4.0 44 | 45 | ## 破坏 46 | 47 | - 修改 `Option` 的 `FromRequest` 实现。 48 | 49 | ## 新增 50 | 51 | - 添加 `OptionalFromRequest` 特征。 52 | - 添加 `ExtractResult`,简化 `Result` 提取器的书写。 53 | 54 | # 0.3.0 55 | 56 | ## 破坏 57 | 58 | - 删除特征 `boluo_core::extract::Name`。 59 | - 删除宏 `boluo_core::name`。 60 | 61 | # 0.2.1 62 | 63 | ## 新增 64 | 65 | - 允许处理程序参数末尾提取 `Request`。 66 | 67 | # 0.2.0 68 | 69 | ## 破坏 70 | 71 | - 重命名 `IntoHeaderError` 为 `HeaderResponseError`,并重构其实现。 72 | - 修改 `ServiceExt` 特征,对除了 `with` 以外的函数添加更多约束,保证函数返回的对象是 `Service`。 73 | 74 | ## 变化 75 | 76 | - 移除实现 `Service` 的多余约束:`Then`,`AndThen`,`OrElse`,`MapResult`,`MapResponse`,`MapErr`,`MapRequest`,`ServiceFn`,`BoxService`,`BoxCloneService`,`ArcService`。 77 | 78 | # 0.1.1 79 | 80 | ## 新增 81 | 82 | - 添加文档。 83 | 84 | # 0.1.0 85 | 86 | - 初始发布 -------------------------------------------------------------------------------- /boluo-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boluo-core" 3 | version = "0.6.0" 4 | edition = { workspace = true } 5 | license = { workspace = true } 6 | homepage = { workspace = true } 7 | repository = { workspace = true } 8 | rust-version = { workspace = true } 9 | readme = "README.md" 10 | description = "boluo的核心类型和特征" 11 | keywords = ["http", "web", "framework", "async"] 12 | categories = ["asynchronous", "network-programming", "web-programming"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | http = "1" 18 | http-body = "1" 19 | http-body-util = "0.1.0" 20 | mime = "0.3" 21 | bytes = "1" 22 | sync_wrapper = "1" 23 | futures-core = "0.3" 24 | futures-io = "0.3" 25 | pin-project-lite = "0.2" 26 | 27 | [package.metadata.docs.rs] 28 | all-features = true 29 | rustdoc-args = ["--cfg", "docsrs"] 30 | -------------------------------------------------------------------------------- /boluo-core/README.md: -------------------------------------------------------------------------------- 1 |

2 | boluo-core 3 |

4 | 5 | `boluo` 的核心类型和特征。 6 | 7 | ## 支持的最低 Rust 版本(MSRV) 8 | 9 | 支持的最低 Rust 版本为 `1.85.0`。 10 | -------------------------------------------------------------------------------- /boluo-core/src/body.rs: -------------------------------------------------------------------------------- 1 | //! HTTP 主体。 2 | 3 | pub use bytes::Bytes; 4 | pub use http_body::{Body as HttpBody, Frame, SizeHint}; 5 | 6 | use std::borrow::Cow; 7 | use std::pin::Pin; 8 | use std::task::{Context, Poll}; 9 | 10 | use futures_core::{Stream, TryStream}; 11 | use http_body_util::{BodyExt, Empty, Full}; 12 | 13 | use crate::BoxError; 14 | 15 | type BoxBody = http_body_util::combinators::UnsyncBoxBody; 16 | 17 | fn boxed(body: B) -> BoxBody 18 | where 19 | B: HttpBody + Send + 'static, 20 | B::Error: Into, 21 | { 22 | crate::util::__try_downcast(body).unwrap_or_else(|body| body.map_err(Into::into).boxed_unsync()) 23 | } 24 | 25 | /// 请求和响应的主体类型。 26 | #[derive(Debug)] 27 | pub struct Body(BoxBody); 28 | 29 | impl Body { 30 | /// 使用给定的 [`HttpBody`] 对象,创建一个新的 [`Body`]。 31 | pub fn new(body: B) -> Self 32 | where 33 | B: HttpBody + Send + 'static, 34 | B::Error: Into, 35 | { 36 | crate::util::__try_downcast(body).unwrap_or_else(|body| Self(boxed(body))) 37 | } 38 | 39 | /// 创建一个空的 [`Body`]。 40 | pub fn empty() -> Self { 41 | Self::new(Empty::new()) 42 | } 43 | 44 | /// 从 [`Stream`] 中创建一个新的 [`Body`]。 45 | pub fn from_data_stream(stream: S) -> Self 46 | where 47 | S: TryStream + Send + 'static, 48 | S::Ok: Into, 49 | S::Error: Into, 50 | { 51 | Self::new(StreamBody { stream }) 52 | } 53 | 54 | /// 将 [`Body`] 的数据帧转换为 [`Stream`],非数据帧的部分将被丢弃。 55 | pub fn into_data_stream(self) -> BodyDataStream { 56 | BodyDataStream { inner: self } 57 | } 58 | 59 | /// 消耗此 [`Body`] 对象,将其所有数据收集并合并为单个 [`Bytes`] 缓冲区。 60 | pub async fn to_bytes(self) -> Result { 61 | self.collect().await.map(|col| col.to_bytes()) 62 | } 63 | } 64 | 65 | impl Default for Body { 66 | fn default() -> Self { 67 | Self::empty() 68 | } 69 | } 70 | 71 | impl From<()> for Body { 72 | fn from(_: ()) -> Self { 73 | Self::empty() 74 | } 75 | } 76 | 77 | macro_rules! body_from_impl { 78 | ($ty:ty) => { 79 | impl From<$ty> for Body { 80 | fn from(buf: $ty) -> Self { 81 | Self::new(Full::from(buf)) 82 | } 83 | } 84 | }; 85 | } 86 | 87 | body_from_impl!(&'static [u8]); 88 | body_from_impl!(Cow<'static, [u8]>); 89 | body_from_impl!(Vec); 90 | 91 | body_from_impl!(&'static str); 92 | body_from_impl!(Cow<'static, str>); 93 | body_from_impl!(String); 94 | 95 | body_from_impl!(Bytes); 96 | 97 | impl HttpBody for Body { 98 | type Data = Bytes; 99 | type Error = BoxError; 100 | 101 | #[inline] 102 | fn poll_frame( 103 | mut self: Pin<&mut Self>, 104 | cx: &mut Context<'_>, 105 | ) -> Poll, Self::Error>>> { 106 | Pin::new(&mut self.0).poll_frame(cx) 107 | } 108 | 109 | #[inline] 110 | fn size_hint(&self) -> SizeHint { 111 | self.0.size_hint() 112 | } 113 | 114 | #[inline] 115 | fn is_end_stream(&self) -> bool { 116 | self.0.is_end_stream() 117 | } 118 | } 119 | 120 | /// [`Body`] 的数据帧流。 121 | #[derive(Debug)] 122 | pub struct BodyDataStream { 123 | inner: Body, 124 | } 125 | 126 | impl Stream for BodyDataStream { 127 | type Item = Result; 128 | 129 | #[inline] 130 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 131 | loop { 132 | match futures_core::ready!(Pin::new(&mut self.inner).poll_frame(cx)?) { 133 | Some(frame) => match frame.into_data() { 134 | Ok(data) => return Poll::Ready(Some(Ok(data))), 135 | Err(_frame) => {} 136 | }, 137 | None => return Poll::Ready(None), 138 | } 139 | } 140 | } 141 | } 142 | 143 | impl HttpBody for BodyDataStream { 144 | type Data = Bytes; 145 | type Error = BoxError; 146 | 147 | #[inline] 148 | fn poll_frame( 149 | mut self: Pin<&mut Self>, 150 | cx: &mut Context<'_>, 151 | ) -> Poll, Self::Error>>> { 152 | Pin::new(&mut self.inner).poll_frame(cx) 153 | } 154 | 155 | #[inline] 156 | fn is_end_stream(&self) -> bool { 157 | self.inner.is_end_stream() 158 | } 159 | 160 | #[inline] 161 | fn size_hint(&self) -> SizeHint { 162 | self.inner.size_hint() 163 | } 164 | } 165 | 166 | pin_project_lite::pin_project! { 167 | struct StreamBody { 168 | #[pin] 169 | stream: S, 170 | } 171 | } 172 | 173 | impl HttpBody for StreamBody 174 | where 175 | S: TryStream, 176 | S::Ok: Into, 177 | S::Error: Into, 178 | { 179 | type Data = Bytes; 180 | type Error = BoxError; 181 | 182 | fn poll_frame( 183 | self: Pin<&mut Self>, 184 | cx: &mut Context<'_>, 185 | ) -> Poll, Self::Error>>> { 186 | let this = self.project(); 187 | match futures_core::ready!(this.stream.try_poll_next(cx)) { 188 | Some(Ok(chunk)) => Poll::Ready(Some(Ok(Frame::data(chunk.into())))), 189 | Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), 190 | None => Poll::Ready(None), 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /boluo-core/src/extract.rs: -------------------------------------------------------------------------------- 1 | //! 从请求中提取数据的类型和特征。 2 | 3 | use std::convert::Infallible; 4 | 5 | use http::{Extensions, HeaderMap, Method, Uri, Version}; 6 | 7 | use crate::BoxError; 8 | use crate::body::{Body, Bytes}; 9 | use crate::request::{Request, RequestParts}; 10 | 11 | /// 可以根据 [`Request`] 创建的类型,用于实现提取器。 12 | /// 13 | /// # 例子 14 | /// 15 | /// ``` 16 | /// use std::convert::Infallible; 17 | /// 18 | /// use boluo_core::extract::FromRequest; 19 | /// use boluo_core::http::{header, HeaderValue}; 20 | /// use boluo_core::request::Request; 21 | /// 22 | /// // 从请求头中提取 HOST 的提取器。 23 | /// struct Host(Option); 24 | /// 25 | /// // 为提取器实现 `FromRequest` 特征。 26 | /// impl FromRequest for Host { 27 | /// type Error = Infallible; 28 | /// 29 | /// async fn from_request(req: &mut Request) -> Result { 30 | /// let value = req.headers().get(header::HOST).map(|v| v.to_owned()); 31 | /// Ok(Host(value)) 32 | /// } 33 | /// } 34 | /// 35 | /// // 在处理程序中使用提取器从请求中提取数据。 36 | /// async fn using_extractor(Host(host): Host) { 37 | /// println!("{host:?}") 38 | /// } 39 | /// ``` 40 | pub trait FromRequest: Sized { 41 | /// 提取器的错误类型。 42 | type Error; 43 | 44 | /// 根据 [`Request`] 创建提取器实例。 45 | fn from_request(req: &mut Request) -> impl Future> + Send; 46 | } 47 | 48 | /// 可以根据 [`Request`] 创建的类型,用于实现提取器。 49 | /// 50 | /// 与 [`FromRequest`] 不同的是,如果提取的数据不存在,则返回 `Ok(None)`。 51 | /// 52 | /// # 例子 53 | /// 54 | /// ``` 55 | /// use std::convert::Infallible; 56 | /// 57 | /// use boluo_core::extract::OptionalFromRequest; 58 | /// use boluo_core::http::{header, HeaderValue}; 59 | /// use boluo_core::request::Request; 60 | /// 61 | /// // 从请求头中提取 HOST 的提取器。 62 | /// struct Host(HeaderValue); 63 | /// 64 | /// // 为提取器实现 `OptionalFromRequest` 特征。 65 | /// impl OptionalFromRequest for Host { 66 | /// type Error = Infallible; 67 | /// 68 | /// async fn from_request(req: &mut Request) -> Result, Self::Error> { 69 | /// Ok(req.headers().get(header::HOST).map(|v| Host(v.to_owned()))) 70 | /// } 71 | /// } 72 | /// 73 | /// // 在处理程序中使用提取器从请求中提取数据。 74 | /// async fn using_extractor(host: Option) { 75 | /// if let Some(Host(host)) = host { 76 | /// println!("{host:?}") 77 | /// } 78 | /// } 79 | /// ``` 80 | pub trait OptionalFromRequest: Sized { 81 | /// 提取器的错误类型。 82 | type Error; 83 | 84 | /// 根据 [`Request`] 创建提取器实例。 85 | fn from_request( 86 | req: &mut Request, 87 | ) -> impl Future, Self::Error>> + Send; 88 | } 89 | 90 | impl FromRequest for Option 91 | where 92 | T: OptionalFromRequest, 93 | { 94 | type Error = T::Error; 95 | 96 | async fn from_request(req: &mut Request) -> Result { 97 | T::from_request(req).await 98 | } 99 | } 100 | 101 | impl FromRequest for Result 102 | where 103 | T: FromRequest, 104 | { 105 | type Error = Infallible; 106 | 107 | async fn from_request(req: &mut Request) -> Result { 108 | Ok(T::from_request(req).await) 109 | } 110 | } 111 | 112 | macro_rules! from_request_tuples { 113 | ($($ty:ident),*) => { 114 | #[allow(non_snake_case)] 115 | impl<$($ty,)*> FromRequest for ($($ty,)*) 116 | where 117 | $($ty: FromRequest + Send,)* 118 | $(<$ty as FromRequest>::Error: Into,)* 119 | { 120 | type Error = BoxError; 121 | 122 | async fn from_request(req: &mut Request) -> Result { 123 | $( 124 | let $ty = $ty::from_request(req).await.map_err(Into::into)?; 125 | )* 126 | Ok(($($ty,)*)) 127 | } 128 | } 129 | }; 130 | } 131 | 132 | from_request_tuples!(T1); 133 | from_request_tuples!(T1, T2); 134 | from_request_tuples!(T1, T2, T3); 135 | from_request_tuples!(T1, T2, T3, T4); 136 | from_request_tuples!(T1, T2, T3, T4, T5); 137 | from_request_tuples!(T1, T2, T3, T4, T5, T6); 138 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7); 139 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8); 140 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9); 141 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); 142 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); 143 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); 144 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); 145 | from_request_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); 146 | from_request_tuples!( 147 | T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 148 | ); 149 | from_request_tuples!( 150 | T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 151 | ); 152 | 153 | impl FromRequest for Body { 154 | type Error = Infallible; 155 | 156 | async fn from_request(req: &mut Request) -> Result { 157 | Ok(std::mem::take(req.body_mut())) 158 | } 159 | } 160 | 161 | impl FromRequest for Bytes { 162 | type Error = BoxError; 163 | 164 | async fn from_request(req: &mut Request) -> Result { 165 | std::mem::take(req.body_mut()).to_bytes().await 166 | } 167 | } 168 | 169 | impl FromRequest for String { 170 | type Error = BoxError; 171 | 172 | async fn from_request(req: &mut Request) -> Result { 173 | let bytes = Bytes::from_request(req).await?; 174 | Ok(std::str::from_utf8(&bytes)?.to_owned()) 175 | } 176 | } 177 | 178 | impl FromRequest for Method { 179 | type Error = Infallible; 180 | 181 | async fn from_request(req: &mut Request) -> Result { 182 | Ok(req.method().clone()) 183 | } 184 | } 185 | 186 | impl FromRequest for Uri { 187 | type Error = Infallible; 188 | 189 | async fn from_request(req: &mut Request) -> Result { 190 | Ok(req.uri().clone()) 191 | } 192 | } 193 | 194 | impl FromRequest for Version { 195 | type Error = Infallible; 196 | 197 | async fn from_request(req: &mut Request) -> Result { 198 | Ok(req.version()) 199 | } 200 | } 201 | 202 | impl FromRequest for HeaderMap { 203 | type Error = Infallible; 204 | 205 | async fn from_request(req: &mut Request) -> Result { 206 | Ok(req.headers().clone()) 207 | } 208 | } 209 | 210 | impl FromRequest for Extensions { 211 | type Error = Infallible; 212 | 213 | async fn from_request(req: &mut Request) -> Result { 214 | Ok(req.extensions().clone()) 215 | } 216 | } 217 | 218 | impl FromRequest for RequestParts { 219 | type Error = Infallible; 220 | 221 | async fn from_request(req: &mut Request) -> Result { 222 | Ok(req.parts().clone()) 223 | } 224 | } 225 | 226 | /// 简化 [`Result`] 提取器的书写。 227 | pub type ExtractResult = std::result::Result::Error>; 228 | -------------------------------------------------------------------------------- /boluo-core/src/handler.rs: -------------------------------------------------------------------------------- 1 | //! 可用于处理请求并返回响应的异步函数。 2 | 3 | use std::marker::PhantomData; 4 | 5 | use crate::BoxError; 6 | use crate::extract::FromRequest; 7 | use crate::request::Request; 8 | use crate::response::{IntoResponse, Response}; 9 | use crate::service::Service; 10 | 11 | /// 将给定的处理程序转换为 [`Service`]。 12 | /// 13 | /// # 例子 14 | /// 15 | /// ``` 16 | /// use boluo_core::handler::handler_fn; 17 | /// 18 | /// async fn hello() -> &'static str { 19 | /// "Hello, World!" 20 | /// } 21 | /// 22 | /// let service = handler_fn(hello); 23 | /// ``` 24 | pub fn handler_fn(f: F) -> HandlerFn 25 | where 26 | HandlerFn: Service, 27 | { 28 | HandlerFn { 29 | f, 30 | _marker: Default::default(), 31 | } 32 | } 33 | 34 | /// 将给定的处理程序转换为 [`Service`]。 35 | pub struct HandlerFn { 36 | f: F, 37 | _marker: PhantomData T>, 38 | } 39 | 40 | impl Clone for HandlerFn { 41 | fn clone(&self) -> Self { 42 | Self { 43 | f: self.f.clone(), 44 | _marker: Default::default(), 45 | } 46 | } 47 | } 48 | 49 | impl Copy for HandlerFn {} 50 | 51 | impl std::fmt::Debug for HandlerFn { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | f.debug_struct("HandlerFn") 54 | .field("f", &std::any::type_name::()) 55 | .finish() 56 | } 57 | } 58 | 59 | impl Service for HandlerFn 60 | where 61 | F: Fn() -> Fut + Send + Sync, 62 | Fut: Future + Send, 63 | Fut::Output: IntoResponse, 64 | { 65 | type Response = Response; 66 | type Error = BoxError; 67 | 68 | async fn call(&self, _: Request) -> Result { 69 | (self.f)().await.into_response().map_err(Into::into) 70 | } 71 | } 72 | 73 | impl Service for HandlerFn 74 | where 75 | F: Fn(Request) -> Fut + Send + Sync, 76 | Fut: Future + Send, 77 | Fut::Output: IntoResponse, 78 | { 79 | type Response = Response; 80 | type Error = BoxError; 81 | 82 | async fn call(&self, req: Request) -> Result { 83 | (self.f)(req).await.into_response().map_err(Into::into) 84 | } 85 | } 86 | 87 | macro_rules! handler_tuples { 88 | ($($ty:ident),*) => { 89 | #[allow(non_snake_case)] 90 | impl Service for HandlerFn 91 | where 92 | F: Fn($($ty,)*) -> Fut + Send + Sync, 93 | Fut: Future + Send, 94 | Fut::Output: IntoResponse, 95 | $($ty: FromRequest + Send,)* 96 | $(<$ty as FromRequest>::Error: Into,)* 97 | { 98 | type Response = Response; 99 | type Error = BoxError; 100 | 101 | async fn call(&self, mut req: Request) -> Result { 102 | $( 103 | let $ty = $ty::from_request(&mut req).await.map_err(Into::into)?; 104 | )* 105 | (self.f)($($ty,)*).await.into_response().map_err(Into::into) 106 | } 107 | } 108 | 109 | #[allow(non_snake_case)] 110 | impl Service for HandlerFn 111 | where 112 | F: Fn($($ty,)* Request) -> Fut + Send + Sync, 113 | Fut: Future + Send, 114 | Fut::Output: IntoResponse, 115 | $($ty: FromRequest + Send,)* 116 | $(<$ty as FromRequest>::Error: Into,)* 117 | { 118 | type Response = Response; 119 | type Error = BoxError; 120 | 121 | async fn call(&self, mut req: Request) -> Result { 122 | $( 123 | let $ty = $ty::from_request(&mut req).await.map_err(Into::into)?; 124 | )* 125 | (self.f)($($ty,)* req).await.into_response().map_err(Into::into) 126 | } 127 | } 128 | }; 129 | } 130 | 131 | handler_tuples!(T1); 132 | handler_tuples!(T1, T2); 133 | handler_tuples!(T1, T2, T3); 134 | handler_tuples!(T1, T2, T3, T4); 135 | handler_tuples!(T1, T2, T3, T4, T5); 136 | handler_tuples!(T1, T2, T3, T4, T5, T6); 137 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7); 138 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8); 139 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9); 140 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); 141 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); 142 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); 143 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); 144 | handler_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); 145 | handler_tuples!( 146 | T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 147 | ); 148 | handler_tuples!( 149 | T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 150 | ); 151 | -------------------------------------------------------------------------------- /boluo-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `boluo` 的核心类型和特征。 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn( 5 | missing_debug_implementations, 6 | missing_docs, 7 | rust_2018_idioms, 8 | unreachable_pub 9 | )] 10 | #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] 11 | 12 | #[doc(hidden)] 13 | pub mod util; 14 | 15 | pub mod body; 16 | pub mod extract; 17 | pub mod handler; 18 | pub mod middleware; 19 | pub mod request; 20 | pub mod response; 21 | pub mod service; 22 | pub mod upgrade; 23 | 24 | pub mod http { 25 | //! http 库的重新导出。 26 | 27 | pub use http::header::{self, HeaderMap, HeaderName, HeaderValue}; 28 | pub use http::method::{self, Method}; 29 | pub use http::status::{self, StatusCode}; 30 | pub use http::uri::{self, Uri}; 31 | pub use http::version::{self, Version}; 32 | pub use http::{Error, Extensions, Result}; 33 | } 34 | 35 | /// 类型擦除的错误类型别名 36 | pub type BoxError = Box; 37 | -------------------------------------------------------------------------------- /boluo-core/src/middleware/around.rs: -------------------------------------------------------------------------------- 1 | use crate::middleware::Middleware; 2 | use crate::service::Service; 3 | 4 | /// 将给定的异步函数转换为 [`Middleware`],并可以携带状态。 5 | /// 6 | /// # 例子 7 | /// 8 | /// ``` 9 | /// use boluo_core::handler::handler_fn; 10 | /// use boluo_core::middleware::around_with_state_fn; 11 | /// use boluo_core::request::Request; 12 | /// use boluo_core::service::{Service, ServiceExt}; 13 | /// 14 | /// // 日志中间件 15 | /// async fn log(prefix: &&str, req: Request, service: &S) -> Result 16 | /// where 17 | /// S: Service, 18 | /// { 19 | /// println!("{prefix}: {} {}", req.method(), req.uri().path()); 20 | /// service.call(req).await 21 | /// } 22 | /// 23 | /// let service = handler_fn(|| async {}); 24 | /// let service = service.with(around_with_state_fn("HTTP", log)); 25 | /// ``` 26 | pub fn around_with_state_fn(state: T, f: F) -> AroundWithStateFn { 27 | AroundWithStateFn { state, f } 28 | } 29 | 30 | /// 详情查看 [`around_with_state_fn`]。 31 | #[derive(Clone, Copy)] 32 | pub struct AroundWithStateFn { 33 | state: T, 34 | f: F, 35 | } 36 | 37 | impl Middleware for AroundWithStateFn { 38 | type Service = AroundWithStateFnService; 39 | 40 | fn transform(self, service: S) -> Self::Service { 41 | AroundWithStateFnService { 42 | state: self.state, 43 | f: self.f, 44 | service, 45 | } 46 | } 47 | } 48 | 49 | impl std::fmt::Debug for AroundWithStateFn 50 | where 51 | T: std::fmt::Debug, 52 | { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | f.debug_struct("AroundWithStateFn") 55 | .field("state", &self.state) 56 | .field("f", &std::any::type_name::()) 57 | .finish() 58 | } 59 | } 60 | 61 | /// 中间件 [`AroundWithStateFn`] 返回的服务。 62 | #[derive(Clone, Copy)] 63 | pub struct AroundWithStateFnService { 64 | state: T, 65 | f: F, 66 | service: S, 67 | } 68 | 69 | impl Service for AroundWithStateFnService 70 | where 71 | for<'a> F: AroundWithState<'a, T, S, Req, Res = Res, Err = Err>, 72 | Self: Send + Sync, 73 | { 74 | type Response = Res; 75 | type Error = Err; 76 | 77 | fn call(&self, req: Req) -> impl Future> + Send { 78 | self.f.call(&self.state, req, &self.service) 79 | } 80 | } 81 | 82 | impl std::fmt::Debug for AroundWithStateFnService 83 | where 84 | T: std::fmt::Debug, 85 | S: std::fmt::Debug, 86 | { 87 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 88 | f.debug_struct("AroundWithStateFnService") 89 | .field("state", &self.state) 90 | .field("f", &std::any::type_name::()) 91 | .field("service", &self.service) 92 | .finish() 93 | } 94 | } 95 | 96 | trait AroundWithState<'a, T, S, R> 97 | where 98 | T: ?Sized, 99 | S: ?Sized, 100 | { 101 | type Res; 102 | type Err; 103 | 104 | fn call( 105 | &self, 106 | state: &'a T, 107 | req: R, 108 | service: &'a S, 109 | ) -> impl Future> + Send; 110 | } 111 | 112 | impl<'a, T, S, F, Fut, Req, Res, Err> AroundWithState<'a, T, S, Req> for F 113 | where 114 | T: ?Sized + 'a, 115 | S: ?Sized + 'a, 116 | F: Fn(&'a T, Req, &'a S) -> Fut, 117 | Fut: Future> + Send + 'a, 118 | { 119 | type Res = Res; 120 | type Err = Err; 121 | 122 | fn call( 123 | &self, 124 | state: &'a T, 125 | req: Req, 126 | service: &'a S, 127 | ) -> impl Future> + Send { 128 | self(state, req, service) 129 | } 130 | } 131 | 132 | /// 将给定的异步函数转换为 [`Middleware`]。 133 | /// 134 | /// # 例子 135 | /// 136 | /// ``` 137 | /// use boluo_core::handler::handler_fn; 138 | /// use boluo_core::middleware::around_fn; 139 | /// use boluo_core::request::Request; 140 | /// use boluo_core::service::{Service, ServiceExt}; 141 | /// 142 | /// // 日志中间件 143 | /// async fn log(req: Request, service: &S) -> Result 144 | /// where 145 | /// S: Service, 146 | /// { 147 | /// println!("HTTP: {} {}", req.method(), req.uri().path()); 148 | /// service.call(req).await 149 | /// } 150 | /// 151 | /// let service = handler_fn(|| async {}); 152 | /// let service = service.with(around_fn(log)); 153 | /// ``` 154 | pub fn around_fn(f: F) -> AroundFn { 155 | AroundFn { f } 156 | } 157 | 158 | /// 详情查看 [`around_fn`]。 159 | #[derive(Clone, Copy)] 160 | pub struct AroundFn { 161 | f: F, 162 | } 163 | 164 | impl Middleware for AroundFn { 165 | type Service = AroundFnService; 166 | 167 | fn transform(self, service: S) -> Self::Service { 168 | AroundFnService { f: self.f, service } 169 | } 170 | } 171 | 172 | impl std::fmt::Debug for AroundFn { 173 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 174 | f.debug_struct("AroundFn") 175 | .field("f", &std::any::type_name::()) 176 | .finish() 177 | } 178 | } 179 | 180 | /// 中间件 [`AroundFn`] 返回的服务。 181 | #[derive(Clone, Copy)] 182 | pub struct AroundFnService { 183 | f: F, 184 | service: S, 185 | } 186 | 187 | impl Service for AroundFnService 188 | where 189 | for<'a> F: Around<'a, S, Req, Res = Res, Err = Err>, 190 | Self: Send + Sync, 191 | { 192 | type Response = Res; 193 | type Error = Err; 194 | 195 | fn call(&self, req: Req) -> impl Future> + Send { 196 | self.f.call(req, &self.service) 197 | } 198 | } 199 | 200 | impl std::fmt::Debug for AroundFnService 201 | where 202 | S: std::fmt::Debug, 203 | { 204 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 205 | f.debug_struct("AroundFnService") 206 | .field("f", &std::any::type_name::()) 207 | .field("service", &self.service) 208 | .finish() 209 | } 210 | } 211 | 212 | trait Around<'a, S, R> 213 | where 214 | S: ?Sized, 215 | { 216 | type Res; 217 | type Err; 218 | 219 | fn call( 220 | &self, 221 | req: R, 222 | service: &'a S, 223 | ) -> impl Future> + Send; 224 | } 225 | 226 | impl<'a, S, F, Fut, Req, Res, Err> Around<'a, S, Req> for F 227 | where 228 | S: ?Sized + 'a, 229 | F: Fn(Req, &'a S) -> Fut, 230 | Fut: Future> + Send + 'a, 231 | { 232 | type Res = Res; 233 | type Err = Err; 234 | 235 | fn call( 236 | &self, 237 | req: Req, 238 | service: &'a S, 239 | ) -> impl Future> + Send { 240 | self(req, service) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /boluo-core/src/middleware/middleware_fn.rs: -------------------------------------------------------------------------------- 1 | use super::Middleware; 2 | 3 | /// 将给定的闭包转换为 [`Middleware`]。 4 | /// 5 | /// # 例子 6 | /// 7 | /// ``` 8 | /// use boluo_core::handler::handler_fn; 9 | /// use boluo_core::middleware::middleware_fn; 10 | /// use boluo_core::request::Request; 11 | /// use boluo_core::service::{Service, ServiceExt}; 12 | /// 13 | /// fn add_extension(service: S) -> impl Service 14 | /// where 15 | /// S: Service, 16 | /// { 17 | /// service.map_request(|mut req: Request| { 18 | /// req.extensions_mut().insert("My Extension"); 19 | /// req 20 | /// }) 21 | /// } 22 | /// 23 | /// let service = handler_fn(|| async {}); 24 | /// let service = service.with(middleware_fn(add_extension)); 25 | /// ``` 26 | pub fn middleware_fn(f: F) -> MiddlewareFn { 27 | MiddlewareFn { f } 28 | } 29 | 30 | /// 将给定的闭包转换为 [`Middleware`]。 31 | #[derive(Clone, Copy)] 32 | pub struct MiddlewareFn { 33 | f: F, 34 | } 35 | 36 | impl Middleware for MiddlewareFn 37 | where 38 | F: FnOnce(S1) -> S2, 39 | { 40 | type Service = S2; 41 | 42 | fn transform(self, service: S1) -> Self::Service { 43 | (self.f)(service) 44 | } 45 | } 46 | 47 | impl std::fmt::Debug for MiddlewareFn { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | f.debug_struct("MiddlewareFn") 50 | .field("f", &std::any::type_name::()) 51 | .finish() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /boluo-core/src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | //! 中间件的特征和相关类型的定义。 2 | 3 | mod around; 4 | mod middleware_fn; 5 | 6 | pub use around::*; 7 | pub use middleware_fn::*; 8 | 9 | /// 用于表示中间件的特征。 10 | /// 11 | /// # 例子 12 | /// 13 | /// ``` 14 | /// use boluo_core::handler::handler_fn; 15 | /// use boluo_core::middleware::Middleware; 16 | /// use boluo_core::request::Request; 17 | /// use boluo_core::response::{IntoResponse, Response}; 18 | /// use boluo_core::service::{Service, ServiceExt}; 19 | /// use boluo_core::BoxError; 20 | /// 21 | /// /// 记录请求日志的中间件。 22 | /// #[derive(Debug, Clone, Copy)] 23 | /// struct Log; 24 | /// 25 | /// /// 为日志中间件实现 [`Middleware`] 特征。 26 | /// impl Middleware for Log 27 | /// where 28 | /// S: Service, 29 | /// S::Response: IntoResponse, 30 | /// S::Error: Into, 31 | /// { 32 | /// type Service = LogService; 33 | /// 34 | /// fn transform(self, service: S) -> Self::Service { 35 | /// LogService { service } 36 | /// } 37 | /// } 38 | /// 39 | /// /// 日志中间件生成的新服务。 40 | /// #[derive(Debug, Clone, Copy)] 41 | /// struct LogService { 42 | /// service: S, 43 | /// } 44 | /// 45 | /// impl Service for LogService 46 | /// where 47 | /// S: Service, 48 | /// S::Response: IntoResponse, 49 | /// S::Error: Into, 50 | /// { 51 | /// type Response = Response; 52 | /// type Error = BoxError; 53 | /// 54 | /// async fn call(&self, req: Request) -> Result { 55 | /// println!("req -> {} {}", req.method(), req.uri().path()); 56 | /// 57 | /// let result = self 58 | /// .service 59 | /// .call(req) 60 | /// .await 61 | /// .map_err(Into::into) 62 | /// .and_then(|r| r.into_response().map_err(Into::into)); 63 | /// 64 | /// match &result { 65 | /// Ok(response) => { 66 | /// println!("res -> {}", response.status()); 67 | /// } 68 | /// Err(err) => { 69 | /// println!("err -> {err}"); 70 | /// } 71 | /// } 72 | /// 73 | /// result 74 | /// } 75 | /// } 76 | /// 77 | /// async fn hello() -> &'static str { 78 | /// "Hello, World!" 79 | /// } 80 | /// 81 | /// let service = handler_fn(hello); 82 | /// let service = service.with(Log); // 添加日志中间件。 83 | /// ``` 84 | pub trait Middleware { 85 | /// 新的 [`Service`] 类型。 86 | /// 87 | /// [`Service`]: crate::service::Service 88 | type Service; 89 | 90 | /// 将给定的 [`Service`] 对象转换为一个新的 [`Service`]。 91 | /// 92 | /// [`Service`]: crate::service::Service 93 | fn transform(self, service: S) -> Self::Service; 94 | } 95 | -------------------------------------------------------------------------------- /boluo-core/src/service/and_then.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`and_then`] 返回的服务。 4 | /// 5 | /// [`and_then`]: crate::service::ServiceExt::and_then 6 | #[derive(Clone, Copy)] 7 | pub struct AndThen { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl AndThen { 13 | /// 创建一个新的 [`AndThen`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for AndThen 20 | where 21 | S: Service, 22 | F: Fn(S::Response) -> Fut + Send + Sync, 23 | Fut: Future> + Send, 24 | { 25 | type Response = Res; 26 | type Error = S::Error; 27 | 28 | fn call(&self, req: Req) -> impl Future> + Send { 29 | let fut = self.service.call(req); 30 | async move { 31 | let response = fut.await?; 32 | (self.f)(response).await 33 | } 34 | } 35 | } 36 | 37 | impl std::fmt::Debug for AndThen 38 | where 39 | S: std::fmt::Debug, 40 | { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | f.debug_struct("AndThen") 43 | .field("service", &self.service) 44 | .field("f", &std::any::type_name::()) 45 | .finish() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /boluo-core/src/service/boxed.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use futures_core::future::BoxFuture; 4 | 5 | use super::Service; 6 | 7 | /// 装箱的 [`Service`] 特征对象。 8 | /// 9 | /// [`BoxService`] 将服务转换为特征对象并装箱,允许服务的 [`Future`] 是动态的。 10 | /// 11 | /// 如果需要一个实现 [`Clone`] 的装箱服务,考虑使用 [`BoxCloneService`] 或 [`ArcService`]。 12 | pub struct BoxService { 13 | service: Box>, 14 | } 15 | 16 | impl BoxService { 17 | /// 将服务转换为[`Service`]特征对象并装箱。 18 | pub fn new(service: S) -> Self 19 | where 20 | S: Service + 'static, 21 | { 22 | Self { 23 | service: Box::new(service), 24 | } 25 | } 26 | } 27 | 28 | impl Service for BoxService { 29 | type Response = Res; 30 | type Error = Err; 31 | 32 | fn call(&self, req: Req) -> impl Future> + Send { 33 | self.service.call(req) 34 | } 35 | } 36 | 37 | impl std::fmt::Debug for BoxService { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | f.debug_struct("BoxService").finish() 40 | } 41 | } 42 | 43 | /// 装箱的 [`Service`] 特征对象。 44 | /// 45 | /// [`ArcService`] 将服务转换为特征对象并装箱,允许服务的 [`Future`] 是动态的, 46 | /// 并允许共享服务。 47 | /// 48 | /// 这与 [`BoxService`] 类似,只是 [`ArcService`] 实现了 [`Clone`]。 49 | pub struct ArcService { 50 | service: Arc>, 51 | } 52 | 53 | impl ArcService { 54 | /// 将服务转换为[`Service`]特征对象并装箱。 55 | pub fn new(service: S) -> Self 56 | where 57 | S: Service + 'static, 58 | { 59 | Self { 60 | service: Arc::new(service), 61 | } 62 | } 63 | } 64 | 65 | impl Service for ArcService { 66 | type Response = Res; 67 | type Error = Err; 68 | 69 | fn call(&self, req: Req) -> impl Future> + Send { 70 | self.service.call(req) 71 | } 72 | } 73 | 74 | impl Clone for ArcService { 75 | fn clone(&self) -> Self { 76 | Self { 77 | service: Arc::clone(&self.service), 78 | } 79 | } 80 | } 81 | 82 | impl std::fmt::Debug for ArcService { 83 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 84 | f.debug_struct("ArcService").finish() 85 | } 86 | } 87 | 88 | /// 装箱的 [`Service`] 特征对象。 89 | /// 90 | /// [`BoxCloneService`] 将服务转换为特征对象并装箱,允许服务的 [`Future`] 是动态的, 91 | /// 并允许克隆服务。 92 | /// 93 | /// 这与 [`BoxService`] 类似,只是 [`BoxCloneService`] 实现了 [`Clone`]。 94 | pub struct BoxCloneService { 95 | service: Box>, 96 | } 97 | 98 | impl BoxCloneService { 99 | /// 将服务转换为[`Service`]特征对象并装箱。 100 | pub fn new(service: S) -> Self 101 | where 102 | S: Service + Clone + 'static, 103 | { 104 | Self { 105 | service: Box::new(service), 106 | } 107 | } 108 | } 109 | 110 | impl Service for BoxCloneService { 111 | type Response = Res; 112 | type Error = Err; 113 | 114 | fn call(&self, req: Req) -> impl Future> + Send { 115 | self.service.call(req) 116 | } 117 | } 118 | 119 | impl Clone for BoxCloneService { 120 | fn clone(&self) -> Self { 121 | Self { 122 | service: self.service.clone_box(), 123 | } 124 | } 125 | } 126 | 127 | impl std::fmt::Debug for BoxCloneService { 128 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 129 | f.debug_struct("BoxCloneService").finish() 130 | } 131 | } 132 | 133 | trait DynCloneService: DynService { 134 | fn clone_box( 135 | &self, 136 | ) -> Box>; 137 | } 138 | 139 | impl DynCloneService for S 140 | where 141 | S: Service + Clone + 'static, 142 | { 143 | fn clone_box( 144 | &self, 145 | ) -> Box> { 146 | Box::new(self.clone()) 147 | } 148 | } 149 | 150 | trait DynService: Send + Sync { 151 | type Response; 152 | type Error; 153 | 154 | fn call<'a>(&'a self, req: Req) -> BoxFuture<'a, Result> 155 | where 156 | Req: 'a; 157 | } 158 | 159 | impl DynService for S 160 | where 161 | S: Service + ?Sized, 162 | { 163 | type Response = S::Response; 164 | type Error = S::Error; 165 | 166 | fn call<'a>(&'a self, req: Req) -> BoxFuture<'a, Result> 167 | where 168 | Req: 'a, 169 | { 170 | Box::pin(Service::call(self, req)) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /boluo-core/src/service/ext.rs: -------------------------------------------------------------------------------- 1 | use crate::middleware::Middleware; 2 | use crate::util::assert_service; 3 | 4 | use super::{ 5 | AndThen, ArcService, BoxCloneService, BoxService, MapErr, MapRequest, MapResponse, MapResult, 6 | OrElse, Service, Then, 7 | }; 8 | 9 | /// [`Service`] 的扩展特征,提供了一些方便的功能。 10 | pub trait ServiceExt: Service { 11 | /// 在此服务上应用中间件。 12 | /// 13 | /// # 例子 14 | /// 15 | /// ``` 16 | /// use boluo_core::handler::handler_fn; 17 | /// use boluo_core::middleware::middleware_fn; 18 | /// use boluo_core::request::Request; 19 | /// use boluo_core::service::{Service, ServiceExt}; 20 | /// 21 | /// fn add_extension(service: S) -> impl Service 22 | /// where 23 | /// S: Service, 24 | /// { 25 | /// service.map_request(|mut req: Request| { 26 | /// req.extensions_mut().insert("My Extension"); 27 | /// req 28 | /// }) 29 | /// } 30 | /// 31 | /// let service = handler_fn(|| async {}); 32 | /// let service = service.with(middleware_fn(add_extension)); 33 | /// ``` 34 | fn with(self, middleware: T) -> T::Service 35 | where 36 | Self: Sized, 37 | T: Middleware, 38 | T::Service: Service, 39 | { 40 | assert_service(middleware.transform(self)) 41 | } 42 | 43 | /// 在此服务执行完成后执行给定的异步函数。 44 | /// 45 | /// # 例子 46 | /// 47 | /// ``` 48 | /// use boluo_core::service::{service_fn, ServiceExt}; 49 | /// 50 | /// #[derive(Debug)] 51 | /// struct MyError; 52 | /// 53 | /// impl std::fmt::Display for MyError { 54 | /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 55 | /// write!(f, "some error message") 56 | /// } 57 | /// } 58 | /// 59 | /// impl std::error::Error for MyError {} 60 | /// 61 | /// async fn throw_error(_: ()) -> Result<(), MyError> { 62 | /// Err(MyError) 63 | /// } 64 | /// 65 | /// let service = service_fn(throw_error); 66 | /// let service = service.then(|result| async move { 67 | /// if let Err(err) = &result { 68 | /// // 打印错误信息。 69 | /// println!("{err}"); 70 | /// } 71 | /// result 72 | /// }); 73 | /// ``` 74 | fn then(self, f: F) -> Then 75 | where 76 | Self: Sized, 77 | F: Fn(Result) -> Fut + Send + Sync, 78 | Fut: Future> + Send, 79 | { 80 | assert_service(Then::new(self, f)) 81 | } 82 | 83 | /// 在此服务执行成功后执行给定的异步函数。 84 | /// 85 | /// # 例子 86 | /// 87 | /// ``` 88 | /// use boluo_core::body::Body; 89 | /// use boluo_core::handler::handler_fn; 90 | /// use boluo_core::service::ServiceExt; 91 | /// use boluo_core::BoxError; 92 | /// 93 | /// async fn hello() -> &'static str { 94 | /// "Hello, World!" 95 | /// } 96 | /// 97 | /// let service = handler_fn(hello); 98 | /// let service = service.and_then(|response| async move { 99 | /// // 清空响应主体。 100 | /// Ok::<_, BoxError>(response.map(|_| Body::empty())) 101 | /// }); 102 | /// ``` 103 | fn and_then(self, f: F) -> AndThen 104 | where 105 | Self: Sized, 106 | F: Fn(Self::Response) -> Fut + Send + Sync, 107 | Fut: Future> + Send, 108 | { 109 | assert_service(AndThen::new(self, f)) 110 | } 111 | 112 | /// 在此服务执行失败后执行给定的异步函数。 113 | /// 114 | /// # 例子 115 | /// 116 | /// ``` 117 | /// use boluo_core::handler::handler_fn; 118 | /// use boluo_core::http::StatusCode; 119 | /// use boluo_core::response::IntoResponse; 120 | /// use boluo_core::service::ServiceExt; 121 | /// 122 | /// #[derive(Debug)] 123 | /// struct MyError; 124 | /// 125 | /// impl std::fmt::Display for MyError { 126 | /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 127 | /// write!(f, "some error message") 128 | /// } 129 | /// } 130 | /// 131 | /// impl std::error::Error for MyError {} 132 | /// 133 | /// async fn throw_error() -> Result<(), MyError> { 134 | /// Err(MyError) 135 | /// } 136 | /// 137 | /// let service = handler_fn(throw_error); 138 | /// let service = service.or_else(|err| async move { 139 | /// // 捕获错误并转换为响应。 140 | /// if let Some(e) = err.downcast_ref::() { 141 | /// let status = StatusCode::INTERNAL_SERVER_ERROR; 142 | /// return Ok((status, format!("{e}")).into_response()?); 143 | /// } 144 | /// Err(err) 145 | /// }); 146 | /// ``` 147 | fn or_else(self, f: F) -> OrElse 148 | where 149 | Self: Sized, 150 | F: Fn(Self::Error) -> Fut + Send + Sync, 151 | Fut: Future> + Send, 152 | { 153 | assert_service(OrElse::new(self, f)) 154 | } 155 | 156 | /// 将此服务返回的结果映射为其他值。 157 | /// 158 | /// # 例子 159 | /// 160 | /// ``` 161 | /// use std::convert::Infallible; 162 | /// 163 | /// use boluo_core::response::{IntoResponse, Response}; 164 | /// use boluo_core::service::{service_fn, ServiceExt}; 165 | /// 166 | /// #[derive(Debug)] 167 | /// struct MyError; 168 | /// 169 | /// impl std::fmt::Display for MyError { 170 | /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 171 | /// write!(f, "some error message") 172 | /// } 173 | /// } 174 | /// 175 | /// impl std::error::Error for MyError {} 176 | /// 177 | /// async fn throw_error(_: ()) -> Result<(), MyError> { 178 | /// Err(MyError) 179 | /// } 180 | /// 181 | /// let service = service_fn(throw_error); 182 | /// let service = service.map_result(|result| -> Result { 183 | /// match result { 184 | /// Ok(r) => r.into_response(), 185 | /// Err(e) => format!("{e}").into_response(), 186 | /// } 187 | /// }); 188 | /// ``` 189 | fn map_result(self, f: F) -> MapResult 190 | where 191 | Self: Sized, 192 | F: Fn(Result) -> Result + Send + Sync, 193 | { 194 | assert_service(MapResult::new(self, f)) 195 | } 196 | 197 | /// 将此服务返回的响应映射为其他值。 198 | /// 199 | /// # 例子 200 | /// 201 | /// ``` 202 | /// use std::convert::Infallible; 203 | /// 204 | /// use boluo_core::body::Body; 205 | /// use boluo_core::service::{service_fn, ServiceExt}; 206 | /// 207 | /// async fn hello(_: ()) -> Result<&'static str, Infallible> { 208 | /// Ok("Hello, World!") 209 | /// } 210 | /// 211 | /// let service = service_fn(hello); 212 | /// let service = service.map_response(|text| Body::from(text)); 213 | /// ``` 214 | fn map_response(self, f: F) -> MapResponse 215 | where 216 | Self: Sized, 217 | F: Fn(Self::Response) -> Res + Send + Sync, 218 | { 219 | assert_service(MapResponse::new(self, f)) 220 | } 221 | 222 | /// 将此服务返回的错误映射为其他值。 223 | /// 224 | /// # 例子 225 | /// 226 | /// ``` 227 | /// use boluo_core::service::{service_fn, ServiceExt}; 228 | /// use boluo_core::BoxError; 229 | /// 230 | /// #[derive(Debug)] 231 | /// struct MyError; 232 | /// 233 | /// impl std::fmt::Display for MyError { 234 | /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 235 | /// write!(f, "some error message") 236 | /// } 237 | /// } 238 | /// 239 | /// impl std::error::Error for MyError {} 240 | /// 241 | /// async fn throw_error(_: ()) -> Result<(), MyError> { 242 | /// Err(MyError) 243 | /// } 244 | /// 245 | /// let service = service_fn(throw_error); 246 | /// let service = service.map_err(|err| BoxError::from(err)); 247 | /// ``` 248 | fn map_err(self, f: F) -> MapErr 249 | where 250 | Self: Sized, 251 | F: Fn(Self::Error) -> Err + Send + Sync, 252 | { 253 | assert_service(MapErr::new(self, f)) 254 | } 255 | 256 | /// 将发送给此服务的请求映射为其他值。 257 | /// 258 | /// # 例子 259 | /// 260 | /// ``` 261 | /// use std::convert::Infallible; 262 | /// 263 | /// use boluo_core::service::{service_fn, Service, ServiceExt}; 264 | /// 265 | /// async fn echo(text: String) -> Result { 266 | /// Ok(text) 267 | /// } 268 | /// 269 | /// let service = service_fn(echo); 270 | /// let service = service.map_request(|slice: &[u8]| { 271 | /// // 将字节片转换为包含无效字符的字符串。 272 | /// String::from_utf8_lossy(slice).into_owned() 273 | /// }); 274 | /// 275 | /// let fut = service.call(b"Hello, World"); 276 | /// ``` 277 | fn map_request(self, f: F) -> MapRequest 278 | where 279 | Self: Sized, 280 | F: Fn(R) -> Req + Send + Sync, 281 | { 282 | assert_service(MapRequest::new(self, f)) 283 | } 284 | 285 | /// 将此服务转换为 [`Service`] 特征对象并装箱。 286 | /// 287 | /// 更多详细信息,请参阅 [`BoxService`]。 288 | fn boxed(self) -> BoxService 289 | where 290 | Self: Sized + 'static, 291 | { 292 | assert_service(BoxService::new(self)) 293 | } 294 | 295 | /// 将此服务转换为 [`Service`] 特征对象并装箱。 296 | /// 297 | /// 更多详细信息,请参阅 [`BoxCloneService`]。 298 | fn boxed_clone(self) -> BoxCloneService 299 | where 300 | Self: Sized + Clone + 'static, 301 | { 302 | assert_service(BoxCloneService::new(self)) 303 | } 304 | 305 | /// 将此服务转换为 [`Service`] 特征对象并装箱。 306 | /// 307 | /// 更多详细信息,请参阅 [`ArcService`]。 308 | fn boxed_arc(self) -> ArcService 309 | where 310 | Self: Sized + 'static, 311 | { 312 | assert_service(ArcService::new(self)) 313 | } 314 | } 315 | 316 | impl ServiceExt for S where S: Service {} 317 | -------------------------------------------------------------------------------- /boluo-core/src/service/map_err.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`map_err`] 返回的服务。 4 | /// 5 | /// [`map_err`]: crate::service::ServiceExt::map_err 6 | #[derive(Clone, Copy)] 7 | pub struct MapErr { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl MapErr { 13 | /// 创建一个新的 [`MapErr`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for MapErr 20 | where 21 | S: Service, 22 | F: Fn(S::Error) -> Err + Send + Sync, 23 | { 24 | type Response = S::Response; 25 | type Error = Err; 26 | 27 | fn call(&self, req: Req) -> impl Future> + Send { 28 | let fut = self.service.call(req); 29 | async move { fut.await.map_err(|err| (self.f)(err)) } 30 | } 31 | } 32 | 33 | impl std::fmt::Debug for MapErr 34 | where 35 | S: std::fmt::Debug, 36 | { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | f.debug_struct("MapErr") 39 | .field("service", &self.service) 40 | .field("f", &std::any::type_name::()) 41 | .finish() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /boluo-core/src/service/map_request.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`map_request`] 返回的服务。 4 | /// 5 | /// [`map_request`]: crate::service::ServiceExt::map_request 6 | #[derive(Clone, Copy)] 7 | pub struct MapRequest { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl MapRequest { 13 | /// 创建一个新的 [`MapRequest`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for MapRequest 20 | where 21 | S: Service, 22 | F: Fn(R1) -> R2 + Send + Sync, 23 | { 24 | type Response = S::Response; 25 | type Error = S::Error; 26 | 27 | fn call(&self, req: R1) -> impl Future> + Send { 28 | self.service.call((self.f)(req)) 29 | } 30 | } 31 | 32 | impl std::fmt::Debug for MapRequest 33 | where 34 | S: std::fmt::Debug, 35 | { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | f.debug_struct("MapRequest") 38 | .field("service", &self.service) 39 | .field("f", &std::any::type_name::()) 40 | .finish() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /boluo-core/src/service/map_response.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`map_response`] 返回的服务。 4 | /// 5 | /// [`map_response`]: crate::service::ServiceExt::map_response 6 | #[derive(Clone, Copy)] 7 | pub struct MapResponse { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl MapResponse { 13 | /// 创建一个新的 [`MapResponse`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for MapResponse 20 | where 21 | S: Service, 22 | F: Fn(S::Response) -> Res + Send + Sync, 23 | { 24 | type Response = Res; 25 | type Error = S::Error; 26 | 27 | fn call(&self, req: Req) -> impl Future> + Send { 28 | let fut = self.service.call(req); 29 | async move { fut.await.map(|res| (self.f)(res)) } 30 | } 31 | } 32 | 33 | impl std::fmt::Debug for MapResponse 34 | where 35 | S: std::fmt::Debug, 36 | { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | f.debug_struct("MapResponse") 39 | .field("service", &self.service) 40 | .field("f", &std::any::type_name::()) 41 | .finish() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /boluo-core/src/service/map_result.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`map_result`] 返回的服务。 4 | /// 5 | /// [`map_result`]: crate::service::ServiceExt::map_result 6 | #[derive(Clone, Copy)] 7 | pub struct MapResult { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl MapResult { 13 | /// 创建一个新的 [`MapResult`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for MapResult 20 | where 21 | S: Service, 22 | F: Fn(Result) -> Result + Send + Sync, 23 | { 24 | type Response = Res; 25 | type Error = Err; 26 | 27 | fn call(&self, req: Req) -> impl Future> + Send { 28 | let fut = self.service.call(req); 29 | async move { (self.f)(fut.await) } 30 | } 31 | } 32 | 33 | impl std::fmt::Debug for MapResult 34 | where 35 | S: std::fmt::Debug, 36 | { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | f.debug_struct("MapResult") 39 | .field("service", &self.service) 40 | .field("f", &std::any::type_name::()) 41 | .finish() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /boluo-core/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | //! 服务的特征和相关类型的定义。 2 | 3 | mod and_then; 4 | mod boxed; 5 | mod ext; 6 | mod map_err; 7 | mod map_request; 8 | mod map_response; 9 | mod map_result; 10 | mod or_else; 11 | mod service_fn; 12 | mod then; 13 | 14 | pub use and_then::AndThen; 15 | pub use boxed::{ArcService, BoxCloneService, BoxService}; 16 | pub use ext::ServiceExt; 17 | pub use map_err::MapErr; 18 | pub use map_request::MapRequest; 19 | pub use map_response::MapResponse; 20 | pub use map_result::MapResult; 21 | pub use or_else::OrElse; 22 | pub use service_fn::{ServiceFn, service_fn}; 23 | pub use then::Then; 24 | 25 | use std::sync::Arc; 26 | 27 | /// 表示一个接收请求并返回响应的异步函数。 28 | /// 29 | /// # 例子 30 | /// 31 | /// ``` 32 | /// use std::convert::Infallible; 33 | /// 34 | /// use boluo_core::request::Request; 35 | /// use boluo_core::response::Response; 36 | /// use boluo_core::service::Service; 37 | /// 38 | /// // 回声服务,响应请求主体。 39 | /// struct Echo; 40 | /// 41 | /// impl Service for Echo { 42 | /// type Response = Response; 43 | /// type Error = Infallible; 44 | /// 45 | /// async fn call(&self, req: Request) -> Result { 46 | /// Ok(Response::new(req.into_body())) 47 | /// } 48 | /// } 49 | /// ``` 50 | pub trait Service: Send + Sync { 51 | /// 服务返回的响应。 52 | type Response; 53 | 54 | /// 服务产生的错误。 55 | type Error; 56 | 57 | /// 处理请求并异步返回响应。 58 | fn call(&self, req: Req) -> impl Future> + Send; 59 | } 60 | 61 | impl Service for &mut S 62 | where 63 | S: Service + ?Sized, 64 | { 65 | type Response = S::Response; 66 | type Error = S::Error; 67 | 68 | fn call(&self, req: Req) -> impl Future> + Send { 69 | S::call(self, req) 70 | } 71 | } 72 | 73 | impl Service for &S 74 | where 75 | S: Service + ?Sized, 76 | { 77 | type Response = S::Response; 78 | type Error = S::Error; 79 | 80 | fn call(&self, req: Req) -> impl Future> + Send { 81 | S::call(self, req) 82 | } 83 | } 84 | 85 | impl Service for Box 86 | where 87 | S: Service + ?Sized, 88 | { 89 | type Response = S::Response; 90 | type Error = S::Error; 91 | 92 | fn call(&self, req: Req) -> impl Future> + Send { 93 | S::call(self, req) 94 | } 95 | } 96 | 97 | impl Service for Arc 98 | where 99 | S: Service + ?Sized, 100 | { 101 | type Response = S::Response; 102 | type Error = S::Error; 103 | 104 | fn call(&self, req: Req) -> impl Future> + Send { 105 | S::call(self, req) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /boluo-core/src/service/or_else.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`or_else`] 返回的服务。 4 | /// 5 | /// [`or_else`]: crate::service::ServiceExt::or_else 6 | #[derive(Clone, Copy)] 7 | pub struct OrElse { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl OrElse { 13 | /// 创建一个新的 [`OrElse`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for OrElse 20 | where 21 | S: Service, 22 | F: Fn(S::Error) -> Fut + Send + Sync, 23 | Fut: Future> + Send, 24 | { 25 | type Response = S::Response; 26 | type Error = Err; 27 | 28 | fn call(&self, req: Req) -> impl Future> + Send { 29 | let fut = self.service.call(req); 30 | async move { 31 | let err = match fut.await { 32 | Ok(res) => return Ok(res), 33 | Err(err) => err, 34 | }; 35 | (self.f)(err).await 36 | } 37 | } 38 | } 39 | 40 | impl std::fmt::Debug for OrElse 41 | where 42 | S: std::fmt::Debug, 43 | { 44 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 45 | f.debug_struct("OrElse") 46 | .field("service", &self.service) 47 | .field("f", &std::any::type_name::()) 48 | .finish() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /boluo-core/src/service/service_fn.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// 将给定的异步函数转换为 [`Service`]。 4 | /// 5 | /// # 例子 6 | /// 7 | /// ``` 8 | /// use std::convert::Infallible; 9 | /// 10 | /// use boluo_core::service::service_fn; 11 | /// 12 | /// async fn hello(_: ()) -> Result<&'static str, Infallible> { 13 | /// Ok("Hello, World!") 14 | /// } 15 | /// 16 | /// let service = service_fn(hello); 17 | /// ``` 18 | pub fn service_fn(f: F) -> ServiceFn { 19 | ServiceFn { f } 20 | } 21 | 22 | /// 将给定的异步函数转换为 [`Service`]。 23 | #[derive(Clone, Copy)] 24 | 25 | pub struct ServiceFn { 26 | f: F, 27 | } 28 | 29 | impl Service for ServiceFn 30 | where 31 | F: Fn(Req) -> Fut + Send + Sync, 32 | Fut: Future> + Send, 33 | { 34 | type Response = Res; 35 | type Error = Err; 36 | 37 | fn call(&self, req: Req) -> impl Future> + Send { 38 | (self.f)(req) 39 | } 40 | } 41 | 42 | impl std::fmt::Debug for ServiceFn { 43 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 44 | f.debug_struct("ServiceFn") 45 | .field("f", &std::any::type_name::()) 46 | .finish() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /boluo-core/src/service/then.rs: -------------------------------------------------------------------------------- 1 | use super::Service; 2 | 3 | /// [`then`] 返回的服务。 4 | /// 5 | /// [`then`]: crate::service::ServiceExt::then 6 | #[derive(Clone, Copy)] 7 | pub struct Then { 8 | service: S, 9 | f: F, 10 | } 11 | 12 | impl Then { 13 | /// 创建一个新的 [`Then`] 服务。 14 | pub fn new(service: S, f: F) -> Self { 15 | Self { service, f } 16 | } 17 | } 18 | 19 | impl Service for Then 20 | where 21 | S: Service, 22 | F: Fn(Result) -> Fut + Send + Sync, 23 | Fut: Future> + Send, 24 | { 25 | type Response = Res; 26 | type Error = Err; 27 | 28 | fn call(&self, req: Req) -> impl Future> + Send { 29 | let fut = self.service.call(req); 30 | async move { (self.f)(fut.await).await } 31 | } 32 | } 33 | 34 | impl std::fmt::Debug for Then 35 | where 36 | S: std::fmt::Debug, 37 | { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | f.debug_struct("Then") 40 | .field("service", &self.service) 41 | .field("f", &std::any::type_name::()) 42 | .finish() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /boluo-core/src/upgrade.rs: -------------------------------------------------------------------------------- 1 | //! HTTP 升级。 2 | 3 | use std::any::Any; 4 | use std::pin::Pin; 5 | use std::sync::{Arc, Mutex}; 6 | use std::task::{Context, Poll}; 7 | 8 | use futures_core::future::BoxFuture; 9 | use futures_io::{AsyncRead, AsyncWrite}; 10 | 11 | use crate::BoxError; 12 | 13 | /// 用于处理 HTTP 升级请求,获取升级后的连接。 14 | #[derive(Clone)] 15 | pub struct OnUpgrade { 16 | fut: Arc>>>, 17 | } 18 | 19 | impl OnUpgrade { 20 | /// 创建一个 `OnUpgrade` 实例。 21 | pub fn new(fut: T) -> Self 22 | where 23 | T: Future> + Send + 'static, 24 | { 25 | Self { 26 | fut: Arc::new(Mutex::new(Box::pin(fut))), 27 | } 28 | } 29 | } 30 | 31 | impl Future for OnUpgrade { 32 | type Output = Result; 33 | 34 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 35 | self.fut.lock().unwrap().as_mut().poll(cx) 36 | } 37 | } 38 | 39 | impl std::fmt::Debug for OnUpgrade { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | f.debug_struct("OnUpgrade").finish() 42 | } 43 | } 44 | 45 | /// HTTP 升级后的连接。 46 | pub struct Upgraded { 47 | io: Box, 48 | } 49 | 50 | impl Upgraded { 51 | /// 创建一个 `Upgraded` 实例。 52 | pub fn new(io: T) -> Self 53 | where 54 | T: AsyncRead + AsyncWrite + Unpin + Send + 'static, 55 | { 56 | Self { io: Box::new(io) } 57 | } 58 | 59 | /// 尝试将 `Upgraded` 实例转换为指定类型。 60 | pub fn downcast(self) -> Result { 61 | if self.io.as_ref().as_any().is::() { 62 | Ok(*self.io.into_any().downcast::().unwrap()) 63 | } else { 64 | Err(self) 65 | } 66 | } 67 | } 68 | 69 | impl AsyncRead for Upgraded { 70 | fn poll_read( 71 | mut self: Pin<&mut Self>, 72 | cx: &mut Context<'_>, 73 | buf: &mut [u8], 74 | ) -> Poll> { 75 | Pin::new(&mut self.io).poll_read(cx, buf) 76 | } 77 | } 78 | 79 | impl AsyncWrite for Upgraded { 80 | fn poll_write( 81 | mut self: Pin<&mut Self>, 82 | cx: &mut Context<'_>, 83 | buf: &[u8], 84 | ) -> Poll> { 85 | Pin::new(&mut self.io).poll_write(cx, buf) 86 | } 87 | 88 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 89 | Pin::new(&mut self.io).poll_flush(cx) 90 | } 91 | 92 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 93 | Pin::new(&mut self.io).poll_close(cx) 94 | } 95 | } 96 | 97 | impl std::fmt::Debug for Upgraded { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | f.debug_struct("Upgraded").finish() 100 | } 101 | } 102 | 103 | trait IO: AsyncRead + AsyncWrite + Unpin + 'static { 104 | fn as_any(&self) -> &dyn Any; 105 | 106 | fn into_any(self: Box) -> Box; 107 | } 108 | 109 | impl IO for T { 110 | fn as_any(&self) -> &dyn Any { 111 | self 112 | } 113 | 114 | fn into_any(self: Box) -> Box { 115 | self 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use std::pin::Pin; 122 | use std::task::{Context, Poll}; 123 | 124 | use futures_io::{AsyncRead, AsyncWrite}; 125 | 126 | use super::Upgraded; 127 | 128 | struct FuturesIo; 129 | 130 | impl AsyncRead for FuturesIo { 131 | fn poll_read( 132 | self: Pin<&mut Self>, 133 | _cx: &mut Context<'_>, 134 | _buf: &mut [u8], 135 | ) -> Poll> { 136 | todo!() 137 | } 138 | } 139 | 140 | impl AsyncWrite for FuturesIo { 141 | fn poll_write( 142 | self: Pin<&mut Self>, 143 | _cx: &mut Context<'_>, 144 | _buf: &[u8], 145 | ) -> Poll> { 146 | todo!() 147 | } 148 | 149 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 150 | todo!() 151 | } 152 | 153 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 154 | todo!() 155 | } 156 | } 157 | 158 | #[test] 159 | fn upgraded_downcast() { 160 | assert!(Upgraded::new(FuturesIo).downcast::<()>().is_err()); 161 | assert!(Upgraded::new(FuturesIo).downcast::().is_ok()); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /boluo-core/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | use crate::BoxError; 4 | use crate::request::Request; 5 | use crate::response::{IntoResponse, Response}; 6 | use crate::service::{ArcService, Service, ServiceExt}; 7 | 8 | /// Private API 9 | #[doc(hidden)] 10 | pub fn __try_downcast(src: Src) -> Result { 11 | let mut src = Some(src); 12 | match ::downcast_mut::>(&mut src) { 13 | Some(dst) => Ok(dst.take().unwrap()), 14 | _ => Err(src.unwrap()), 15 | } 16 | } 17 | 18 | /// Private API 19 | #[doc(hidden)] 20 | pub fn __into_arc_service(service: S) -> ArcService 21 | where 22 | S: Service + 'static, 23 | S::Response: IntoResponse, 24 | S::Error: Into, 25 | { 26 | __try_downcast(service).unwrap_or_else(|service| { 27 | service 28 | .map_result(|result| result.into_response()) 29 | .boxed_arc() 30 | }) 31 | } 32 | 33 | /// 断言 `S` 是一个 [`Service`]。 34 | #[inline] 35 | pub(crate) fn assert_service(service: S) -> S 36 | where 37 | S: Service, 38 | { 39 | service 40 | } 41 | -------------------------------------------------------------------------------- /boluo-macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # unreleased 2 | 3 | # 0.2.1 4 | 5 | ## 新增 6 | 7 | - `route` 宏支持通过 `crate` 属性指定别名路径。 8 | 9 | # 0.2.0 10 | 11 | ## 破坏 12 | 13 | - 迁移到 rust 2024 (1.85.0) 版本。 14 | 15 | # 0.1.2 16 | 17 | ## 新增 18 | 19 | - 添加文档。 20 | 21 | # 0.1.1 22 | 23 | ## 新增 24 | 25 | - 路由宏的method属性可以省略。 26 | 27 | # 0.1.0 28 | 29 | - 初始发布 -------------------------------------------------------------------------------- /boluo-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boluo-macros" 3 | version = "0.2.1" 4 | edition = { workspace = true } 5 | license = { workspace = true } 6 | homepage = { workspace = true } 7 | repository = { workspace = true } 8 | rust-version = { workspace = true } 9 | readme = "README.md" 10 | description = "boluo的宏" 11 | keywords = ["http", "web", "framework", "async"] 12 | categories = ["asynchronous", "network-programming", "web-programming"] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [lib] 17 | proc-macro = true 18 | 19 | [dependencies] 20 | proc-macro2 = "1" 21 | quote = "1" 22 | syn = { version = "2", features = ["full"] } 23 | -------------------------------------------------------------------------------- /boluo-macros/README.md: -------------------------------------------------------------------------------- 1 |

2 | boluo-macros 3 |

4 | 5 | `boluo` 的宏。 6 | 7 | ## 支持的最低 Rust 版本(MSRV) 8 | 9 | 支持的最低 Rust 版本为 `1.85.0`。 10 | -------------------------------------------------------------------------------- /boluo-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `boluo` 的宏。 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn( 5 | missing_debug_implementations, 6 | missing_docs, 7 | rust_2018_idioms, 8 | unreachable_pub 9 | )] 10 | #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] 11 | 12 | mod route; 13 | 14 | use proc_macro::TokenStream; 15 | 16 | /// 为处理程序添加请求路径和方法。 17 | /// 18 | /// # 例子 19 | /// 20 | /// ```ignore 21 | /// #[boluo::route("/", method = "GET")] 22 | /// async fn hello() -> &'static str { 23 | /// "Hello, World!" 24 | /// } 25 | /// ``` 26 | #[proc_macro_attribute] 27 | pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { 28 | route::route(attr, item) 29 | } 30 | -------------------------------------------------------------------------------- /boluo-macros/src/route.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Span, TokenStream as TokenStream2}; 3 | use quote::{ToTokens, TokenStreamExt, quote}; 4 | use syn::ext::IdentExt; 5 | use syn::parse::{Parse, ParseStream}; 6 | use syn::{Attribute, Error, Ident, ItemFn, LitStr, Token, Visibility}; 7 | 8 | pub(crate) fn route(attr: TokenStream, item: TokenStream) -> TokenStream { 9 | let attr = syn::parse_macro_input!(attr as RouteAttr); 10 | 11 | let item_fn = match syn::parse::(item.clone()) { 12 | Ok(item_fn) => item_fn, 13 | Err(e) => return input_and_compile_error(item, e), 14 | }; 15 | 16 | match Route::new(attr, item_fn) { 17 | Ok(route) => route.into_token_stream().into(), 18 | Err(e) => input_and_compile_error(item, e), 19 | } 20 | } 21 | 22 | struct PathAttr(String); 23 | 24 | impl Parse for PathAttr { 25 | fn parse(input: ParseStream<'_>) -> syn::Result { 26 | input.parse::().map(|s| s.value()).map(Self) 27 | } 28 | } 29 | 30 | impl ToTokens for PathAttr { 31 | fn to_tokens(&self, stream: &mut TokenStream2) { 32 | stream.append(LitStr::new(self.0.as_str(), Span::call_site()).token()); 33 | } 34 | } 35 | 36 | #[derive(PartialEq, Eq, Hash)] 37 | struct MethodAttr(String); 38 | 39 | impl MethodAttr { 40 | fn parse_more(input: ParseStream<'_>) -> syn::Result> { 41 | let content; 42 | let _bracket_token = syn::bracketed!(content in input); 43 | let methods = content.parse_terminated(MethodAttr::parse_one, Token![,])?; 44 | Ok(methods.into_iter().collect()) 45 | } 46 | 47 | fn parse_one(input: ParseStream<'_>) -> syn::Result { 48 | input.parse::().and_then(MethodAttr::try_from) 49 | } 50 | 51 | fn parse(input: ParseStream<'_>) -> syn::Result> { 52 | if MethodAttr::parse_more(&input.fork()).is_ok() { 53 | return MethodAttr::parse_more(input); 54 | } 55 | MethodAttr::parse_one(input).map(|v| vec![v]) 56 | } 57 | } 58 | 59 | impl ToTokens for MethodAttr { 60 | fn to_tokens(&self, stream: &mut TokenStream2) { 61 | stream.append(LitStr::new(self.0.as_str(), Span::call_site()).token()); 62 | } 63 | } 64 | 65 | impl TryFrom for MethodAttr { 66 | type Error = Error; 67 | 68 | fn try_from(value: LitStr) -> Result { 69 | let method = value.value(); 70 | if method.is_empty() { 71 | Err(Error::new_spanned(value, "invalid HTTP method")) 72 | } else { 73 | Ok(Self(method)) 74 | } 75 | } 76 | } 77 | 78 | struct CratePath(syn::Path); 79 | 80 | impl CratePath { 81 | fn parse(input: ParseStream<'_>) -> syn::Result { 82 | input.parse::()?.parse::().map(Self) 83 | } 84 | } 85 | 86 | impl ToTokens for CratePath { 87 | fn to_tokens(&self, stream: &mut TokenStream2) { 88 | self.0.to_tokens(stream); 89 | } 90 | } 91 | 92 | struct RouteAttr { 93 | path: PathAttr, 94 | methods: Vec, 95 | crate_path: Option, 96 | } 97 | 98 | impl Parse for RouteAttr { 99 | fn parse(input: ParseStream<'_>) -> syn::Result { 100 | let path = PathAttr::parse(input).map_err(|_| { 101 | Error::new( 102 | Span::call_site(), 103 | r#"invalid route definition, expected #[route("", ...)]"#, 104 | ) 105 | })?; 106 | 107 | let mut methods = None; 108 | let mut crate_path = None; 109 | 110 | while !input.is_empty() { 111 | input.parse::()?; 112 | let ident = Ident::parse_any(input)?; 113 | input.parse::()?; 114 | 115 | match ident.to_string().as_str() { 116 | "method" => { 117 | if methods.is_some() { 118 | return Err(Error::new_spanned( 119 | &ident, 120 | format!("duplicate attribute `{ident}`"), 121 | )); 122 | } 123 | methods = Some(MethodAttr::parse(input)?); 124 | } 125 | "crate" => { 126 | if crate_path.is_some() { 127 | return Err(Error::new_spanned( 128 | &ident, 129 | format!("duplicate attribute `{ident}`"), 130 | )); 131 | } 132 | crate_path = Some(CratePath::parse(input)?); 133 | } 134 | _ => { 135 | return Err(Error::new_spanned( 136 | &ident, 137 | format!("illegal attribute `{ident}`"), 138 | )); 139 | } 140 | } 141 | } 142 | 143 | Ok(Self { 144 | path, 145 | methods: methods.unwrap_or_default(), 146 | crate_path, 147 | }) 148 | } 149 | } 150 | 151 | struct Route { 152 | item_fn: ItemFn, 153 | vis: Visibility, 154 | name: Ident, 155 | attr: RouteAttr, 156 | docs: Vec, 157 | } 158 | 159 | impl Route { 160 | fn new(attr: RouteAttr, item_fn: ItemFn) -> syn::Result { 161 | let vis = item_fn.vis.clone(); 162 | let name = item_fn.sig.ident.clone(); 163 | 164 | let docs = item_fn 165 | .attrs 166 | .iter() 167 | .filter(|attr| attr.path().is_ident("doc")) 168 | .cloned() 169 | .collect(); 170 | 171 | Ok(Self { 172 | item_fn, 173 | vis, 174 | name, 175 | attr, 176 | docs, 177 | }) 178 | } 179 | } 180 | 181 | impl ToTokens for Route { 182 | fn to_tokens(&self, tokens: &mut TokenStream2) { 183 | let Self { 184 | item_fn, 185 | vis, 186 | name, 187 | attr, 188 | docs, 189 | } = self; 190 | 191 | let RouteAttr { 192 | path, 193 | methods, 194 | crate_path, 195 | } = attr; 196 | 197 | let crate_path = if let Some(name) = crate_path { 198 | quote!(#name) 199 | } else { 200 | quote!(::boluo) 201 | }; 202 | 203 | let methods = methods.iter(); 204 | 205 | let handler_service = quote! { 206 | #crate_path::service::Service<#crate_path::request::Request, 207 | Response = #crate_path::response::Response, 208 | Error = #crate_path::BoxError, 209 | > 210 | }; 211 | 212 | let stream = quote! { 213 | #(#docs)* 214 | #[allow(non_camel_case_types)] 215 | #[derive(Clone, Copy)] 216 | #vis struct #name; 217 | 218 | impl #crate_path::service::Service<#crate_path::request::Request> for #name { 219 | type Response = #crate_path::response::Response; 220 | type Error = #crate_path::BoxError; 221 | 222 | async fn call( 223 | &self, 224 | req: #crate_path::request::Request, 225 | ) -> ::std::result::Result { 226 | #item_fn 227 | 228 | fn assert_service(service: S) -> impl #handler_service 229 | where 230 | S: #handler_service, 231 | { 232 | service 233 | } 234 | 235 | let service = #crate_path::handler::handler_fn(#name); 236 | let service = assert_service(service); 237 | 238 | #crate_path::service::Service::call(&service, req).await 239 | } 240 | } 241 | 242 | impl ::std::convert::Into<#crate_path::route::Route<#name>> for #name { 243 | fn into(self) -> #crate_path::route::Route<#name> { 244 | let method_route = #crate_path::route::any(#name) 245 | #(.add(#crate_path::http::Method::try_from(#methods).unwrap()))*; 246 | #crate_path::route::Route::new(#path, method_route) 247 | } 248 | } 249 | }; 250 | 251 | tokens.extend(stream); 252 | } 253 | } 254 | 255 | fn input_and_compile_error(mut item: TokenStream, err: Error) -> TokenStream { 256 | let compile_err = TokenStream::from(err.to_compile_error()); 257 | item.extend(compile_err); 258 | item 259 | } 260 | -------------------------------------------------------------------------------- /boluo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # unreleased 2 | 3 | # 0.7.0 4 | 5 | ## 破坏 6 | 7 | - 将 `matchit` 的依赖版本锁定为 `0.8.6`。 8 | - 重命名 `ConnectionInfo` 为 `ConnectInfo`。 9 | - 将 `boluo-core` 升级到 `0.6`。 10 | 11 | ## 变化 12 | 13 | - 重构:优化服务器的实现和代码结构。 14 | - 重构:将 `match_method` 提取为 `MethodRouter` 的方法以优化代码结构。 15 | - 重构:修改 `add_endpoint` 和 `add_endpoint_with` 的实现,减少代码膨胀。 16 | - 修改 `Server` 的 `Debug` 实现。 17 | 18 | # 0.6.5 19 | 20 | ## 变化 21 | 22 | - 重构:优化路由器的实现和代码结构。 23 | 24 | # 0.6.4 25 | 26 | ## 新增 27 | 28 | - 新增函数 `Router::scope_merge`,`Router::scope_merge_with`,`Router::try_scope_merge`,`Router::try_scope_merge_with`,允许在合并路由器时向所有路由添加前缀。 29 | 30 | ## 修复 31 | 32 | - 恢复路径通配符替换逻辑为仅处理末尾的 {*}。 33 | 34 | # 0.6.3 35 | 36 | ## 新增 37 | 38 | - 新增函数 `Router::iter`,用于遍历路由器中的所有路由。 39 | - 新增函数 `Router::remove`,用于移除路由器中的指定路由。 40 | - 公开 `Endpoint` 类型,改进 `Router::iter` 函数。 41 | 42 | # 0.6.2 43 | 44 | ## 变化 45 | 46 | - `Router` 的 `merge`,`merge_with`,`try_merge`,`try_merge_with` 方法现在接受任何可以转换为 `Router` 的类型。 47 | 48 | # 0.6.1 49 | 50 | ## 修复 51 | 52 | - 修复嵌套路由路径 `{*}` 前缀缺失 `/` 的问题。 53 | 54 | # 0.6.0 55 | 56 | ## 破坏 57 | 58 | - 迁移到 rust 2024 (1.85.0) 版本。 59 | - 改进 `Listener`,现在可以返回自定义的连接地址类型。 60 | - 将模块 fs 重命名为 static_file,并将功能修改为使用 static-file 特性开关。 61 | - 调整 features。 62 | - 将 tokio-tungstenite 依赖的版本提升到 0.26,并重构 ws 模块。 63 | 64 | ## 新增 65 | 66 | - 新增模块 upgrade。 67 | - 新增函数 `KeepAlive::event`,用于自定义 SSE 保持连接的消息事件。 68 | 69 | ## 变化 70 | 71 | - 使用 upgrade 模块完成 WebSocket 升级。 72 | 73 | # 0.5.4 74 | 75 | ## 新增 76 | 77 | - 公开 `PathParams`。 78 | 79 | ## 修复 80 | 81 | - 修复少部分情况下 `Router` 在未找到处理程序时提取路径参数。 82 | 83 | # 0.5.3 84 | 85 | ## 变化 86 | 87 | - 重构 `GracefulShutdown` 的实现。 88 | 89 | # 0.5.2 90 | 91 | ## 变化 92 | 93 | - `Server::http1_header_read_timeout` 可以传递None以禁用配置。 94 | 95 | ## 修复 96 | 97 | - `Server` 内部未配置 `Timer` 导致的 `http1_header_read_timeout` 等函数配置后服务器无法处理请求。 98 | 99 | # 0.5.1 100 | 101 | ## 新增 102 | 103 | - `Server` 新增函数:`http1_max_headers`,`http2_max_pending_accept_reset_streams`。 104 | 105 | ## 变化 106 | 107 | - tokio-tungstenite = "0.24"。 108 | 109 | # 0.5.0 110 | 111 | ## 破坏 112 | 113 | - boluo-core = "0.4"。 114 | - 修改 `ServeFile` 和 `ServeDir` 的 `Service` 实现。 115 | - 修改 `ExtensionService` 的 `Service` 实现。 116 | - 删除 `OptionalTypedHeader`。 117 | 118 | ## 新增 119 | 120 | - 为 `Extension` 实现 `OptionalFromRequest`。 121 | - 为 `TypedHeader` 实现 `OptionalFromRequest`。 122 | 123 | # 0.4.0 124 | 125 | ## 破坏 126 | 127 | - boluo-core = "0.3"。 128 | - 重构模块 `boluo::extract::header`。 129 | 130 | ## 新增 131 | 132 | - 导出 `headers` 库。 133 | 134 | # 0.3.2 135 | 136 | ## 新增 137 | 138 | - 为 `EventBuilder` 实现 `Default`。 139 | 140 | # 0.3.1 141 | 142 | ## 修复 143 | 144 | - 当 `http2` 功能关闭时,从依赖项中删除 `h2`。 145 | 146 | # 0.3.0 147 | 148 | ## 破坏 149 | 150 | - boluo-core = "0.2"。 151 | - 移除 `FormResponseError` 的 `From` 实现。 152 | - 移除 `JsonResponseError` 的 `From` 实现。 153 | - 重构错误类型:`MultipartError`,`RedirectUriError`,`EventValueError`。 154 | 155 | ## 新增 156 | 157 | - 为 `PathExtractError` 实现 `Clone`。 158 | 159 | ## 变化 160 | 161 | - 变化:移除实现 `Service` 的多余约束:`ServeFile`,`ServeDir`,`ExtensionService`。 162 | 163 | # 0.2.0 164 | 165 | ## 破坏 166 | 167 | - 私有化 `MethodRoute::any` 和 `MethodRoute::one`。 168 | - 重构模块 `boluo::extract` 下的错误类型。 169 | - 重构模块 `boluo::response` 下的错误类型。 170 | - 变更提取器 `Form` 的行为,`HEAD` 请求也将从查询字符串中读取表单数据。 171 | - `Router` 的嵌套路径不允许为空。 172 | 173 | ## 新增 174 | 175 | - 为 `Extension` 实现 `IntoResponseParts`。 176 | - 添加文档。 177 | 178 | # 0.1.1 179 | 180 | ## 修复 181 | 182 | - 将 `boluo/README.md` 的示例链接指向正确的位置。 183 | 184 | # 0.1.0 185 | 186 | - 初始发布 -------------------------------------------------------------------------------- /boluo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boluo" 3 | version = "0.7.0" 4 | edition = { workspace = true } 5 | license = { workspace = true } 6 | homepage = { workspace = true } 7 | repository = { workspace = true } 8 | rust-version = { workspace = true } 9 | readme = "README.md" 10 | description = "简单易用的异步网络框架" 11 | keywords = ["http", "web", "framework", "async"] 12 | categories = [ 13 | "asynchronous", 14 | "network-programming", 15 | "web-programming::http-server", 16 | ] 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [dependencies] 21 | boluo-core = { path = "../boluo-core", version = "0.6" } 22 | boluo-macros = { path = "../boluo-macros", version = "0.2" } 23 | serde = { version = "1", features = ["derive"] } 24 | serde_json = "1" 25 | serde_urlencoded = "0.7" 26 | matchit = "=0.8.6" 27 | futures-util = "0.3" 28 | pin-project-lite = "0.2" 29 | mime = "0.3" 30 | bytes = "1" 31 | percent-encoding = "2" 32 | headers = "0.4" 33 | 34 | multer = { version = "3", optional = true } 35 | hyper = { version = "1", optional = true } 36 | hyper-util = { version = "0.1", optional = true } 37 | tokio = { version = "1", optional = true } 38 | tokio-util = { version = "0.7", optional = true } 39 | tokio-tungstenite = { version = "0.26", optional = true } 40 | sha1 = { version = "0.10", optional = true } 41 | base64 = { version = "0.22", optional = true } 42 | mime_guess = { version = "2", optional = true } 43 | memchr = { version = "2", optional = true } 44 | 45 | [dev-dependencies] 46 | criterion = { version = "0.5", features = ["async_tokio"] } 47 | tokio = { version = "1", features = ["full"] } 48 | 49 | [features] 50 | default = ["http1"] 51 | server = [ 52 | "hyper-util/server", 53 | "hyper-util/tokio", 54 | "tokio/rt", 55 | "tokio/macros", 56 | "tokio-util/compat", 57 | ] 58 | http1 = ["server", "hyper/http1", "hyper-util/http1"] 59 | http2 = ["server", "hyper/http2", "hyper-util/http2"] 60 | multipart = ["multer"] 61 | sse = ["tokio/time", "memchr"] 62 | ws = [ 63 | "hyper", 64 | "hyper-util/tokio", 65 | "tokio/rt", 66 | "tokio-util/compat", 67 | "tokio-tungstenite", 68 | "sha1", 69 | "base64", 70 | ] 71 | static-file = ["tokio/fs", "tokio/io-util", "tokio-util/io", "mime_guess"] 72 | tokio = ["dep:tokio", "tokio/net"] 73 | 74 | [package.metadata.docs.rs] 75 | all-features = true 76 | rustdoc-args = ["--cfg", "docsrs"] 77 | 78 | [[bench]] 79 | name = "bench" 80 | harness = false 81 | -------------------------------------------------------------------------------- /boluo/README.md: -------------------------------------------------------------------------------- 1 |

2 | boluo 3 |

4 | 5 |

6 | 简单易用的高性能异步网络框架 7 |

8 | 9 | ## 介绍 10 | 11 | `boluo` 是一个基于 `tokio` 和 `hyper` 开发的轻量级路由层,几乎没有额外的性能开销,拥有极快的运行速度。 12 | 13 | ## 特点 14 | 15 | - 简单清晰的路由定义,支持嵌套路由、宏定义路由和路由合并。 16 | - 提供核心特征 `Service` 和 `Middleware`,灵活且易于扩展。 17 | - 提供集中化的错误处理和错误传播机制。 18 | 19 | ## 可选功能 20 | 21 | | 功能名 | 描述 | 默认启用 | 22 | | ----------- | ------------------------------------- | -------- | 23 | | http1 | 启用HTTP1服务器 | 是 | 24 | | http2 | 启用HTTP2服务器 | | 25 | | multipart | 添加对 `multipart/form-data` 格式的支持 | | 26 | | sse | 添加对服务器发送事件的支持 | | 27 | | ws | 添加对网络套接字的支持 | | 28 | | static-file | 添加对静态文件的支持 | | 29 | 30 | ## 快速开始 31 | 32 | 新建项目: 33 | 34 | ```bash 35 | cargo new demo && cd demo 36 | ``` 37 | 38 | 添加依赖: 39 | 40 | ```toml 41 | [dependencies] 42 | boluo = "0.7" 43 | tokio = { version = "1", features = ["full"] } 44 | ``` 45 | 46 | 用以下内容覆盖 `src/main.rs`: 47 | 48 | ```rust 49 | use boluo::response::IntoResponse; 50 | use boluo::route::Router; 51 | use boluo::server::Server; 52 | use tokio::net::TcpListener; 53 | 54 | #[tokio::main] 55 | async fn main() { 56 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 57 | 58 | let app = Router::new().mount(hello); 59 | 60 | Server::new(listener).run(app).await.unwrap(); 61 | } 62 | 63 | #[boluo::route("/", method = "GET")] 64 | async fn hello() -> impl IntoResponse { 65 | "Hello, World!" 66 | } 67 | ``` 68 | 69 | 运行项目: 70 | 71 | ```bash 72 | cargo run 73 | ``` 74 | 75 | 访问服务: 76 | 77 | ```bash 78 | curl http://127.0.0.1:3000/ 79 | ``` 80 | 81 | ## 更多示例 82 | 83 | [在这里](../examples/)可以找到更多的示例代码。在示例目录中,你可以通过以下命令运行示例: 84 | 85 | ```bash 86 | cargo run --bin hello 87 | ``` 88 | 89 | ## 支持的最低 Rust 版本(MSRV) 90 | 91 | 支持的最低 Rust 版本为 `1.85.0`。 92 | -------------------------------------------------------------------------------- /boluo/benches/bench.rs: -------------------------------------------------------------------------------- 1 | // TODO 对该框架的基准测试还没有完成,以下是一个简单的例子。 2 | 3 | use boluo::BoxError; 4 | use boluo::body::Body; 5 | use boluo::handler::handler_fn; 6 | use boluo::http::Method; 7 | use boluo::request::Request; 8 | use boluo::response::IntoResponse; 9 | use boluo::route::{Router, get, patch, post, put}; 10 | use boluo::service::Service; 11 | use criterion::{Criterion, black_box, criterion_group, criterion_main}; 12 | use tokio::runtime::Builder; 13 | 14 | fn router() -> impl Service> { 15 | Router::new() 16 | .route("/users", post(handler_fn(|| async {}))) 17 | .route("/posts", post(handler_fn(|| async {}))) 18 | .route("/users/{id}", patch(handler_fn(|| async {}))) 19 | .route("/posts/{id}", patch(handler_fn(|| async {}))) 20 | .route("/users/{id}/nickname", put(handler_fn(|| async {}))) 21 | .route("/posts/{id}/state", put(handler_fn(|| async {}))) 22 | .route("/users/{id}/nickname", get(handler_fn(|| async {}))) 23 | .route("/posts/{id}/state", get(handler_fn(|| async {}))) 24 | .route("/users/{id}", get(handler_fn(|| async {}))) 25 | .route("/posts/{id}", get(handler_fn(|| async {}))) 26 | } 27 | 28 | fn router_benchmark(c: &mut Criterion) { 29 | let runtime = Builder::new_multi_thread() 30 | .worker_threads(4) 31 | .build() 32 | .unwrap(); 33 | 34 | let router_a = black_box(router()); 35 | 36 | c.bench_function("router", |b| { 37 | b.to_async(&runtime).iter(|| async { 38 | for req in black_box(requests()) { 39 | let _ = router_a.call(req).await; 40 | } 41 | }) 42 | }); 43 | } 44 | 45 | criterion_group!(benches, router_benchmark); 46 | criterion_main!(benches); 47 | 48 | fn requests() -> impl IntoIterator { 49 | [ 50 | request!(Method::POST, "/users"), 51 | request!(Method::POST, "/posts"), 52 | request!(Method::PATCH, "/users/643734"), 53 | request!(Method::PATCH, "/posts/124133"), 54 | request!(Method::PUT, "/users/643734/nickname"), 55 | request!(Method::PUT, "/posts/124133/state"), 56 | request!(Method::GET, "/users/210875/username"), 57 | request!(Method::GET, "/users/490584/nickname"), 58 | request!(Method::GET, "/posts/124543"), 59 | request!(Method::GET, "/posts/637577"), 60 | request!(Method::DELETE, "/posts/124812"), 61 | request!(Method::GET, "/posts/450824/content"), 62 | ] 63 | } 64 | 65 | macro_rules! request { 66 | ($method:expr, $uri:expr) => { 67 | Request::builder() 68 | .method($method) 69 | .uri($uri) 70 | .body(Body::empty()) 71 | .unwrap() 72 | }; 73 | } 74 | 75 | use request; 76 | -------------------------------------------------------------------------------- /boluo/doc/route/route.md: -------------------------------------------------------------------------------- 1 | # 路径 2 | 3 | 路径是由 `/` 分隔的段组成,每个段可以是静态段、捕获段或通配段。 4 | 5 | ## 路径的静态段 6 | 7 | - / 8 | - /foo 9 | - /foo/bar 10 | 11 | ## 路径的捕获段 12 | 13 | - /{key} 14 | - /users/{id} 15 | - /users/{id}/email 16 | 17 | 路径可以包含像 `{key}` 这样的段,它匹配任何单个段,并存储在 `key` 处捕获的值。 18 | 19 | ## 路径的通配段 20 | 21 | - /{*} 22 | - /{*key} 23 | - /resource/{*path} 24 | 25 | 路径可以在结尾包含像 `{*key}` 这样的段,该段匹配所有剩余的段,并存储在 `key` 处捕获的值。 26 | 27 | 未命名的通配段 `{*}`,不会存储匹配的值。 28 | -------------------------------------------------------------------------------- /boluo/doc/route/scope.md: -------------------------------------------------------------------------------- 1 | # 嵌套路径 2 | 3 | 嵌套路径是由 `/` 分隔的段组成,每个段可以是静态段、捕获段或通配段。 4 | 5 | ## 嵌套路径的静态段 6 | 7 | - / 8 | - /foo 9 | - /foo/bar 10 | 11 | ## 嵌套路径的捕获段 12 | 13 | - /{key} 14 | - /users/{id} 15 | - /users/{id}/email 16 | 17 | 嵌套路径可以包含像 `{key}` 这样的段,它匹配任何单个段,并存储在 `key` 处捕获的值。 18 | 19 | ## 嵌套路径的通配段 20 | 21 | - /{*} 22 | - /resource/{*} 23 | 24 | 嵌套路径可以在结尾包含像 `{*}` 这样的段,该段匹配所有剩余的段,并且通配段匹配的路径不会被去除。 25 | -------------------------------------------------------------------------------- /boluo/src/data.rs: -------------------------------------------------------------------------------- 1 | //! 通用数据类型。 2 | 3 | use std::ops::{Deref, DerefMut}; 4 | 5 | /// JSON 提取器和响应。 6 | /// 7 | /// # 提取 8 | /// 9 | /// 当用作提取器时,[`Json`] 可以将请求体反序列化为实现 [`serde::de::DeserializeOwned`] 的类型。 10 | /// 11 | /// ``` 12 | /// use boluo::data::Json; 13 | /// 14 | /// #[derive(serde::Deserialize)] 15 | /// struct CreateUser { 16 | /// username: String, 17 | /// password: String, 18 | /// } 19 | /// 20 | /// #[boluo::route("/users", method = "POST")] 21 | /// async fn create_user(Json(user): Json) { 22 | /// // 创建用户。 23 | /// } 24 | /// ``` 25 | /// 26 | /// # 响应 27 | /// 28 | /// 当用作响应时,[`Json`] 可以将实现 [`serde::Serialize`] 的类型序列化为 JSON, 29 | /// 并设置响应标头 `Content-Type: application/json`。 30 | /// 31 | /// ``` 32 | /// use boluo::data::Json; 33 | /// use boluo::extract::Path; 34 | /// 35 | /// #[derive(serde::Serialize)] 36 | /// struct User { 37 | /// id: String, 38 | /// username: String, 39 | /// } 40 | /// 41 | /// #[boluo::route("/users/{id}", method = "GET")] 42 | /// async fn get_user(Path(id): Path) -> Json { 43 | /// let user = find_user(&id).await; 44 | /// Json(user) 45 | /// } 46 | /// 47 | /// async fn find_user(id: &str) -> User { 48 | /// todo!() 49 | /// } 50 | /// ``` 51 | #[derive(Debug, Clone, Copy)] 52 | pub struct Json(pub T); 53 | 54 | impl Deref for Json { 55 | type Target = T; 56 | 57 | #[inline] 58 | fn deref(&self) -> &Self::Target { 59 | &self.0 60 | } 61 | } 62 | 63 | impl DerefMut for Json { 64 | #[inline] 65 | fn deref_mut(&mut self) -> &mut Self::Target { 66 | &mut self.0 67 | } 68 | } 69 | 70 | impl Json { 71 | /// 得到内部的值。 72 | #[inline] 73 | pub fn into_inner(this: Self) -> T { 74 | this.0 75 | } 76 | } 77 | 78 | /// 表单提取器和响应。 79 | /// 80 | /// # 提取 81 | /// 82 | /// 当用作提取器时,[`Form`] 可以将请求中的表单数据反序列化为实现 83 | /// [`serde::de::DeserializeOwned`] 的类型。 84 | /// 85 | /// 如果请求具有 `GET` 或 `HEAD` 方法,则会从查询字符串中读取表单数据(与 [`Query`] 相同), 86 | /// 如果请求具有不同的方法,则会从请求体中读取表单数据。 87 | /// 88 | /// ``` 89 | /// use boluo::data::Form; 90 | /// 91 | /// #[derive(serde::Deserialize)] 92 | /// struct CreateUser { 93 | /// username: String, 94 | /// password: String, 95 | /// } 96 | /// 97 | /// #[boluo::route("/users", method = "POST")] 98 | /// async fn create_user(Form(user): Form) { 99 | /// // 创建用户。 100 | /// } 101 | /// ``` 102 | /// 103 | /// # 响应 104 | /// 105 | /// 当用作响应时,[`Form`] 可以将实现 [`serde::Serialize`] 的类型序列化为表单, 106 | /// 并设置响应标头 `Content-Type: application/x-www-form-urlencoded`。 107 | /// 108 | /// ``` 109 | /// use boluo::data::Form; 110 | /// use boluo::extract::Path; 111 | /// 112 | /// #[derive(serde::Serialize)] 113 | /// struct User { 114 | /// id: String, 115 | /// username: String, 116 | /// } 117 | /// 118 | /// #[boluo::route("/users/{id}", method = "GET")] 119 | /// async fn get_user(Path(id): Path) -> Form { 120 | /// let user = find_user(&id).await; 121 | /// Form(user) 122 | /// } 123 | /// 124 | /// async fn find_user(id: &str) -> User { 125 | /// todo!() 126 | /// } 127 | /// ``` 128 | /// 129 | /// [`Query`]: crate::extract::Query 130 | #[derive(Debug, Clone, Copy)] 131 | pub struct Form(pub T); 132 | 133 | impl Deref for Form { 134 | type Target = T; 135 | 136 | #[inline] 137 | fn deref(&self) -> &Self::Target { 138 | &self.0 139 | } 140 | } 141 | 142 | impl DerefMut for Form { 143 | #[inline] 144 | fn deref_mut(&mut self) -> &mut Self::Target { 145 | &mut self.0 146 | } 147 | } 148 | 149 | impl Form { 150 | /// 得到内部的值。 151 | #[inline] 152 | pub fn into_inner(this: Self) -> T { 153 | this.0 154 | } 155 | } 156 | 157 | /// 扩展提取器和响应。 158 | /// 159 | /// # 提取 160 | /// 161 | /// 这通常用于在处理程序之间共享状态。 162 | /// 163 | /// ``` 164 | /// use std::sync::Arc; 165 | /// 166 | /// use boluo::data::Extension; 167 | /// use boluo::route::Router; 168 | /// use boluo::service::ServiceExt; 169 | /// 170 | /// // 在整个应用程序中使用的一些共享状态。 171 | /// struct State { 172 | /// // ... 173 | /// } 174 | /// 175 | /// #[boluo::route("/")] 176 | /// async fn handler(Extension(state): Extension>) { 177 | /// // ... 178 | /// } 179 | /// 180 | /// let state = Arc::new(State { /* ... */ }); 181 | /// 182 | /// Router::new().mount(handler) 183 | /// // 添加中间件,将状态插入到所有传入请求的扩展中。 184 | /// .with(Extension(state)); 185 | /// ``` 186 | /// 187 | /// # 响应 188 | /// 189 | /// 响应扩展可以用于与中间件共享状态。 190 | /// 191 | /// ``` 192 | /// use boluo::data::Extension; 193 | /// use boluo::response::IntoResponse; 194 | /// 195 | /// #[derive(Clone)] 196 | /// struct Foo(&'static str); 197 | /// 198 | /// async fn handler() -> impl IntoResponse { 199 | /// (Extension(Foo("foo")), "Hello, World!") 200 | /// } 201 | /// ``` 202 | #[derive(Debug, Clone, Copy)] 203 | pub struct Extension(pub T); 204 | 205 | impl Deref for Extension { 206 | type Target = T; 207 | 208 | #[inline] 209 | fn deref(&self) -> &Self::Target { 210 | &self.0 211 | } 212 | } 213 | 214 | impl DerefMut for Extension { 215 | #[inline] 216 | fn deref_mut(&mut self) -> &mut Self::Target { 217 | &mut self.0 218 | } 219 | } 220 | 221 | impl Extension { 222 | /// 得到内部的值。 223 | #[inline] 224 | pub fn into_inner(this: Self) -> T { 225 | this.0 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /boluo/src/extract/extension.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use boluo_core::extract::{FromRequest, OptionalFromRequest}; 4 | use boluo_core::request::Request; 5 | 6 | pub use crate::data::Extension; 7 | 8 | impl FromRequest for Extension 9 | where 10 | T: Clone + Send + Sync + 'static, 11 | { 12 | type Error = ExtensionExtractError; 13 | 14 | async fn from_request(req: &mut Request) -> Result { 15 | let opt = Option::>::from_request(req) 16 | .await 17 | .map_err(|e| match e {})?; 18 | opt.ok_or_else(|| ExtensionExtractError::MissingExtension { 19 | name: std::any::type_name::(), 20 | }) 21 | } 22 | } 23 | 24 | impl OptionalFromRequest for Extension 25 | where 26 | T: Clone + Send + Sync + 'static, 27 | { 28 | type Error = Infallible; 29 | 30 | async fn from_request(req: &mut Request) -> Result, Self::Error> { 31 | Ok(req 32 | .extensions() 33 | .get::() 34 | .map(|value| Extension(value.clone()))) 35 | } 36 | } 37 | 38 | /// 扩展提取错误。 39 | #[derive(Debug, Clone, Copy)] 40 | pub enum ExtensionExtractError { 41 | /// 缺少请求扩展。 42 | MissingExtension { 43 | /// 扩展类型名。 44 | name: &'static str, 45 | }, 46 | } 47 | 48 | impl std::fmt::Display for ExtensionExtractError { 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | match self { 51 | ExtensionExtractError::MissingExtension { name } => { 52 | write!(f, "missing request extension `{name}`") 53 | } 54 | } 55 | } 56 | } 57 | 58 | impl std::error::Error for ExtensionExtractError {} 59 | -------------------------------------------------------------------------------- /boluo/src/extract/form.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::BoxError; 2 | use boluo_core::body::Bytes; 3 | use boluo_core::extract::FromRequest; 4 | use boluo_core::http::{HeaderMap, Method, header}; 5 | use boluo_core::request::Request; 6 | use serde::de::DeserializeOwned; 7 | 8 | pub use crate::data::Form; 9 | 10 | use super::{Query, QueryExtractError}; 11 | 12 | impl FromRequest for Form 13 | where 14 | T: DeserializeOwned, 15 | { 16 | type Error = FormExtractError; 17 | 18 | async fn from_request(req: &mut Request) -> Result { 19 | if req.method() == Method::GET || req.method() == Method::HEAD { 20 | Query::from_request(req) 21 | .await 22 | .map(|Query(value)| Form(value)) 23 | .map_err(FormExtractError::from_extract_query_error) 24 | } else { 25 | if !has_content_type(req.headers(), &mime::APPLICATION_WWW_FORM_URLENCODED) { 26 | return Err(FormExtractError::UnsupportedContentType); 27 | } 28 | 29 | let bytes = Bytes::from_request(req) 30 | .await 31 | .map_err(FormExtractError::FailedToBufferBody)?; 32 | 33 | serde_urlencoded::from_bytes::(&bytes) 34 | .map(|value| Form(value)) 35 | .map_err(FormExtractError::FailedToDeserialize) 36 | } 37 | } 38 | } 39 | 40 | fn has_content_type(headers: &HeaderMap, expected_content_type: &mime::Mime) -> bool { 41 | let Some(content_type) = headers.get(header::CONTENT_TYPE) else { 42 | return false; 43 | }; 44 | let Ok(content_type) = content_type.to_str() else { 45 | return false; 46 | }; 47 | 48 | content_type.starts_with(expected_content_type.as_ref()) 49 | } 50 | 51 | /// 表单提取错误。 52 | #[derive(Debug)] 53 | pub enum FormExtractError { 54 | /// 不支持的内容类型。 55 | UnsupportedContentType, 56 | /// 缓冲主体失败。 57 | FailedToBufferBody(BoxError), 58 | /// 反序列化失败。 59 | FailedToDeserialize(serde_urlencoded::de::Error), 60 | } 61 | 62 | impl FormExtractError { 63 | fn from_extract_query_error(error: QueryExtractError) -> Self { 64 | match error { 65 | QueryExtractError::FailedToDeserialize(e) => FormExtractError::FailedToDeserialize(e), 66 | } 67 | } 68 | } 69 | 70 | impl std::fmt::Display for FormExtractError { 71 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 | match self { 73 | FormExtractError::UnsupportedContentType => f.write_str("unsupported content type"), 74 | FormExtractError::FailedToBufferBody(e) => write!(f, "failed to buffer body ({e})"), 75 | FormExtractError::FailedToDeserialize(e) => { 76 | write!(f, "failed to deserialize form ({e})") 77 | } 78 | } 79 | } 80 | } 81 | 82 | impl std::error::Error for FormExtractError {} 83 | -------------------------------------------------------------------------------- /boluo/src/extract/header.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use boluo_core::extract::{FromRequest, OptionalFromRequest}; 4 | use boluo_core::http::HeaderName; 5 | use boluo_core::request::Request; 6 | use headers::{Header, HeaderMapExt}; 7 | 8 | /// 获取请求标头值的提取器。 9 | /// 10 | /// `T` 需要实现 [`Header`]。 11 | /// 12 | /// # 例子 13 | /// 14 | /// ``` 15 | /// use boluo::extract::TypedHeader; 16 | /// use boluo::headers::Host; 17 | /// 18 | /// #[boluo::route("/", method = "GET")] 19 | /// async fn handler(TypedHeader(host): TypedHeader) { 20 | /// // ... 21 | /// } 22 | /// ``` 23 | #[derive(Debug, Clone, Copy)] 24 | pub struct TypedHeader(pub T); 25 | 26 | impl Deref for TypedHeader { 27 | type Target = T; 28 | 29 | #[inline] 30 | fn deref(&self) -> &Self::Target { 31 | &self.0 32 | } 33 | } 34 | 35 | impl DerefMut for TypedHeader { 36 | #[inline] 37 | fn deref_mut(&mut self) -> &mut Self::Target { 38 | &mut self.0 39 | } 40 | } 41 | 42 | impl TypedHeader { 43 | /// 得到内部的值。 44 | #[inline] 45 | pub fn into_inner(this: Self) -> T { 46 | this.0 47 | } 48 | } 49 | 50 | impl FromRequest for TypedHeader 51 | where 52 | T: Header, 53 | { 54 | type Error = TypedHeaderExtractError; 55 | 56 | async fn from_request(req: &mut Request) -> Result { 57 | Option::>::from_request(req) 58 | .await? 59 | .ok_or_else(|| TypedHeaderExtractError::MissingHeader { name: T::name() }) 60 | } 61 | } 62 | 63 | impl OptionalFromRequest for TypedHeader 64 | where 65 | T: Header, 66 | { 67 | type Error = TypedHeaderExtractError; 68 | 69 | async fn from_request(req: &mut Request) -> Result, Self::Error> { 70 | req.headers() 71 | .typed_try_get() 72 | .map(|v| v.map(TypedHeader)) 73 | .map_err(|source| TypedHeaderExtractError::ParseError { 74 | name: T::name(), 75 | source, 76 | }) 77 | } 78 | } 79 | 80 | /// 获取请求标头值错误。 81 | #[derive(Debug)] 82 | pub enum TypedHeaderExtractError { 83 | /// 标头不存在。 84 | MissingHeader { 85 | /// 标头名。 86 | name: &'static HeaderName, 87 | }, 88 | /// 解析错误。 89 | ParseError { 90 | /// 标头名。 91 | name: &'static HeaderName, 92 | /// 错误源。 93 | source: headers::Error, 94 | }, 95 | } 96 | 97 | impl std::fmt::Display for TypedHeaderExtractError { 98 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 99 | match self { 100 | TypedHeaderExtractError::MissingHeader { name } => { 101 | write!(f, "missing request header `{name}`") 102 | } 103 | TypedHeaderExtractError::ParseError { name, source } => { 104 | write!(f, "failed to parse request header `{name}` ({source})") 105 | } 106 | } 107 | } 108 | } 109 | 110 | impl std::error::Error for TypedHeaderExtractError {} 111 | -------------------------------------------------------------------------------- /boluo/src/extract/json.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::BoxError; 2 | use boluo_core::body::Bytes; 3 | use boluo_core::extract::FromRequest; 4 | use boluo_core::http::{HeaderMap, header}; 5 | use boluo_core::request::Request; 6 | use serde::de::DeserializeOwned; 7 | 8 | pub use crate::data::Json; 9 | 10 | impl FromRequest for Json 11 | where 12 | T: DeserializeOwned, 13 | { 14 | type Error = JsonExtractError; 15 | 16 | async fn from_request(req: &mut Request) -> Result { 17 | if !is_json_content_type(req.headers()) { 18 | return Err(JsonExtractError::UnsupportedContentType); 19 | } 20 | 21 | let bytes = Bytes::from_request(req) 22 | .await 23 | .map_err(JsonExtractError::FailedToBufferBody)?; 24 | 25 | serde_json::from_slice::(&bytes) 26 | .map(|value| Json(value)) 27 | .map_err(JsonExtractError::FailedToDeserialize) 28 | } 29 | } 30 | 31 | fn is_json_content_type(headers: &HeaderMap) -> bool { 32 | let Some(content_type) = headers.get(header::CONTENT_TYPE) else { 33 | return false; 34 | }; 35 | let Ok(content_type) = content_type.to_str() else { 36 | return false; 37 | }; 38 | 39 | let Ok(mime) = content_type.parse::() else { 40 | return false; 41 | }; 42 | 43 | let is_json_content_type = mime.type_() == "application" 44 | && (mime.subtype() == "json" || mime.suffix().filter(|name| *name == "json").is_some()); 45 | 46 | is_json_content_type 47 | } 48 | 49 | /// JSON 提取错误。 50 | #[derive(Debug)] 51 | pub enum JsonExtractError { 52 | /// 不支持的内容类型。 53 | UnsupportedContentType, 54 | /// 缓冲主体失败。 55 | FailedToBufferBody(BoxError), 56 | /// 反序列化失败。 57 | FailedToDeserialize(serde_json::Error), 58 | } 59 | 60 | impl std::fmt::Display for JsonExtractError { 61 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 62 | match self { 63 | JsonExtractError::UnsupportedContentType => f.write_str("unsupported content type"), 64 | JsonExtractError::FailedToBufferBody(e) => write!(f, "failed to buffer body ({e})"), 65 | JsonExtractError::FailedToDeserialize(e) => { 66 | write!(f, "failed to deserialize json ({e})") 67 | } 68 | } 69 | } 70 | } 71 | 72 | impl std::error::Error for JsonExtractError {} 73 | -------------------------------------------------------------------------------- /boluo/src/extract/mod.rs: -------------------------------------------------------------------------------- 1 | //! 从请求中提取数据的类型和特征。 2 | 3 | pub use boluo_core::extract::*; 4 | 5 | mod extension; 6 | mod form; 7 | mod header; 8 | mod json; 9 | mod path; 10 | mod query; 11 | 12 | pub use extension::{Extension, ExtensionExtractError}; 13 | pub use form::{Form, FormExtractError}; 14 | pub use header::{TypedHeader, TypedHeaderExtractError}; 15 | pub use json::{Json, JsonExtractError}; 16 | pub use path::{Path, PathExtractError, RawPathParams}; 17 | pub use query::{Query, QueryExtractError, RawQuery}; 18 | -------------------------------------------------------------------------------- /boluo/src/extract/path/mod.rs: -------------------------------------------------------------------------------- 1 | mod de; 2 | 3 | use std::convert::Infallible; 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | use boluo_core::extract::FromRequest; 7 | use boluo_core::request::Request; 8 | use serde::de::DeserializeOwned; 9 | 10 | use crate::route::PathParams; 11 | 12 | /// 获取原始路径参数的提取器,不对路径参数进行解析。 13 | /// 14 | /// # 例子 15 | /// 16 | /// ``` 17 | /// use boluo::extract::RawPathParams; 18 | /// 19 | /// #[boluo::route("/classes/{class_id}/students/{stdnt_id}", method = "GET")] 20 | /// async fn handler(RawPathParams(params): RawPathParams) { 21 | /// for (k, v) in params { 22 | /// println!("{k}: {v}"); 23 | /// } 24 | /// } 25 | /// ``` 26 | #[derive(Debug, Clone)] 27 | pub struct RawPathParams(pub Vec<(String, String)>); 28 | 29 | impl Deref for RawPathParams { 30 | type Target = Vec<(String, String)>; 31 | 32 | #[inline] 33 | fn deref(&self) -> &Self::Target { 34 | &self.0 35 | } 36 | } 37 | 38 | impl DerefMut for RawPathParams { 39 | #[inline] 40 | fn deref_mut(&mut self) -> &mut Self::Target { 41 | &mut self.0 42 | } 43 | } 44 | 45 | impl RawPathParams { 46 | /// 得到内部的值。 47 | #[inline] 48 | pub fn into_inner(this: Self) -> Vec<(String, String)> { 49 | this.0 50 | } 51 | } 52 | 53 | impl FromRequest for RawPathParams { 54 | type Error = Infallible; 55 | 56 | async fn from_request(req: &mut Request) -> Result { 57 | Ok(RawPathParams( 58 | req.extensions() 59 | .get::() 60 | .map(|PathParams(params)| params.clone()) 61 | .unwrap_or_default(), 62 | )) 63 | } 64 | } 65 | 66 | /// 用于获取路径参数的提取器。 67 | /// 68 | /// `T` 需要实现 [`serde::de::DeserializeOwned`]。 69 | /// 70 | /// # 例子 71 | /// 72 | /// ``` 73 | /// use std::collections::HashMap; 74 | /// 75 | /// use boluo::extract::Path; 76 | /// 77 | /// #[derive(serde::Deserialize)] 78 | /// struct Params { 79 | /// class_id: i32, 80 | /// stdnt_id: i32, 81 | /// } 82 | /// 83 | /// // 使用结构体提取路径参数,结构体的字段名需要和路径参数名相同。 84 | /// #[boluo::route("/classes/{class_id}/students/{stdnt_id}", method = "GET")] 85 | /// async fn using_struct(Path(Params { class_id, stdnt_id }): Path) { 86 | /// // ... 87 | /// } 88 | /// 89 | /// // 可以使用 `HashMap` 提取所有路径参数。 90 | /// #[boluo::route("/classes/{class_id}/students/{stdnt_id}", method = "GET")] 91 | /// async fn using_hashmap(Path(hashmap): Path>) { 92 | /// // ... 93 | /// } 94 | /// 95 | /// // 使用元组提取路径参数,元组的大小和路径参数数量必须相同,路径参数将按照顺序解析到元组中。 96 | /// #[boluo::route("/classes/{class_id}/students/{stdnt_id}", method = "GET")] 97 | /// async fn using_tuple(Path((class_id, stdnt_id)): Path<(i32, i32)>) { 98 | /// // ... 99 | /// } 100 | /// 101 | /// // 可以使用 `Vec` 提取所有路径参数,路径参数将按照顺序解析到数组中。 102 | /// #[boluo::route("/classes/{class_id}/students/{stdnt_id}", method = "GET")] 103 | /// async fn using_vec(Path(vec): Path>) { 104 | /// // ... 105 | /// } 106 | /// 107 | /// // 如果路径参数只有一个,使用元组提取时,可以省略元组。 108 | /// #[boluo::route("/classes/{class_id}", method = "GET")] 109 | /// async fn only_one(Path(class_id): Path) { 110 | /// // ... 111 | /// } 112 | /// ``` 113 | #[derive(Debug, Clone, Copy)] 114 | pub struct Path(pub T); 115 | 116 | impl Deref for Path { 117 | type Target = T; 118 | 119 | #[inline] 120 | fn deref(&self) -> &Self::Target { 121 | &self.0 122 | } 123 | } 124 | 125 | impl DerefMut for Path { 126 | #[inline] 127 | fn deref_mut(&mut self) -> &mut Self::Target { 128 | &mut self.0 129 | } 130 | } 131 | 132 | impl Path { 133 | /// 得到内部的值。 134 | #[inline] 135 | pub fn into_inner(this: Self) -> T { 136 | this.0 137 | } 138 | } 139 | 140 | impl FromRequest for Path 141 | where 142 | T: DeserializeOwned, 143 | { 144 | type Error = PathExtractError; 145 | 146 | async fn from_request(req: &mut Request) -> Result { 147 | let path_params = match req.extensions().get::() { 148 | Some(PathParams(path_params)) => &path_params[..], 149 | None => &[], 150 | }; 151 | Ok(Path(T::deserialize(de::PathDeserializer::new( 152 | path_params, 153 | ))?)) 154 | } 155 | } 156 | 157 | /// 路径参数提取错误。 158 | #[derive(Debug, Clone)] 159 | pub enum PathExtractError { 160 | /// 参数数量不正确。 161 | WrongNumberOfParameters { 162 | /// 实际的参数数量。 163 | got: usize, 164 | /// 预期的参数数量。 165 | expected: usize, 166 | }, 167 | /// 尝试反序列化为不受支持的键类型。 168 | UnsupportedKeyType { 169 | /// 键类型名。 170 | name: &'static str, 171 | }, 172 | /// 尝试反序列化为不受支持的值类型。 173 | UnsupportedValueType { 174 | /// 值类型名。 175 | name: &'static str, 176 | }, 177 | /// 解析错误。 178 | ParseError(String), 179 | } 180 | 181 | impl std::fmt::Display for PathExtractError { 182 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 183 | match self { 184 | PathExtractError::WrongNumberOfParameters { got, expected } => write!( 185 | f, 186 | "wrong number of path arguments. expected {expected} but got {got}" 187 | ), 188 | PathExtractError::UnsupportedKeyType { name } => { 189 | write!(f, "unsupported key type `{name}`") 190 | } 191 | PathExtractError::UnsupportedValueType { name } => { 192 | write!(f, "unsupported value type `{name}`") 193 | } 194 | PathExtractError::ParseError(msg) => { 195 | write!(f, "failed to parse path arguments ({msg})") 196 | } 197 | } 198 | } 199 | } 200 | 201 | impl std::error::Error for PathExtractError {} 202 | 203 | impl From for PathExtractError { 204 | fn from(error: de::PathDeserializationError) -> Self { 205 | match error { 206 | de::PathDeserializationError::WrongNumberOfParameters { got, expected } => { 207 | PathExtractError::WrongNumberOfParameters { got, expected } 208 | } 209 | de::PathDeserializationError::UnsupportedKeyType { name } => { 210 | PathExtractError::UnsupportedKeyType { name } 211 | } 212 | de::PathDeserializationError::UnsupportedValueType { name } => { 213 | PathExtractError::UnsupportedValueType { name } 214 | } 215 | _ => PathExtractError::ParseError(error.to_string()), 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /boluo/src/extract/query.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | use boluo_core::extract::FromRequest; 5 | use boluo_core::request::Request; 6 | use serde::de::DeserializeOwned; 7 | 8 | /// 将查询字符串反序列化为某种类型的提取器。 9 | /// 10 | /// `T` 需要实现 [`serde::de::DeserializeOwned`]。 11 | /// 12 | /// # 例子 13 | /// 14 | /// ``` 15 | /// use boluo::extract::Query; 16 | /// 17 | /// #[derive(serde::Deserialize)] 18 | /// struct Pagination { 19 | /// page: usize, 20 | /// per_page: usize, 21 | /// } 22 | /// 23 | /// #[boluo::route("/list_things", method = "GET")] 24 | /// async fn list_things(Query(pagination): Query) { 25 | /// // ... 26 | /// } 27 | /// ``` 28 | #[derive(Debug, Clone, Copy)] 29 | pub struct Query(pub T); 30 | 31 | impl Deref for Query { 32 | type Target = T; 33 | 34 | #[inline] 35 | fn deref(&self) -> &Self::Target { 36 | &self.0 37 | } 38 | } 39 | 40 | impl DerefMut for Query { 41 | #[inline] 42 | fn deref_mut(&mut self) -> &mut Self::Target { 43 | &mut self.0 44 | } 45 | } 46 | 47 | impl Query { 48 | /// 得到内部的值。 49 | #[inline] 50 | pub fn into_inner(this: Self) -> T { 51 | this.0 52 | } 53 | } 54 | 55 | impl FromRequest for Query 56 | where 57 | T: DeserializeOwned, 58 | { 59 | type Error = QueryExtractError; 60 | 61 | async fn from_request(req: &mut Request) -> Result { 62 | let query = req.uri().query().unwrap_or_default(); 63 | serde_urlencoded::from_str::(query) 64 | .map(|value| Query(value)) 65 | .map_err(QueryExtractError::FailedToDeserialize) 66 | } 67 | } 68 | 69 | /// 获取原始查询字符串的提取器,不对查询字符串进行解析。 70 | /// 71 | /// # 例子 72 | /// 73 | /// ``` 74 | /// use boluo::extract::RawQuery; 75 | /// 76 | /// #[boluo::route("/", method = "GET")] 77 | /// async fn handler(RawQuery(query): RawQuery) { 78 | /// // ... 79 | /// } 80 | /// ``` 81 | #[derive(Debug, Clone)] 82 | pub struct RawQuery(pub String); 83 | 84 | impl Deref for RawQuery { 85 | type Target = String; 86 | 87 | #[inline] 88 | fn deref(&self) -> &Self::Target { 89 | &self.0 90 | } 91 | } 92 | 93 | impl DerefMut for RawQuery { 94 | #[inline] 95 | fn deref_mut(&mut self) -> &mut Self::Target { 96 | &mut self.0 97 | } 98 | } 99 | 100 | impl RawQuery { 101 | /// 得到内部的值。 102 | #[inline] 103 | pub fn into_inner(this: Self) -> String { 104 | this.0 105 | } 106 | } 107 | 108 | impl FromRequest for RawQuery { 109 | type Error = Infallible; 110 | 111 | async fn from_request(req: &mut Request) -> Result { 112 | Ok(RawQuery(req.uri().query().unwrap_or_default().to_owned())) 113 | } 114 | } 115 | 116 | /// 查询字符串提取错误。 117 | #[derive(Debug)] 118 | pub enum QueryExtractError { 119 | /// 反序列化错误。 120 | FailedToDeserialize(serde_urlencoded::de::Error), 121 | } 122 | 123 | impl std::fmt::Display for QueryExtractError { 124 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 125 | match self { 126 | QueryExtractError::FailedToDeserialize(e) => { 127 | write!(f, "failed to deserialize query string ({e})") 128 | } 129 | } 130 | } 131 | } 132 | 133 | impl std::error::Error for QueryExtractError {} 134 | -------------------------------------------------------------------------------- /boluo/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `boluo` 是一个简单易用的异步网络框架。 2 | //! 3 | //! # 目录 4 | //! 5 | //! - [快速开始](#快速开始) 6 | //! - [服务](#服务) 7 | //! - [处理程序](#处理程序) 8 | //! - [提取器](#提取器) 9 | //! - [响应](#响应) 10 | //! - [路由](#路由) 11 | //! - [错误处理](#错误处理) 12 | //! - [中间件](#中间件) 13 | //! 14 | //! # 快速开始 15 | //! 16 | //! 新建项目: 17 | //! 18 | //! ```bash 19 | //! cargo new demo && cd demo 20 | //! ``` 21 | //! 22 | //! 添加依赖: 23 | //! 24 | //! ```toml 25 | //! [dependencies] 26 | //! boluo = "0.7" 27 | //! tokio = { version = "1", features = ["full"] } 28 | //! ``` 29 | //! 30 | //! 用以下内容覆盖 `src/main.rs`: 31 | //! 32 | //! ```no_run 33 | //! use boluo::response::IntoResponse; 34 | //! use boluo::route::Router; 35 | //! use boluo::server::Server; 36 | //! use tokio::net::TcpListener; 37 | //! 38 | //! #[tokio::main] 39 | //! async fn main() { 40 | //! let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 41 | //! 42 | //! let app = Router::new().mount(hello); 43 | //! 44 | //! Server::new(listener).run(app).await.unwrap(); 45 | //! } 46 | //! 47 | //! #[boluo::route("/", method = "GET")] 48 | //! async fn hello() -> impl IntoResponse { 49 | //! "Hello, World!" 50 | //! } 51 | //! ``` 52 | //! 53 | //! 运行项目: 54 | //! 55 | //! ```bash 56 | //! cargo run 57 | //! ``` 58 | //! 59 | //! 访问服务: 60 | //! 61 | //! ```bash 62 | //! curl http://127.0.0.1:3000/ 63 | //! ``` 64 | //! 65 | //! # 服务 66 | //! 67 | //! [`Service`] 特征表示一个接收请求并返回响应的异步函数。 68 | //! 69 | //! # 处理程序 70 | //! 71 | //! 处理程序是一个异步函数,它接受零个或多个提取器作为参数,并返回可以转换为响应的内容。 72 | //! 73 | //! 处理程序如下所示: 74 | //! 75 | //! ``` 76 | //! use boluo::body::Body; 77 | //! use boluo::handler::handler_fn; 78 | //! use boluo::response::IntoResponse; 79 | //! 80 | //! // 返回空的 `200 OK` 响应的处理程序。 81 | //! async fn empty() {} 82 | //! 83 | //! // 返回带有纯文本主体的 `200 OK` 响应的处理程序。 84 | //! async fn hello() -> &'static str { 85 | //! "Hello, World!" 86 | //! } 87 | //! 88 | //! // 返回带有请求主体的 `200 OK` 响应的处理程序。 89 | //! // 90 | //! // `Body` 实现了 `FromRequest` 特征,可以作为提取器解析请求。并且也实现了 91 | //! // `IntoResponse` 特征,可以作为响应类型。 92 | //! async fn echo(body: Body) -> impl IntoResponse { 93 | //! body 94 | //! } 95 | //! 96 | //! // 使用 `handler_fn` 函数将处理程序转换为 `Service`。 97 | //! let service = handler_fn(echo); 98 | //! ``` 99 | //! 100 | //! # 提取器 101 | //! 102 | //! 提取器是实现了 [`FromRequest`] 特征的类型,可以根据 [`Request`] 创建实例。 103 | //! 104 | //! ``` 105 | //! use std::convert::Infallible; 106 | //! 107 | //! use boluo::extract::FromRequest; 108 | //! use boluo::http::{header, HeaderValue}; 109 | //! use boluo::request::Request; 110 | //! 111 | //! // 从请求头中提取 HOST 的提取器。 112 | //! struct Host(Option); 113 | //! 114 | //! // 为提取器实现 `FromRequest` 特征。 115 | //! impl FromRequest for Host { 116 | //! type Error = Infallible; 117 | //! 118 | //! async fn from_request(req: &mut Request) -> Result { 119 | //! let value = req.headers().get(header::HOST).map(|v| v.to_owned()); 120 | //! Ok(Host(value)) 121 | //! } 122 | //! } 123 | //! 124 | //! // 在处理程序中使用提取器从请求中提取数据。 125 | //! async fn using_extractor(Host(host): Host) { 126 | //! println!("{host:?}") 127 | //! } 128 | //! ``` 129 | //! 130 | //! # 响应 131 | //! 132 | //! 任何实现 [`IntoResponse`] 特征的类型都可以作为响应。 133 | //! 134 | //! ``` 135 | //! use boluo::response::Html; 136 | //! use boluo::response::IntoResponse; 137 | //! 138 | //! // 返回带有纯文本主体的 `200 OK` 响应的处理程序。 139 | //! async fn hello() -> &'static str { 140 | //! "Hello, World!" 141 | //! } 142 | //! 143 | //! // 返回显示 `Hello, World!` 的 HTML 页面的处理程序。 144 | //! async fn html() -> impl IntoResponse { 145 | //! Html("Hello, World!") 146 | //! } 147 | //! ``` 148 | //! 149 | //! # 路由 150 | //! 151 | //! [`Router`] 用于设置哪些路径通向哪些服务。 152 | //! 153 | //! ``` 154 | //! use boluo::handler::handler_fn; 155 | //! use boluo::route::Router; 156 | //! 157 | //! #[boluo::route("/f", method = "GET")] 158 | //! async fn f() -> &'static str { 159 | //! "f" 160 | //! } 161 | //! 162 | //! let ab = Router::new() 163 | //! .route("/a", handler_fn(|| async { "a" })) 164 | //! .route("/b", handler_fn(|| async { "b" })); 165 | //! 166 | //! let cd = Router::new() 167 | //! .route("/c", handler_fn(|| async { "c" })) 168 | //! .route("/d", handler_fn(|| async { "d" })); 169 | //! 170 | //! Router::new() 171 | //! // 路由。 172 | //! .route("/a", handler_fn(|| async { "a" })) 173 | //! .route("/b", handler_fn(|| async { "b" })) 174 | //! // 嵌套路由。 175 | //! .scope("/x", ab) 176 | //! // 将其他路由器的路由合并到当前路由器。 177 | //! .merge(cd) 178 | //! // 挂载宏定义路由。 179 | //! .mount(f); 180 | //! ``` 181 | //! 182 | //! # 错误处理 183 | //! 184 | //! 错误和响应是分离的,可以在中间件对特定错误进行捕获,并将错误转换为自己想要的响应格式。 185 | //! 186 | //! 未经处理的错误到达服务器时,服务器将返回带有错误信息的 `500 INTERNAL_SERVER_ERROR` 响应。 187 | //! 188 | //! ``` 189 | //! use boluo::http::StatusCode; 190 | //! use boluo::response::{IntoResponse, Response}; 191 | //! use boluo::route::{RouteError, RouteErrorKind, Router}; 192 | //! use boluo::service::ServiceExt; 193 | //! use boluo::BoxError; 194 | //! 195 | //! #[derive(Debug)] 196 | //! struct MyError; 197 | //! 198 | //! impl std::fmt::Display for MyError { 199 | //! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 200 | //! write!(f, "some error message") 201 | //! } 202 | //! } 203 | //! 204 | //! impl std::error::Error for MyError {} 205 | //! 206 | //! // 处理程序。 207 | //! #[boluo::route("/", method = "GET")] 208 | //! async fn throw_error() -> Result<(), MyError> { 209 | //! Err(MyError) 210 | //! } 211 | //! 212 | //! // 错误处理。 213 | //! async fn handle_error(err: BoxError) -> Result { 214 | //! // 处理框架抛出的路由错误,并自定义响应方式。 215 | //! if let Some(e) = err.downcast_ref::() { 216 | //! let status_code = match e.kind() { 217 | //! RouteErrorKind::NotFound => StatusCode::NOT_FOUND, 218 | //! RouteErrorKind::MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED, 219 | //! }; 220 | //! return Ok((status_code, format!("{status_code}")).into_response()?); 221 | //! } 222 | //! if let Some(_) = err.downcast_ref::() { 223 | //! // 记录错误、转为响应等等。 224 | //! } 225 | //! Err(err) 226 | //! } 227 | //! 228 | //! Router::new().mount(throw_error).or_else(handle_error); 229 | //! ``` 230 | //! 231 | //! # 中间件 232 | //! 233 | //! 中间件是实现了 [`Middleware`] 特征的类型,可以调用 [`ServiceExt::with`] 函数将中间件 234 | //! 应用于 [`Service`]。 235 | //! 236 | //! 中间件实际上是将原始服务转换为新的服务。 237 | //! 238 | //! ``` 239 | //! use boluo::data::Extension; 240 | //! use boluo::handler::handler_fn; 241 | //! use boluo::service::ServiceExt; 242 | //! 243 | //! async fn extension(Extension(text): Extension<&'static str>) -> &'static str { 244 | //! text 245 | //! } 246 | //! 247 | //! let service = handler_fn(extension); 248 | //! let service = service.with(Extension("Hello, World!")); 249 | //! ``` 250 | //! 251 | //! [`Service`]: crate::service::Service 252 | //! [`FromRequest`]: crate::extract::FromRequest 253 | //! [`Request`]: crate::request::Request 254 | //! [`IntoResponse`]: crate::response::IntoResponse 255 | //! [`Router`]: crate::route::Router 256 | //! [`Middleware`]: crate::middleware::Middleware 257 | //! [`ServiceExt::with`]: crate::service::ServiceExt::with 258 | 259 | #![forbid(unsafe_code)] 260 | #![warn( 261 | missing_debug_implementations, 262 | missing_docs, 263 | rust_2018_idioms, 264 | unreachable_pub 265 | )] 266 | #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] 267 | 268 | pub use boluo_core::BoxError; 269 | pub use boluo_core::{body, handler, http, request, service, upgrade}; 270 | 271 | pub use boluo_macros::route; 272 | 273 | pub mod data; 274 | pub mod extract; 275 | pub mod listener; 276 | pub mod middleware; 277 | pub mod response; 278 | pub mod route; 279 | 280 | pub use headers; 281 | 282 | #[cfg(any(feature = "http1", feature = "http2"))] 283 | pub mod server; 284 | 285 | #[cfg(feature = "multipart")] 286 | pub mod multipart; 287 | 288 | #[cfg(feature = "ws")] 289 | pub mod ws; 290 | 291 | #[cfg(feature = "static-file")] 292 | pub mod static_file; 293 | -------------------------------------------------------------------------------- /boluo/src/listener.rs: -------------------------------------------------------------------------------- 1 | //! 监听器的特征和相关类型的定义。 2 | 3 | use std::net::SocketAddr; 4 | 5 | /// 连接信息。 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 7 | pub struct ConnectInfo { 8 | /// 连接的本地地址。 9 | pub local: SocketAddr, 10 | /// 连接的远程地址。 11 | pub remote: SocketAddr, 12 | } 13 | 14 | /// 表示可以提供连接的类型,用于实现监听器。 15 | pub trait Listener { 16 | /// 监听器返回的连接。 17 | type IO; 18 | 19 | /// 监听器返回的连接地址。 20 | type Addr; 21 | 22 | /// 监听器产生的错误。 23 | type Error; 24 | 25 | /// 接收此监听器新传入的连接。 26 | fn accept( 27 | &mut self, 28 | ) -> impl Future> + Send; 29 | } 30 | 31 | #[cfg(feature = "tokio")] 32 | impl Listener for tokio::net::TcpListener { 33 | type IO = tokio::net::TcpStream; 34 | type Addr = ConnectInfo; 35 | type Error = std::io::Error; 36 | 37 | async fn accept(&mut self) -> std::io::Result<(Self::IO, Self::Addr)> { 38 | tokio::net::TcpListener::accept(self) 39 | .await 40 | .and_then(|(conn, remote)| { 41 | self.local_addr() 42 | .map(|local| (conn, ConnectInfo { local, remote })) 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /boluo/src/middleware/extension.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::middleware::Middleware; 2 | use boluo_core::request::Request; 3 | use boluo_core::service::Service; 4 | 5 | pub use crate::extract::Extension; 6 | 7 | impl Middleware for Extension { 8 | type Service = ExtensionService; 9 | 10 | fn transform(self, service: S) -> Self::Service { 11 | ExtensionService { 12 | service, 13 | value: Extension::into_inner(self), 14 | } 15 | } 16 | } 17 | 18 | /// 中间件 [`Extension`] 返回的服务。 19 | #[derive(Debug, Clone, Copy)] 20 | pub struct ExtensionService { 21 | service: S, 22 | value: T, 23 | } 24 | 25 | impl Service for ExtensionService 26 | where 27 | S: Service, 28 | T: Clone + Send + Sync + 'static, 29 | { 30 | type Response = S::Response; 31 | type Error = S::Error; 32 | 33 | async fn call(&self, mut req: Request) -> Result { 34 | req.extensions_mut().insert(self.value.clone()); 35 | self.service.call(req).await 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /boluo/src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | //! 中间件的特征和相关类型的定义。 2 | 3 | pub use boluo_core::middleware::*; 4 | 5 | mod extension; 6 | 7 | pub use extension::{Extension, ExtensionService}; 8 | -------------------------------------------------------------------------------- /boluo/src/multipart.rs: -------------------------------------------------------------------------------- 1 | //! 用于解析文件上传中常用的 `multipart/form-data` 格式数据。 2 | 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use boluo_core::body::Bytes; 7 | use boluo_core::extract::FromRequest; 8 | use boluo_core::http::HeaderMap; 9 | use boluo_core::http::header::CONTENT_TYPE; 10 | use boluo_core::request::Request; 11 | use futures_util::Stream; 12 | 13 | /// 解析 `multipart/form-data` 请求的提取器。 14 | /// 15 | /// # 例子 16 | /// 17 | /// ``` 18 | /// use boluo::multipart::Multipart; 19 | /// 20 | /// #[boluo::route("/upload", method = "POST")] 21 | /// async fn upload(mut multipart: Multipart) { 22 | /// while let Ok(Some(field)) = multipart.next_field().await { 23 | /// let name = field.name().unwrap_or_default().to_owned(); 24 | /// let data = field.bytes().await.unwrap(); 25 | /// 26 | /// println!("Length of `{}` is {} bytes", name, data.len()); 27 | /// } 28 | /// } 29 | /// ``` 30 | #[derive(Debug)] 31 | pub struct Multipart { 32 | inner: multer::Multipart<'static>, 33 | } 34 | 35 | impl Multipart { 36 | fn parse_boundary(headers: &HeaderMap) -> Option { 37 | headers 38 | .get(&CONTENT_TYPE)? 39 | .to_str() 40 | .ok() 41 | .and_then(|content_type| multer::parse_boundary(content_type).ok()) 42 | } 43 | } 44 | 45 | impl Multipart { 46 | /// 生成下一个 [`Field`]。 47 | pub async fn next_field(&mut self) -> Result, MultipartError> { 48 | let field = self 49 | .inner 50 | .next_field() 51 | .await 52 | .map_err(MultipartError::from_multer)?; 53 | 54 | Ok(field.map(|f| Field { inner: f })) 55 | } 56 | } 57 | 58 | impl Stream for Multipart { 59 | type Item = Result; 60 | 61 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 62 | std::pin::pin!(self.next_field()) 63 | .poll(cx) 64 | .map(|f| f.transpose()) 65 | } 66 | } 67 | 68 | impl FromRequest for Multipart { 69 | type Error = MultipartError; 70 | 71 | async fn from_request(req: &mut Request) -> Result { 72 | let Some(boundary) = Self::parse_boundary(req.headers()) else { 73 | return Err(MultipartError::UnsupportedContentType); 74 | }; 75 | 76 | let stream = std::mem::take(req.body_mut()).into_data_stream(); 77 | 78 | Ok(Self { 79 | inner: multer::Multipart::new(stream, boundary), 80 | }) 81 | } 82 | } 83 | 84 | /// 多部分流中的单个字段。 85 | #[derive(Debug)] 86 | pub struct Field { 87 | inner: multer::Field<'static>, 88 | } 89 | 90 | impl Field { 91 | /// 在 `Content-Disposition` 标头中找到的字段名。 92 | pub fn name(&self) -> Option<&str> { 93 | self.inner.name() 94 | } 95 | 96 | /// 在 `Content-Disposition` 标头中找到的文件名。 97 | pub fn file_name(&self) -> Option<&str> { 98 | self.inner.file_name() 99 | } 100 | 101 | /// 获取字段的内容类型。 102 | pub fn content_type(&self) -> Option<&str> { 103 | self.inner.content_type().map(|m| m.as_ref()) 104 | } 105 | 106 | /// 获取字段的标头集。 107 | pub fn headers(&self) -> &HeaderMap { 108 | self.inner.headers() 109 | } 110 | 111 | /// 以二进制的形式获取字段的完整数据。 112 | pub async fn bytes(self) -> Result { 113 | self.inner 114 | .bytes() 115 | .await 116 | .map_err(MultipartError::from_multer) 117 | } 118 | 119 | /// 以文本的形式获取完整的字段数据。 120 | pub async fn text(self) -> Result { 121 | self.inner.text().await.map_err(MultipartError::from_multer) 122 | } 123 | 124 | /// 流式获取字段的数据块。 125 | pub async fn chunk(&mut self) -> Result, MultipartError> { 126 | self.inner 127 | .chunk() 128 | .await 129 | .map_err(MultipartError::from_multer) 130 | } 131 | } 132 | 133 | impl Stream for Field { 134 | type Item = Result; 135 | 136 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 137 | Pin::new(&mut self.inner) 138 | .poll_next(cx) 139 | .map_err(MultipartError::from_multer) 140 | } 141 | } 142 | 143 | /// `multipart/form-data` 解析错误。 144 | #[derive(Debug, Clone)] 145 | pub enum MultipartError { 146 | /// 不支持的内容类型。 147 | UnsupportedContentType, 148 | /// 解析错误。 149 | ParseError(String), 150 | } 151 | 152 | impl MultipartError { 153 | fn from_multer(error: multer::Error) -> Self { 154 | MultipartError::ParseError(error.to_string()) 155 | } 156 | } 157 | 158 | impl std::fmt::Display for MultipartError { 159 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 160 | match self { 161 | MultipartError::UnsupportedContentType => f.write_str("unsupported content type"), 162 | MultipartError::ParseError(e) => { 163 | write!(f, "failed to parse `multipart/form-data` request ({e})") 164 | } 165 | } 166 | } 167 | } 168 | 169 | impl std::error::Error for MultipartError {} 170 | -------------------------------------------------------------------------------- /boluo/src/response/extension.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use boluo_core::response::{IntoResponseParts, ResponseParts}; 4 | 5 | pub use crate::data::Extension; 6 | 7 | impl IntoResponseParts for Extension 8 | where 9 | T: Clone + Send + Sync + 'static, 10 | { 11 | type Error = Infallible; 12 | 13 | fn into_response_parts(self, mut parts: ResponseParts) -> Result { 14 | parts.extensions.insert(self.0); 15 | Ok(parts) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /boluo/src/response/form.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::body::Body; 2 | use boluo_core::http::{HeaderValue, header}; 3 | use boluo_core::response::{IntoResponse, Response}; 4 | use serde::Serialize; 5 | 6 | pub use crate::data::Form; 7 | 8 | impl IntoResponse for Form 9 | where 10 | T: Serialize, 11 | { 12 | type Error = FormResponseError; 13 | 14 | fn into_response(self) -> Result { 15 | let data = match serde_urlencoded::to_string(&self.0) { 16 | Ok(data) => data, 17 | Err(e) => return Err(FormResponseError::FailedToSerialize(e)), 18 | }; 19 | let mut res = Response::new(Body::from(data)); 20 | res.headers_mut().insert( 21 | header::CONTENT_TYPE, 22 | HeaderValue::from_static(mime::APPLICATION_WWW_FORM_URLENCODED.as_ref()), 23 | ); 24 | Ok(res) 25 | } 26 | } 27 | 28 | /// 表单响应错误。 29 | #[derive(Debug)] 30 | pub enum FormResponseError { 31 | /// 序列化错误。 32 | FailedToSerialize(serde_urlencoded::ser::Error), 33 | } 34 | 35 | impl std::fmt::Display for FormResponseError { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | match self { 38 | FormResponseError::FailedToSerialize(e) => { 39 | write!(f, "failed to serialize form ({e})") 40 | } 41 | } 42 | } 43 | } 44 | 45 | impl std::error::Error for FormResponseError {} 46 | -------------------------------------------------------------------------------- /boluo/src/response/html.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::http::{HeaderValue, header}; 2 | use boluo_core::response::{IntoResponse, Response}; 3 | 4 | /// HTML响应。 5 | /// 6 | /// 设置响应标头 `Content-Type: text/html; charset=utf-8`。 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct Html(pub T); 9 | 10 | impl IntoResponse for Html 11 | where 12 | T: IntoResponse, 13 | { 14 | type Error = T::Error; 15 | 16 | fn into_response(self) -> Result { 17 | let mut res = self.0.into_response()?; 18 | res.headers_mut().insert( 19 | header::CONTENT_TYPE, 20 | HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()), 21 | ); 22 | Ok(res) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /boluo/src/response/json.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::body::Body; 2 | use boluo_core::http::{HeaderValue, header}; 3 | use boluo_core::response::{IntoResponse, Response}; 4 | use serde::Serialize; 5 | 6 | pub use crate::data::Json; 7 | 8 | impl IntoResponse for Json 9 | where 10 | T: Serialize, 11 | { 12 | type Error = JsonResponseError; 13 | 14 | fn into_response(self) -> Result { 15 | let data = match serde_json::to_vec(&self.0) { 16 | Ok(data) => data, 17 | Err(e) => return Err(JsonResponseError::FailedToSerialize(e)), 18 | }; 19 | let mut res = Response::new(Body::from(data)); 20 | res.headers_mut().insert( 21 | header::CONTENT_TYPE, 22 | HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), 23 | ); 24 | Ok(res) 25 | } 26 | } 27 | 28 | /// JSON 响应错误。 29 | #[derive(Debug)] 30 | pub enum JsonResponseError { 31 | /// 序列化错误。 32 | FailedToSerialize(serde_json::Error), 33 | } 34 | 35 | impl std::fmt::Display for JsonResponseError { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | match self { 38 | JsonResponseError::FailedToSerialize(e) => { 39 | write!(f, "failed to serialize json ({e})") 40 | } 41 | } 42 | } 43 | } 44 | 45 | impl std::error::Error for JsonResponseError {} 46 | -------------------------------------------------------------------------------- /boluo/src/response/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP 响应。 2 | 3 | pub use boluo_core::response::*; 4 | 5 | #[cfg(feature = "sse")] 6 | pub mod sse; 7 | 8 | mod extension; 9 | mod form; 10 | mod html; 11 | mod json; 12 | mod redirect; 13 | 14 | pub use extension::Extension; 15 | pub use form::{Form, FormResponseError}; 16 | pub use html::Html; 17 | pub use json::{Json, JsonResponseError}; 18 | pub use redirect::{Redirect, RedirectUriError}; 19 | -------------------------------------------------------------------------------- /boluo/src/response/redirect.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use boluo_core::body::Body; 4 | use boluo_core::http::{HeaderValue, StatusCode, header::LOCATION}; 5 | use boluo_core::response::{IntoResponse, Response}; 6 | 7 | /// 将请求重定向到另一个位置的响应。 8 | #[derive(Debug, Clone)] 9 | pub struct Redirect { 10 | status_code: StatusCode, 11 | location: HeaderValue, 12 | } 13 | 14 | impl Redirect { 15 | /// Create a new [`Redirect`] that uses a [`303 See Other`][mdn] status code. 16 | /// 17 | /// This redirect instructs the client to change the method to GET for the subsequent request 18 | /// to the given `uri`, which is useful after successful form submission, file upload or when 19 | /// you generally don't want the redirected-to page to observe the original request method and 20 | /// body (if non-empty). If you want to preserve the request method and body, 21 | /// [`Redirect::temporary`] should be used instead. 22 | /// 23 | /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303 24 | pub fn to(uri: &str) -> Result { 25 | Self::with_status_code(StatusCode::SEE_OTHER, uri) 26 | } 27 | 28 | /// Create a new [`Redirect`] that uses a [`307 Temporary Redirect`][mdn] status code. 29 | /// 30 | /// This has the same behavior as [`Redirect::to`], except it will preserve the original HTTP 31 | /// method and body. 32 | /// 33 | /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307 34 | pub fn temporary(uri: &str) -> Result { 35 | Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri) 36 | } 37 | 38 | /// Create a new [`Redirect`] that uses a [`308 Permanent Redirect`][mdn] status code. 39 | /// 40 | /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308 41 | pub fn permanent(uri: &str) -> Result { 42 | Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri) 43 | } 44 | 45 | // This is intentionally not public since other kinds of redirects might not 46 | // use the `Location` header, namely `304 Not Modified`. 47 | fn with_status_code(status_code: StatusCode, uri: &str) -> Result { 48 | debug_assert!( 49 | status_code.is_redirection(), 50 | "not a redirection status code" 51 | ); 52 | 53 | HeaderValue::try_from(uri) 54 | .map_err(|_| RedirectUriError { _priv: () }) 55 | .map(|location| Self { 56 | status_code, 57 | location, 58 | }) 59 | } 60 | } 61 | 62 | impl IntoResponse for Redirect { 63 | type Error = Infallible; 64 | 65 | fn into_response(self) -> Result { 66 | Response::builder() 67 | .status(self.status_code) 68 | .header(LOCATION, self.location) 69 | .body(Body::empty()) 70 | .map_err(|e| unreachable!("{e}")) 71 | } 72 | } 73 | 74 | /// 重定向的 URI 不是有效的标头值。 75 | pub struct RedirectUriError { 76 | _priv: (), 77 | } 78 | 79 | impl std::fmt::Debug for RedirectUriError { 80 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 81 | f.debug_struct("RedirectUriError").finish() 82 | } 83 | } 84 | 85 | impl std::fmt::Display for RedirectUriError { 86 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 | write!(f, "redirect uri isn't a valid header value") 88 | } 89 | } 90 | 91 | impl std::error::Error for RedirectUriError {} 92 | -------------------------------------------------------------------------------- /boluo/src/response/sse/event.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, time::Duration}; 2 | 3 | /// 服务器发送的事件。 4 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] 5 | pub struct Event { 6 | comment: Option>, 7 | retry: Option, 8 | id: Option>, 9 | event: Option>, 10 | data: Option>, 11 | } 12 | 13 | impl Event { 14 | /// 创建新的构建器以构建事件。 15 | /// 16 | /// # 例子 17 | /// 18 | /// ``` 19 | /// use std::time::Duration; 20 | /// 21 | /// use boluo::response::sse::Event; 22 | /// 23 | /// let event = Event::builder() 24 | /// .data("hello world") 25 | /// .build() 26 | /// .unwrap(); 27 | /// 28 | /// let event_string = format!("{event}"); 29 | /// 30 | /// assert_eq!(event_string, "data: hello world\n\n"); 31 | /// ``` 32 | pub fn builder() -> EventBuilder { 33 | EventBuilder::new() 34 | } 35 | } 36 | 37 | impl std::fmt::Display for Event { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | if let Some(comment) = &self.comment { 40 | writeln!(f, ": {comment}")?; 41 | } 42 | if let Some(retry) = &self.retry { 43 | writeln!(f, "retry: {}", retry.as_millis())?; 44 | } 45 | if let Some(id) = &self.id { 46 | writeln!(f, "id: {id}")?; 47 | } 48 | if let Some(event) = &self.event { 49 | writeln!(f, "event: {event}")?; 50 | } 51 | if let Some(data) = &self.data { 52 | for line in data.lines() { 53 | writeln!(f, "data: {line}")?; 54 | } 55 | } 56 | writeln!(f) 57 | } 58 | } 59 | 60 | /// 事件的构建器。 61 | #[derive(Debug)] 62 | pub struct EventBuilder { 63 | inner: Result, 64 | } 65 | 66 | impl EventBuilder { 67 | /// 创建构建器的默认实例以构建事件。 68 | /// 69 | /// # 例子 70 | /// 71 | /// ``` 72 | /// use std::time::Duration; 73 | /// 74 | /// use boluo::response::sse::EventBuilder; 75 | /// 76 | /// let event = EventBuilder::new() 77 | /// .data("hello world") 78 | /// .build() 79 | /// .unwrap(); 80 | /// 81 | /// let event_string = format!("{event}"); 82 | /// 83 | /// assert_eq!(event_string, "data: hello world\n\n"); 84 | /// ``` 85 | pub fn new() -> Self { 86 | Self { 87 | inner: Ok(Event::default()), 88 | } 89 | } 90 | 91 | /// 设置事件的注释字段。 92 | /// 93 | /// # 错误 94 | /// 95 | /// 如果设置的值包含换行符或回车符,则调用 [`EventBuilder::build`] 将返回错误。 96 | pub fn comment(self, value: impl Into>) -> Self { 97 | self.and_then(|mut event| { 98 | Self::not_contains_newlines_or_carriage_returns(value).map(|value| { 99 | event.comment = Some(value); 100 | event 101 | }) 102 | }) 103 | } 104 | 105 | /// 设置事件的重试超时字段。 106 | pub fn retry(self, value: Duration) -> Self { 107 | Self { 108 | inner: self.inner.map(|mut event| { 109 | event.retry = Some(value); 110 | event 111 | }), 112 | } 113 | } 114 | 115 | /// 设置事件的标识符字段。 116 | /// 117 | /// # 错误 118 | /// 119 | /// 如果设置的值包含换行符或回车符,则调用 [`EventBuilder::build`] 将返回错误。 120 | pub fn id(self, value: impl Into>) -> Self { 121 | self.and_then(|mut event| { 122 | Self::not_contains_newlines_or_carriage_returns(value).map(|value| { 123 | event.id = Some(value); 124 | event 125 | }) 126 | }) 127 | } 128 | 129 | /// 设置事件的名称字段。 130 | /// 131 | /// # 错误 132 | /// 133 | /// 如果设置的值包含换行符或回车符,则调用 [`EventBuilder::build`] 将返回错误。 134 | pub fn event(self, value: impl Into>) -> Self { 135 | self.and_then(|mut event| { 136 | Self::not_contains_newlines_or_carriage_returns(value).map(|value| { 137 | event.event = Some(value); 138 | event 139 | }) 140 | }) 141 | } 142 | 143 | /// 设置事件的数据字段。 144 | /// 145 | /// # 错误 146 | /// 147 | /// 如果设置的值包含回车符,则调用 [`EventBuilder::build`] 将返回错误。 148 | pub fn data(self, value: impl Into>) -> Self { 149 | self.and_then(|mut event| { 150 | let value: Cow<'static, str> = value.into(); 151 | Self::not_contains_carriage_returns(value).map(|value| { 152 | event.data = Some(value); 153 | event 154 | }) 155 | }) 156 | } 157 | 158 | /// 消耗构建器,构建事件。 159 | /// 160 | /// # 错误 161 | /// 162 | /// 如果之前配置的任意一个参数发生错误,则在调用此函数时将返回错误。 163 | pub fn build(self) -> Result { 164 | self.inner 165 | } 166 | 167 | fn and_then(self, func: F) -> Self 168 | where 169 | F: FnOnce(Event) -> Result, 170 | { 171 | Self { 172 | inner: self.inner.and_then(func), 173 | } 174 | } 175 | 176 | fn not_contains_newlines_or_carriage_returns( 177 | value: impl Into>, 178 | ) -> Result, EventValueError> { 179 | let value = value.into(); 180 | if memchr::memchr2(b'\r', b'\n', value.as_bytes()).is_none() { 181 | Ok(value) 182 | } else { 183 | Err(EventValueError { _priv: () }) 184 | } 185 | } 186 | 187 | fn not_contains_carriage_returns( 188 | value: impl Into>, 189 | ) -> Result, EventValueError> { 190 | let value = value.into(); 191 | if memchr::memchr(b'\r', value.as_bytes()).is_none() { 192 | Ok(value) 193 | } else { 194 | Err(EventValueError { _priv: () }) 195 | } 196 | } 197 | } 198 | 199 | impl Default for EventBuilder { 200 | fn default() -> Self { 201 | Self::new() 202 | } 203 | } 204 | 205 | /// SSE 事件值不能包含换行或回车。 206 | pub struct EventValueError { 207 | _priv: (), 208 | } 209 | 210 | impl std::fmt::Debug for EventValueError { 211 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 212 | f.debug_struct("EventValueError").finish() 213 | } 214 | } 215 | 216 | impl std::fmt::Display for EventValueError { 217 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 218 | write!( 219 | f, 220 | "SSE event value cannot contain newlines or carriage returns" 221 | ) 222 | } 223 | } 224 | 225 | impl std::error::Error for EventValueError {} 226 | 227 | #[cfg(test)] 228 | mod tests { 229 | use std::time::Duration; 230 | 231 | use super::Event; 232 | 233 | #[test] 234 | fn comment() { 235 | let event = Event::builder().comment("xx").build().unwrap(); 236 | assert_eq!(format!("{event}"), ": xx\n\n"); 237 | } 238 | 239 | #[test] 240 | fn retry() { 241 | let event = Event::builder() 242 | .retry(Duration::from_secs(1)) 243 | .build() 244 | .unwrap(); 245 | assert_eq!(format!("{event}"), "retry: 1000\n\n"); 246 | } 247 | 248 | #[test] 249 | fn id() { 250 | let event = Event::builder().id("1").build().unwrap(); 251 | assert_eq!(format!("{event}"), "id: 1\n\n"); 252 | } 253 | 254 | #[test] 255 | fn event() { 256 | let event = Event::builder().event("message").build().unwrap(); 257 | assert_eq!(format!("{event}"), "event: message\n\n"); 258 | } 259 | 260 | #[test] 261 | fn data() { 262 | let event = Event::builder().data("hello\nworld\n").build().unwrap(); 263 | assert_eq!(format!("{event}"), "data: hello\ndata: world\n\n"); 264 | } 265 | 266 | #[test] 267 | fn all() { 268 | let event = Event::builder() 269 | .comment("xx") 270 | .retry(Duration::from_secs(1)) 271 | .id("1") 272 | .event("message") 273 | .data("hello\nworld\n") 274 | .build() 275 | .unwrap(); 276 | assert_eq!( 277 | format!("{event}"), 278 | ": xx\nretry: 1000\nid: 1\nevent: message\ndata: hello\ndata: world\n\n" 279 | ); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /boluo/src/response/sse/keep_alive.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | use std::time::Duration; 5 | 6 | use futures_util::ready; 7 | use tokio::time::{Instant, Sleep}; 8 | 9 | use super::{Event, EventValueError}; 10 | 11 | /// 用于配置保持连接的消息间隔和消息文本。 12 | #[derive(Debug, Clone)] 13 | pub struct KeepAlive { 14 | event: Event, 15 | interval: Duration, 16 | } 17 | 18 | impl KeepAlive { 19 | /// 创建一个新的 [`KeepAlive`] 实例。 20 | pub fn new() -> Self { 21 | Default::default() 22 | } 23 | 24 | /// 自定义保持连接的消息间隔。 25 | /// 26 | /// 默认值为15秒。 27 | pub fn interval(mut self, time: Duration) -> Self { 28 | self.interval = time; 29 | self 30 | } 31 | 32 | /// 自定义保持连接的消息文本。 33 | /// 34 | /// 默认为空注释。 35 | pub fn text(mut self, text: impl Into>) -> Result { 36 | Event::builder().comment(text).build().map(|event| { 37 | self.event = event; 38 | self 39 | }) 40 | } 41 | 42 | /// 自定义保持连接的消息事件。 43 | /// 44 | /// 默认为空注释。 45 | pub fn event(mut self, event: Event) -> Self { 46 | self.event = event; 47 | self 48 | } 49 | } 50 | 51 | impl Default for KeepAlive { 52 | fn default() -> Self { 53 | Self { 54 | event: Event::builder().comment("").build().unwrap(), 55 | interval: Duration::from_secs(15), 56 | } 57 | } 58 | } 59 | 60 | pin_project_lite::pin_project! { 61 | pub(super) struct KeepAliveStream { 62 | keep_alive: KeepAlive, 63 | #[pin] 64 | timer: Sleep, 65 | } 66 | } 67 | 68 | impl KeepAliveStream { 69 | pub(super) fn new(keep_alive: KeepAlive) -> Self { 70 | Self { 71 | timer: tokio::time::sleep(keep_alive.interval), 72 | keep_alive, 73 | } 74 | } 75 | 76 | pub(super) fn reset(self: Pin<&mut Self>) { 77 | let this = self.project(); 78 | this.timer.reset(Instant::now() + this.keep_alive.interval); 79 | } 80 | 81 | pub(super) fn poll_event(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 82 | ready!(self.as_mut().project().timer.poll(cx)); 83 | 84 | self.as_mut().reset(); 85 | 86 | Poll::Ready(self.keep_alive.event.clone()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /boluo/src/response/sse/mod.rs: -------------------------------------------------------------------------------- 1 | //! 服务器发送事件(SSE)。 2 | 3 | mod event; 4 | mod keep_alive; 5 | 6 | pub use event::{Event, EventBuilder, EventValueError}; 7 | pub use keep_alive::KeepAlive; 8 | 9 | use std::convert::Infallible; 10 | use std::pin::Pin; 11 | use std::task::{Context, Poll}; 12 | 13 | use boluo_core::BoxError; 14 | use boluo_core::body::{Body, Bytes, Frame, HttpBody}; 15 | use boluo_core::http::header; 16 | use boluo_core::response::{IntoResponse, Response}; 17 | use futures_util::Stream; 18 | 19 | use self::keep_alive::KeepAliveStream; 20 | 21 | /// 服务器发送事件。 22 | #[derive(Clone)] 23 | pub struct Sse { 24 | stream: S, 25 | keep_alive: Option, 26 | } 27 | 28 | impl Sse { 29 | /// 创建一个新的 [`Sse`] 实例。 30 | pub fn new(stream: S) -> Self { 31 | Self { 32 | stream, 33 | keep_alive: None, 34 | } 35 | } 36 | 37 | /// 保持连接。 38 | pub fn keep_alive(mut self, keep_alive: KeepAlive) -> Self { 39 | self.keep_alive = Some(keep_alive); 40 | self 41 | } 42 | } 43 | 44 | impl std::fmt::Debug for Sse { 45 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 46 | f.debug_struct("Sse") 47 | .field("stream", &std::any::type_name::()) 48 | .field("keep_alive", &self.keep_alive) 49 | .finish() 50 | } 51 | } 52 | 53 | impl IntoResponse for Sse 54 | where 55 | S: Stream> + Send + 'static, 56 | E: Into, 57 | { 58 | type Error = Infallible; 59 | 60 | fn into_response(self) -> Result { 61 | let body = SseBody { 62 | stream: self.stream, 63 | keep_alive: self.keep_alive.map(KeepAliveStream::new), 64 | }; 65 | 66 | Response::builder() 67 | .header(header::CONTENT_TYPE, mime::TEXT_EVENT_STREAM.as_ref()) 68 | .header(header::CACHE_CONTROL, "no-cache") 69 | .body(Body::new(body)) 70 | .map_err(|e| unreachable!("{e}")) 71 | } 72 | } 73 | 74 | pin_project_lite::pin_project! { 75 | struct SseBody { 76 | #[pin] 77 | stream: S, 78 | #[pin] 79 | keep_alive: Option, 80 | } 81 | } 82 | 83 | impl HttpBody for SseBody 84 | where 85 | S: Stream>, 86 | { 87 | type Data = Bytes; 88 | type Error = E; 89 | 90 | fn poll_frame( 91 | self: Pin<&mut Self>, 92 | cx: &mut Context<'_>, 93 | ) -> Poll, Self::Error>>> { 94 | let this = self.project(); 95 | 96 | match this.stream.poll_next(cx) { 97 | Poll::Pending => { 98 | if let Some(keep_alive) = this.keep_alive.as_pin_mut() { 99 | keep_alive 100 | .poll_event(cx) 101 | .map(|e| Some(Ok(Frame::data(Bytes::from(e.to_string()))))) 102 | } else { 103 | Poll::Pending 104 | } 105 | } 106 | Poll::Ready(Some(Ok(event))) => { 107 | if let Some(keep_alive) = this.keep_alive.as_pin_mut() { 108 | keep_alive.reset(); 109 | } 110 | Poll::Ready(Some(Ok(Frame::data(Bytes::from(event.to_string()))))) 111 | } 112 | Poll::Ready(Some(Err(error))) => Poll::Ready(Some(Err(error))), 113 | Poll::Ready(None) => Poll::Ready(None), 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /boluo/src/route/error.rs: -------------------------------------------------------------------------------- 1 | use boluo_core::http::StatusCode; 2 | use boluo_core::request::Request; 3 | 4 | /// 路由器路由错误的类别。 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 6 | pub enum RouteErrorKind { 7 | /// 请求路径不存在。 8 | NotFound, 9 | /// 请求方法不存在。 10 | MethodNotAllowed, 11 | } 12 | 13 | /// 路由器路由错误。 14 | #[derive(Debug)] 15 | pub struct RouteError { 16 | kind: RouteErrorKind, 17 | request: Request, 18 | } 19 | 20 | impl RouteError { 21 | /// 使用给定的请求和类别创建 [`RouteError`]。 22 | #[inline] 23 | pub fn new(request: Request, kind: RouteErrorKind) -> Self { 24 | Self { kind, request } 25 | } 26 | 27 | /// 使用给定的请求创建 [`RouteError`],类别为 [`RouteErrorKind::NotFound`]。 28 | #[inline] 29 | pub fn not_found(request: Request) -> Self { 30 | Self::new(request, RouteErrorKind::NotFound) 31 | } 32 | 33 | /// 使用给定的请求创建 [`RouteError`],类别为 [`RouteErrorKind::MethodNotAllowed`]。 34 | #[inline] 35 | pub fn method_not_allowed(request: Request) -> Self { 36 | Self::new(request, RouteErrorKind::MethodNotAllowed) 37 | } 38 | 39 | /// 返回此错误的类别。 40 | #[inline] 41 | pub fn kind(&self) -> RouteErrorKind { 42 | self.kind 43 | } 44 | 45 | /// 获取本次请求的引用。 46 | #[inline] 47 | pub fn request_ref(&self) -> &Request { 48 | &self.request 49 | } 50 | 51 | /// 获取本次请求的可变引用。 52 | #[inline] 53 | pub fn request_mut(&mut self) -> &mut Request { 54 | &mut self.request 55 | } 56 | 57 | /// 消耗错误,返回本次请求。 58 | #[inline] 59 | pub fn into_request(self) -> Request { 60 | self.request 61 | } 62 | } 63 | 64 | impl std::fmt::Display for RouteError { 65 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 66 | match self.kind() { 67 | RouteErrorKind::NotFound => write!(f, "{}", StatusCode::NOT_FOUND), 68 | RouteErrorKind::MethodNotAllowed => { 69 | write!(f, "{}", StatusCode::METHOD_NOT_ALLOWED) 70 | } 71 | } 72 | } 73 | } 74 | 75 | impl std::error::Error for RouteError {} 76 | 77 | /// 路由器构建错误。 78 | #[derive(Debug, Clone)] 79 | pub enum RouterError { 80 | /// 注册的路径冲突。 81 | PathConflict { 82 | /// 错误路径。 83 | path: String, 84 | /// 错误信息。 85 | message: String, 86 | }, 87 | /// 注册的路径无效。 88 | InvalidPath { 89 | /// 错误路径。 90 | path: String, 91 | /// 错误信息。 92 | message: String, 93 | }, 94 | /// 注册的路径超过最大上限。 95 | TooManyPath, 96 | } 97 | 98 | impl RouterError { 99 | pub(super) fn from_matchit_insert_error(path: String, error: matchit::InsertError) -> Self { 100 | match error { 101 | matchit::InsertError::Conflict { .. } => RouterError::PathConflict { 102 | path, 103 | message: "conflict with previously registered path".to_owned(), 104 | }, 105 | _ => RouterError::InvalidPath { 106 | path, 107 | message: format!("{error}"), 108 | }, 109 | } 110 | } 111 | } 112 | 113 | impl std::fmt::Display for RouterError { 114 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 115 | match self { 116 | RouterError::PathConflict { path, message } => { 117 | write!(f, "path conflict \"{path}\" ({message})") 118 | } 119 | RouterError::InvalidPath { path, message } => { 120 | write!(f, "invalid path \"{path}\" ({message})") 121 | } 122 | RouterError::TooManyPath => f.write_str("too many path"), 123 | } 124 | } 125 | } 126 | 127 | impl std::error::Error for RouterError {} 128 | -------------------------------------------------------------------------------- /boluo/src/route/method.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use boluo_core::BoxError; 4 | use boluo_core::http::Method; 5 | use boluo_core::middleware::Middleware; 6 | use boluo_core::request::Request; 7 | use boluo_core::response::{IntoResponse, Response}; 8 | use boluo_core::service::{ArcService, Service}; 9 | 10 | use super::RouteError; 11 | 12 | #[derive(Debug, Default, Clone)] 13 | pub(super) struct MethodRouter { 14 | map: HashMap>, 15 | any: Option>, 16 | } 17 | 18 | impl MethodRouter { 19 | #[inline] 20 | fn add(&mut self, service: ArcService, method: Method) { 21 | self.map.insert(method, service); 22 | } 23 | 24 | #[inline] 25 | fn add_any(&mut self, service: ArcService) { 26 | self.any = Some(service); 27 | } 28 | 29 | #[inline] 30 | fn contains(&self, method: &Method) -> bool { 31 | self.map.contains_key(method) 32 | } 33 | 34 | #[inline] 35 | fn contains_any(&self) -> bool { 36 | self.any.is_some() 37 | } 38 | 39 | pub(super) fn iter( 40 | &self, 41 | ) -> impl Iterator, &ArcService)> { 42 | self.map 43 | .iter() 44 | .map(|(method, service)| (Some(method), service)) 45 | .chain(self.any.as_ref().map(|service| (None, service))) 46 | } 47 | 48 | pub(super) fn remove<'a>( 49 | &mut self, 50 | method: impl Into>, 51 | ) -> Option> { 52 | if let Some(method) = method.into() { 53 | self.map.remove(method) 54 | } else { 55 | self.any.take() 56 | } 57 | } 58 | 59 | pub(super) fn is_empty(&self) -> bool { 60 | self.map.is_empty() && self.any.is_none() 61 | } 62 | 63 | fn match_method(&self, method: &Method) -> Option<&ArcService> { 64 | if let Some(service) = self.map.get(method) { 65 | return Some(service); 66 | } 67 | if method == Method::HEAD { 68 | if let Some(service) = self.map.get(&Method::GET) { 69 | return Some(service); 70 | } 71 | } 72 | self.any.as_ref() 73 | } 74 | } 75 | 76 | impl Service for MethodRouter { 77 | type Response = Response; 78 | type Error = BoxError; 79 | 80 | async fn call(&self, req: Request) -> Result { 81 | let Some(service) = self.match_method(req.method()) else { 82 | return Err(RouteError::method_not_allowed(req).into()); 83 | }; 84 | service.call(req).await 85 | } 86 | } 87 | 88 | #[derive(Debug, Clone)] 89 | enum Methods { 90 | Any, 91 | One(Method), 92 | Set(HashSet), 93 | } 94 | 95 | impl Methods { 96 | fn add(mut self, method: Method) -> Self { 97 | match self { 98 | Methods::Any => Methods::One(method), 99 | Methods::One(m) => Self::Set(HashSet::from([m, method])), 100 | Methods::Set(ref mut s) => { 101 | s.insert(method); 102 | self 103 | } 104 | } 105 | } 106 | } 107 | 108 | /// 方法路由。 109 | /// 110 | /// 用于向路由器注册服务的类型,描述访问服务的请求方法。 111 | #[derive(Debug, Clone)] 112 | pub struct MethodRoute { 113 | methods: Methods, 114 | service: S, 115 | } 116 | 117 | impl MethodRoute { 118 | /// 创建方法路由,服务接收任意方法的请求。 119 | #[inline] 120 | fn any(service: S) -> Self { 121 | Self { 122 | methods: Methods::Any, 123 | service, 124 | } 125 | } 126 | 127 | /// 创建方法路由,服务接收给定方法的请求。 128 | #[inline] 129 | fn one(service: S, method: Method) -> Self { 130 | Self { 131 | methods: Methods::One(method), 132 | service, 133 | } 134 | } 135 | 136 | /// 增加访问服务的请求方法。 137 | /// 138 | /// 使用 [`any`] 创建的方法路由调用此函数后,服务不再接收任意方法的请求。 139 | #[allow(clippy::should_implement_trait)] 140 | pub fn add(mut self, method: Method) -> Self { 141 | self.methods = self.methods.add(method); 142 | self 143 | } 144 | 145 | /// 消耗方法路由,得到内部服务。 146 | pub fn into_service(self) -> S { 147 | self.service 148 | } 149 | 150 | /// 对方法路由内部的服务应用中间件。 151 | pub fn with(self, middleware: T) -> MethodRoute 152 | where 153 | T: Middleware, 154 | { 155 | MethodRoute { 156 | methods: self.methods, 157 | service: middleware.transform(self.service), 158 | } 159 | } 160 | } 161 | 162 | #[inline] 163 | fn method(service: S, method: Method) -> MethodRoute { 164 | MethodRoute::one(service, method) 165 | } 166 | 167 | /// 创建 [`MethodRoute`],服务接收任意方法的请求。 168 | #[inline] 169 | pub fn any(service: S) -> MethodRoute { 170 | MethodRoute::any(service) 171 | } 172 | 173 | macro_rules! impl_method_fn { 174 | ($name:ident, $method:expr) => { 175 | #[doc = concat!("创建 [`MethodRoute`],服务接收 [`", stringify!($method), "`] 请求。")] 176 | #[inline] 177 | pub fn $name(service: S) -> MethodRoute { 178 | method(service, $method) 179 | } 180 | }; 181 | } 182 | 183 | impl_method_fn!(connect, Method::CONNECT); 184 | impl_method_fn!(delete, Method::DELETE); 185 | impl_method_fn!(get, Method::GET); 186 | impl_method_fn!(head, Method::HEAD); 187 | impl_method_fn!(options, Method::OPTIONS); 188 | impl_method_fn!(patch, Method::PATCH); 189 | impl_method_fn!(post, Method::POST); 190 | impl_method_fn!(put, Method::PUT); 191 | impl_method_fn!(trace, Method::TRACE); 192 | 193 | mod private { 194 | use super::{MethodRoute, Request, Service}; 195 | 196 | pub trait Sealed {} 197 | 198 | impl Sealed for S where S: Service {} 199 | impl Sealed for MethodRoute {} 200 | } 201 | 202 | /// 用于生成 [`MethodRoute`] 的特征。 203 | pub trait IntoMethodRoute: private::Sealed { 204 | /// 返回的 [`MethodRoute`] 内部的服务类型。 205 | type Service; 206 | 207 | /// 得到一个 [`MethodRoute`] 实例。 208 | fn into_method_route(self) -> MethodRoute; 209 | } 210 | 211 | impl IntoMethodRoute for S 212 | where 213 | S: Service, 214 | { 215 | type Service = S; 216 | 217 | #[inline] 218 | fn into_method_route(self) -> MethodRoute { 219 | MethodRoute::any(self) 220 | } 221 | } 222 | 223 | impl IntoMethodRoute for MethodRoute { 224 | type Service = S; 225 | 226 | #[inline] 227 | fn into_method_route(self) -> MethodRoute { 228 | self 229 | } 230 | } 231 | 232 | pub(super) trait WithMiddleware { 233 | type Output; 234 | 235 | fn with(self, middleware: M) -> Self::Output; 236 | } 237 | 238 | impl WithMiddleware for MethodRouter 239 | where 240 | M: Middleware> + Clone, 241 | M::Service: Service + 'static, 242 | >::Response: IntoResponse, 243 | >::Error: Into, 244 | { 245 | type Output = MethodRouter; 246 | 247 | fn with(mut self, middleware: M) -> Self::Output { 248 | self.map = self 249 | .map 250 | .into_iter() 251 | .map(|(method, service)| { 252 | ( 253 | method, 254 | boluo_core::util::__into_arc_service(middleware.clone().transform(service)), 255 | ) 256 | }) 257 | .collect(); 258 | self.any = self.any.map(|service| { 259 | boluo_core::util::__into_arc_service(middleware.clone().transform(service)) 260 | }); 261 | self 262 | } 263 | } 264 | 265 | pub(super) trait MergeToMethodRouter { 266 | fn merge_to(self, router: &mut MethodRouter) -> Result<(), Option>; 267 | } 268 | 269 | impl MergeToMethodRouter for MethodRouter { 270 | fn merge_to(self, router: &mut MethodRouter) -> Result<(), Option> { 271 | for method in self.map.keys() { 272 | if router.contains(method) { 273 | return Err(Some(method.clone())); 274 | } 275 | } 276 | if let Some(service) = self.any { 277 | if router.contains_any() { 278 | return Err(None); 279 | } 280 | router.add_any(service); 281 | } 282 | for (method, service) in self.map { 283 | router.add(service, method); 284 | } 285 | Ok(()) 286 | } 287 | } 288 | 289 | impl MergeToMethodRouter for MethodRoute> { 290 | fn merge_to(self, router: &mut MethodRouter) -> Result<(), Option> { 291 | match self.methods { 292 | Methods::Any => { 293 | if router.contains_any() { 294 | return Err(None); 295 | } 296 | router.add_any(self.service); 297 | } 298 | Methods::One(method) => { 299 | if router.contains(&method) { 300 | return Err(Some(method)); 301 | } 302 | router.add(self.service, method); 303 | } 304 | Methods::Set(methods) => { 305 | for method in methods.iter() { 306 | if router.contains(method) { 307 | return Err(Some(method.clone())); 308 | } 309 | } 310 | for method in methods { 311 | router.add(self.service.clone(), method); 312 | } 313 | } 314 | } 315 | Ok(()) 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /boluo/src/route/mod.rs: -------------------------------------------------------------------------------- 1 | //! 将请求转发到服务的类型和特征。 2 | 3 | mod error; 4 | mod method; 5 | mod params; 6 | mod router; 7 | 8 | pub use error::{RouteError, RouteErrorKind, RouterError}; 9 | pub use method::{IntoMethodRoute, MethodRoute}; 10 | pub use method::{any, connect, delete, get, head, options, patch, post, put, trace}; 11 | pub use params::PathParams; 12 | pub use router::{Endpoint, Route, Router}; 13 | -------------------------------------------------------------------------------- /boluo/src/route/params.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use boluo_core::http::Extensions; 4 | use matchit::Params; 5 | 6 | use super::router::PRIVATE_TAIL_PARAM; 7 | 8 | /// 路由器提取的路径参数。 9 | #[derive(Default, Debug, Clone)] 10 | pub struct PathParams(pub Vec<(String, String)>); 11 | 12 | impl Deref for PathParams { 13 | type Target = Vec<(String, String)>; 14 | 15 | #[inline] 16 | fn deref(&self) -> &Self::Target { 17 | &self.0 18 | } 19 | } 20 | 21 | impl DerefMut for PathParams { 22 | #[inline] 23 | fn deref_mut(&mut self) -> &mut Self::Target { 24 | &mut self.0 25 | } 26 | } 27 | 28 | pub(super) fn parse_path_params(params: Params<'_, '_>) -> (PathParams, Option) { 29 | let mut path_params = PathParams(Vec::with_capacity(params.len())); 30 | let mut tail_params = None; 31 | 32 | for (name, value) in params.iter() { 33 | if name == PRIVATE_TAIL_PARAM { 34 | tail_params = Some(if value.starts_with('/') { 35 | value.to_owned() 36 | } else { 37 | format!("/{value}") 38 | }); 39 | } else { 40 | path_params.push((name.to_owned(), value.to_owned())); 41 | } 42 | } 43 | 44 | (path_params, tail_params) 45 | } 46 | 47 | pub(super) fn insert_path_params(extensions: &mut Extensions, params: PathParams) { 48 | if let Some(path_params) = extensions.get_mut::() { 49 | path_params.extend(params.0); 50 | } else { 51 | extensions.insert(params); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /boluo/src/server/compat.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use boluo_core::BoxError; 4 | use boluo_core::body::Body; 5 | use boluo_core::http::{Extensions, StatusCode}; 6 | use boluo_core::request::Request; 7 | use boluo_core::response::{IntoResponse, Response}; 8 | use boluo_core::service::{ArcService, Service, ServiceExt}; 9 | use boluo_core::upgrade::{OnUpgrade, Upgraded}; 10 | use hyper::Request as HyperRequest; 11 | use hyper::Response as HyperResponse; 12 | use hyper::body::Incoming; 13 | use hyper::upgrade::OnUpgrade as HyperOnUpgrade; 14 | use hyper_util::rt::TokioIo; 15 | use tokio_util::compat::TokioAsyncReadCompatExt; 16 | 17 | #[derive(Clone)] 18 | pub(super) struct ServiceToHyper { 19 | service: ArcService, 20 | } 21 | 22 | impl Service> for ServiceToHyper { 23 | type Response = HyperResponse; 24 | type Error = Infallible; 25 | 26 | async fn call(&self, req: HyperRequest) -> Result { 27 | self.service 28 | .call(request_from_hyper(req)) 29 | .await 30 | .map(response_to_hyper) 31 | } 32 | } 33 | 34 | pub(super) fn service_to_hyper(service: S) -> ServiceToHyper 35 | where 36 | S: Service + 'static, 37 | S::Response: IntoResponse, 38 | S::Error: Into, 39 | { 40 | ServiceToHyper { 41 | service: into_arc_service(service), 42 | } 43 | } 44 | 45 | fn into_arc_service(service: S) -> ArcService 46 | where 47 | S: Service + 'static, 48 | S::Response: IntoResponse, 49 | S::Error: Into, 50 | { 51 | boluo_core::util::__try_downcast(service).unwrap_or_else(|service| { 52 | let service = service.map_result(|result| { 53 | result.into_response().or_else(|e| { 54 | (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")) 55 | .into_response() 56 | .map_err(|e| unreachable!("{e}")) 57 | }) 58 | }); 59 | service.boxed_arc() 60 | }) 61 | } 62 | 63 | fn request_from_hyper(req: HyperRequest) -> Request { 64 | let (parts, body) = req.into_parts(); 65 | let mut req = Request::new(Body::new(body)); 66 | *req.method_mut() = parts.method; 67 | *req.uri_mut() = parts.uri; 68 | *req.version_mut() = parts.version; 69 | *req.headers_mut() = parts.headers; 70 | *req.extensions_mut() = replace_hyper_upgrade(parts.extensions); 71 | req 72 | } 73 | 74 | fn response_to_hyper(res: Response) -> HyperResponse { 75 | let (parts, body) = res.into_inner(); 76 | let mut res = HyperResponse::new(body); 77 | *res.status_mut() = parts.status; 78 | *res.version_mut() = parts.version; 79 | *res.headers_mut() = parts.headers; 80 | *res.extensions_mut() = parts.extensions; 81 | res 82 | } 83 | 84 | fn upgrade_from_hyper(on_upgrade: HyperOnUpgrade) -> OnUpgrade { 85 | OnUpgrade::new(async { 86 | on_upgrade 87 | .await 88 | .map(|upgraded| Upgraded::new(TokioIo::new(upgraded).compat())) 89 | .map_err(Into::into) 90 | }) 91 | } 92 | 93 | fn replace_hyper_upgrade(mut extensions: Extensions) -> Extensions { 94 | if let Some(on_upgrade) = extensions.remove::() { 95 | extensions.insert(upgrade_from_hyper(on_upgrade)); 96 | } 97 | extensions 98 | } 99 | -------------------------------------------------------------------------------- /boluo/src/server/graceful_shutdown.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | use std::time::Duration; 3 | 4 | use tokio::sync::watch::{self, Receiver, Sender}; 5 | use tokio_util::sync::CancellationToken; 6 | 7 | /// 优雅关机,用于等待服务器完成剩余请求。 8 | #[derive(Debug)] 9 | pub struct GracefulShutdown { 10 | tx: Sender<()>, 11 | rx: Receiver<()>, 12 | shutdown_signal: CancellationToken, 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | pub(super) struct Monitor { 17 | rx: Receiver<()>, 18 | shutdown_signal: CancellationToken, 19 | } 20 | 21 | impl Monitor { 22 | /// 监视任务并在接收到关机信号时执行关机操作。 23 | pub(super) async fn watch(self, task: T, shutdown: impl FnOnce(Pin<&mut T>)) -> T::Output 24 | where 25 | T: Future, 26 | { 27 | let _rx = self.rx; 28 | let mut task = std::pin::pin!(task); 29 | 30 | tokio::select! { 31 | _ = self.shutdown_signal.cancelled() => { 32 | shutdown(task.as_mut()); 33 | task.await 34 | } 35 | v = task.as_mut() => v 36 | } 37 | } 38 | } 39 | 40 | impl GracefulShutdown { 41 | /// 创建新的 `GracefulShutdown` 实例。 42 | pub(super) fn new() -> Self { 43 | let (tx, rx) = watch::channel(()); 44 | Self { 45 | tx, 46 | rx, 47 | shutdown_signal: CancellationToken::new(), 48 | } 49 | } 50 | 51 | /// 创建一个 `Monitor` 实例,用于监视任务。 52 | pub(super) fn monitor(&self) -> Monitor { 53 | Monitor { 54 | rx: self.rx.clone(), 55 | shutdown_signal: self.shutdown_signal.clone(), 56 | } 57 | } 58 | 59 | /// 发出关机信号,在指定时间内等待服务器完成剩余请求。 60 | /// 61 | /// 如果超时时间设置为 `None`,则该函数将持续等待,直到服务器完成所有剩余请求为止。 62 | /// 如果设置了超时时间,则该函数将在超时时间到达时返回。 63 | /// 64 | /// 该函数返回 `true` 表示服务器完成了所有剩余请求, 65 | /// 返回 `false` 表示在超时时间内服务器未能完成所有剩余请求。 66 | pub async fn shutdown(self, timeout: Option) -> bool { 67 | let GracefulShutdown { 68 | tx, 69 | rx, 70 | shutdown_signal, 71 | } = self; 72 | 73 | drop(rx); 74 | shutdown_signal.cancel(); 75 | 76 | tokio::select! { 77 | _ = tx.closed() => true, 78 | _ = sleep(timeout) => false, 79 | } 80 | } 81 | } 82 | 83 | async fn sleep(timeout: Option) { 84 | if let Some(timeout) = timeout { 85 | tokio::time::sleep(timeout).await 86 | } else { 87 | std::future::pending().await 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /boluo/src/ws/message.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use boluo_core::BoxError; 4 | use bytes::Bytes; 5 | use tokio_tungstenite::tungstenite as ts; 6 | 7 | pub use tokio_tungstenite::tungstenite::Utf8Bytes; 8 | pub use tokio_tungstenite::tungstenite::protocol::CloseFrame; 9 | pub use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; 10 | 11 | /// An enum representing the various forms of a WebSocket message. 12 | #[derive(Debug, Clone, Eq, PartialEq)] 13 | pub enum Message { 14 | /// A text WebSocket message 15 | Text(Utf8Bytes), 16 | /// A binary WebSocket message 17 | Binary(Bytes), 18 | /// A ping message with the specified payload 19 | /// 20 | /// The payload here must have a length less than 125 bytes 21 | Ping(Bytes), 22 | /// A pong message with the specified payload 23 | /// 24 | /// The payload here must have a length less than 125 bytes 25 | Pong(Bytes), 26 | /// A close message with the optional close frame. 27 | Close(Option), 28 | } 29 | 30 | impl Message { 31 | pub(super) fn into_tungstenite(self) -> ts::Message { 32 | match self { 33 | Self::Text(text) => ts::Message::Text(text), 34 | Self::Binary(binary) => ts::Message::Binary(binary), 35 | Self::Ping(ping) => ts::Message::Ping(ping), 36 | Self::Pong(pong) => ts::Message::Pong(pong), 37 | Self::Close(close) => ts::Message::Close(close), 38 | } 39 | } 40 | 41 | pub(super) fn from_tungstenite(message: ts::Message) -> Message { 42 | match message { 43 | ts::Message::Text(text) => Self::Text(text), 44 | ts::Message::Binary(binary) => Self::Binary(binary), 45 | ts::Message::Ping(ping) => Self::Ping(ping), 46 | ts::Message::Pong(pong) => Self::Pong(pong), 47 | ts::Message::Close(close) => Self::Close(close), 48 | ts::Message::Frame(_) => unreachable!(), 49 | } 50 | } 51 | 52 | /// 创建文本消息。 53 | pub fn text(text: S) -> Message 54 | where 55 | S: Into, 56 | { 57 | Message::Text(text.into()) 58 | } 59 | 60 | /// 创建二进制消息。 61 | pub fn binary(data: D) -> Message 62 | where 63 | D: Into, 64 | { 65 | Message::Binary(data.into()) 66 | } 67 | 68 | /// 创建 `ping` 消息。 69 | pub fn ping(data: D) -> Message 70 | where 71 | D: Into, 72 | { 73 | Message::Ping(data.into()) 74 | } 75 | 76 | /// 创建 `pong` 消息。 77 | pub fn pong(data: D) -> Message 78 | where 79 | D: Into, 80 | { 81 | Message::Pong(data.into()) 82 | } 83 | 84 | /// 创建空的关闭消息。 85 | pub fn close() -> Message { 86 | Message::Close(None) 87 | } 88 | 89 | /// 创建带有状态码和原因的关闭消息。 90 | pub fn close_with(code: impl Into, reason: impl Into) -> Message { 91 | Message::Close(Some(CloseFrame { 92 | code: CloseCode::from(code.into()), 93 | reason: reason.into(), 94 | })) 95 | } 96 | 97 | /// 判断消息是否为 [`Message::Text`]。 98 | pub fn is_text(&self) -> bool { 99 | matches!(*self, Message::Text(_)) 100 | } 101 | 102 | /// 判断消息是否为 [`Message::Binary`]。 103 | pub fn is_binary(&self) -> bool { 104 | matches!(*self, Message::Binary(_)) 105 | } 106 | 107 | /// 判断消息是否为 [`Message::Ping`]。 108 | pub fn is_ping(&self) -> bool { 109 | matches!(*self, Message::Ping(_)) 110 | } 111 | 112 | /// 判断消息是否为 [`Message::Pong`]。 113 | pub fn is_pong(&self) -> bool { 114 | matches!(*self, Message::Pong(_)) 115 | } 116 | 117 | /// 判断消息是否为 [`Message::Close`]。 118 | pub fn is_close(&self) -> bool { 119 | matches!(*self, Message::Close(_)) 120 | } 121 | 122 | /// 获取消息的长度。 123 | pub fn len(&self) -> usize { 124 | match self { 125 | Message::Text(text) => text.len(), 126 | Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => data.len(), 127 | Message::Close(data) => data.as_ref().map(|d| d.reason.len()).unwrap_or(0), 128 | } 129 | } 130 | 131 | /// 判断消息是否为空。 132 | pub fn is_empty(&self) -> bool { 133 | self.len() == 0 134 | } 135 | 136 | /// 将消息作为二进制数据返回。 137 | pub fn into_bytes(self) -> Bytes { 138 | match self { 139 | Message::Text(text) => text.into(), 140 | Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => data, 141 | Message::Close(None) => Bytes::new(), 142 | Message::Close(Some(frame)) => frame.reason.into(), 143 | } 144 | } 145 | 146 | /// 将消息作为二进制数据返回。 147 | pub fn as_bytes(&self) -> &[u8] { 148 | match self { 149 | Message::Text(text) => text.as_bytes(), 150 | Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => data, 151 | Message::Close(None) => &[], 152 | Message::Close(Some(frame)) => frame.reason.as_bytes(), 153 | } 154 | } 155 | 156 | /// 尝试将消息作为文本数据返回。 157 | pub fn into_text(self) -> Result { 158 | match self { 159 | Message::Text(text) => Ok(text), 160 | Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => { 161 | Utf8Bytes::try_from(data).map_err(From::from) 162 | } 163 | Message::Close(None) => Ok(Utf8Bytes::default()), 164 | Message::Close(Some(frame)) => Ok(frame.reason), 165 | } 166 | } 167 | 168 | /// 尝试将消息作为文本数据返回。 169 | pub fn to_text(&self) -> Result<&str, BoxError> { 170 | match self { 171 | Message::Text(text) => Ok(text), 172 | Message::Binary(data) | Message::Ping(data) | Message::Pong(data) => { 173 | std::str::from_utf8(data).map_err(From::from) 174 | } 175 | Message::Close(None) => Ok(""), 176 | Message::Close(Some(frame)) => Ok(&frame.reason), 177 | } 178 | } 179 | } 180 | 181 | impl<'a> From> for Message { 182 | fn from(text: Cow<'a, str>) -> Self { 183 | Message::text(text.into_owned()) 184 | } 185 | } 186 | 187 | impl From for Message { 188 | fn from(text: String) -> Self { 189 | Message::text(text) 190 | } 191 | } 192 | 193 | impl<'a> From<&'a str> for Message { 194 | fn from(text: &'a str) -> Self { 195 | Message::text(text) 196 | } 197 | } 198 | 199 | impl<'a> From> for Message { 200 | fn from(data: Cow<'a, [u8]>) -> Self { 201 | Message::binary(data.into_owned()) 202 | } 203 | } 204 | 205 | impl From> for Message { 206 | fn from(data: Vec) -> Self { 207 | Message::binary(data) 208 | } 209 | } 210 | 211 | impl<'a> From<&'a [u8]> for Message { 212 | fn from(data: &'a [u8]) -> Self { 213 | Message::binary(data.to_vec()) 214 | } 215 | } 216 | 217 | impl From for Bytes { 218 | fn from(message: Message) -> Self { 219 | message.into_bytes() 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /boluo/src/ws/util.rs: -------------------------------------------------------------------------------- 1 | use base64::Engine; 2 | use base64::engine::general_purpose::STANDARD; 3 | use boluo_core::body::Bytes; 4 | use boluo_core::http::header::{HeaderMap, HeaderName, HeaderValue}; 5 | use sha1::{Digest, Sha1}; 6 | 7 | pub(super) fn sign(key: &[u8]) -> HeaderValue { 8 | let mut sha1 = Sha1::default(); 9 | sha1.update(key); 10 | sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); 11 | let b64 = Bytes::from(STANDARD.encode(sha1.finalize())); 12 | HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value") 13 | } 14 | 15 | pub(super) fn header_eq_ignore_case(headers: &HeaderMap, key: HeaderName, value: &str) -> bool { 16 | if let Some(header) = headers.get(&key) { 17 | header.as_bytes().eq_ignore_ascii_case(value.as_bytes()) 18 | } else { 19 | false 20 | } 21 | } 22 | 23 | pub(super) fn header_eq(headers: &HeaderMap, key: HeaderName, value: &str) -> bool { 24 | if let Some(header) = headers.get(&key) { 25 | header.as_bytes().eq(value.as_bytes()) 26 | } else { 27 | false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = ["*"] 4 | exclude = ["target"] 5 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # 示例 2 | 3 | 这些示例演示了 `boluo` 的主要功能和使用方式,你可以通过以下命令运行这些示例: 4 | 5 | ```bash 6 | cargo run --bin [crate] # [crate]替换为具体的示例项目 7 | ``` 8 | 9 | | 目录 | 说明 | 10 | | ----------------------------------------- | ------------------------------------------ | 11 | | [hello](./hello/) | 输出 "hello world" | 12 | | [route](./route/) | 添加路由,为处理程序设置访问路径和访问方法 | 13 | | [state](./state/) | 添加状态,用于在处理程序中共享资源 | 14 | | [extract-path](./extract-path/) | 提取路径参数 | 15 | | [handle-error](./handle-error/) | 捕获错误,并将错误转为响应 | 16 | | [custom-middleware](./custom-middleware/) | 自定义中间件,并将中间件挂载到服务上 | 17 | | [custom-listener](./custom-listener) | 自定义监听器 | 18 | | [graceful-shutdown](./graceful-shutdown/) | 优雅关机 | 19 | | [sse](./sse/) | 服务器发送事件 | 20 | | [ws](./ws/) | 网络套接字 | 21 | | [log](./log/) | 记录请求日志 | 22 | | [static-file](./static-file/) | 静态文件服务 | 23 | | [tls](./tls/) | 配置 TLS 加密以增强安全性 | 24 | | [compat-tower](./compat-tower/) | 使用 `tower` 的服务和中间件 | 25 | -------------------------------------------------------------------------------- /examples/compat-tower/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compat-tower" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | http = "1" 12 | tower = { version = "0.5", features = ["timeout"] } 13 | tower-http = { version = "0.6", features = ["add-extension"] } 14 | -------------------------------------------------------------------------------- /examples/compat-tower/src/compat.rs: -------------------------------------------------------------------------------- 1 | use std::future::poll_fn; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use boluo::middleware::Middleware; 6 | use boluo::request::Request; 7 | use boluo::response::Response; 8 | use boluo::service::Service; 9 | use tower::Layer; 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct CompatTower(pub S); 13 | 14 | impl Service for CompatTower 15 | where 16 | S: tower::Service + Clone + Send + Sync, 17 | S::Future: Send, 18 | R: Send, 19 | { 20 | type Response = S::Response; 21 | type Error = S::Error; 22 | 23 | async fn call(&self, req: R) -> Result { 24 | let mut service = self.0.clone(); 25 | poll_fn(|cx| service.poll_ready(cx)).await?; 26 | service.call(req).await 27 | } 28 | } 29 | 30 | impl Middleware for CompatTower 31 | where 32 | L: Layer>, 33 | { 34 | type Service = CompatTower; 35 | 36 | fn transform(self, service: S) -> Self::Service { 37 | CompatTower(self.0.layer(TowerService(service))) 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone, Copy)] 42 | pub struct CompatTowerHttp(pub S); 43 | 44 | impl Service> for CompatTowerHttp 45 | where 46 | S: tower::Service, Response = http::Response> + Clone + Send + Sync, 47 | S::Future: Send, 48 | ReqB: Send, 49 | { 50 | type Response = Response; 51 | type Error = S::Error; 52 | 53 | async fn call(&self, req: Request) -> Result { 54 | let req = into_http_request(req); 55 | let mut service = self.0.clone(); 56 | poll_fn(|cx| service.poll_ready(cx)).await?; 57 | service.call(req).await.map(into_boluo_response) 58 | } 59 | } 60 | 61 | impl Middleware for CompatTowerHttp 62 | where 63 | L: Layer>, 64 | { 65 | type Service = CompatTowerHttp; 66 | 67 | fn transform(self, service: S) -> Self::Service { 68 | CompatTowerHttp(self.0.layer(TowerHttpService(service))) 69 | } 70 | } 71 | 72 | #[derive(Debug, Clone, Copy)] 73 | pub struct TowerService(pub S); 74 | 75 | impl tower::Service for TowerService 76 | where 77 | S: Service + Clone + 'static, 78 | R: Send + 'static, 79 | { 80 | type Response = S::Response; 81 | type Error = S::Error; 82 | type Future = Pin> + Send>>; 83 | 84 | fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { 85 | Poll::Ready(Ok(())) 86 | } 87 | 88 | fn call(&mut self, req: R) -> Self::Future { 89 | let service = self.0.clone(); 90 | Box::pin(async move { service.call(req).await }) 91 | } 92 | } 93 | 94 | #[derive(Debug, Clone, Copy)] 95 | pub struct TowerHttpService(pub S); 96 | 97 | impl tower::Service> for TowerHttpService 98 | where 99 | S: Service, Response = Response> + Clone + 'static, 100 | ReqB: Send + 'static, 101 | { 102 | type Response = http::Response; 103 | type Error = S::Error; 104 | type Future = Pin> + Send>>; 105 | 106 | fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { 107 | Poll::Ready(Ok(())) 108 | } 109 | 110 | fn call(&mut self, req: http::Request) -> Self::Future { 111 | let req = into_boluo_request(req); 112 | let service = self.0.clone(); 113 | Box::pin(async move { service.call(req).await.map(into_http_response) }) 114 | } 115 | } 116 | 117 | fn into_http_request(req: Request) -> http::Request { 118 | let (parts, body) = req.into_inner(); 119 | let mut req = http::Request::new(body); 120 | *req.method_mut() = parts.method; 121 | *req.uri_mut() = parts.uri; 122 | *req.version_mut() = parts.version; 123 | *req.headers_mut() = parts.headers; 124 | *req.extensions_mut() = parts.extensions; 125 | req 126 | } 127 | 128 | fn into_http_response(res: Response) -> http::Response { 129 | let (parts, body) = res.into_inner(); 130 | let mut res = http::Response::new(body); 131 | *res.status_mut() = parts.status; 132 | *res.version_mut() = parts.version; 133 | *res.headers_mut() = parts.headers; 134 | *res.extensions_mut() = parts.extensions; 135 | res 136 | } 137 | 138 | fn into_boluo_request(req: http::Request) -> Request { 139 | let (parts, body) = req.into_parts(); 140 | let mut req = Request::new(body); 141 | *req.method_mut() = parts.method; 142 | *req.uri_mut() = parts.uri; 143 | *req.version_mut() = parts.version; 144 | *req.headers_mut() = parts.headers; 145 | *req.extensions_mut() = parts.extensions; 146 | req 147 | } 148 | 149 | fn into_boluo_response(res: http::Response) -> Response { 150 | let (parts, body) = res.into_parts(); 151 | let mut res = Response::new(body); 152 | *res.status_mut() = parts.status; 153 | *res.version_mut() = parts.version; 154 | *res.headers_mut() = parts.headers; 155 | *res.extensions_mut() = parts.extensions; 156 | res 157 | } 158 | -------------------------------------------------------------------------------- /examples/compat-tower/src/main.rs: -------------------------------------------------------------------------------- 1 | mod compat; 2 | 3 | use std::time::Duration; 4 | 5 | use boluo::data::Extension; 6 | use boluo::response::IntoResponse; 7 | use boluo::route::Router; 8 | use boluo::server::Server; 9 | use boluo::service::ServiceExt; 10 | use compat::{CompatTower, CompatTowerHttp}; 11 | use tokio::net::TcpListener; 12 | use tower::timeout::TimeoutLayer; 13 | use tower_http::add_extension::AddExtensionLayer; 14 | 15 | #[tokio::main] 16 | async fn main() { 17 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 18 | 19 | let app = Router::new() 20 | .mount(hello) 21 | .mount(sleep) 22 | // 避免服务被深度拷贝。 23 | // 这是因为tower的服务每次调用都需要在内部克隆boluo的服务。 24 | .boxed_arc() 25 | .with(CompatTowerHttp(AddExtensionLayer::new("compat tower"))) 26 | .with(CompatTower(TimeoutLayer::new(Duration::from_secs(6)))); 27 | 28 | Server::new(listener).run(app).await.unwrap(); 29 | } 30 | 31 | #[boluo::route("/", method = "GET")] 32 | async fn hello(message: Extension<&'static str>) -> impl IntoResponse { 33 | message.0 34 | } 35 | 36 | #[boluo::route("/sleep", method = "GET")] 37 | async fn sleep() -> impl IntoResponse { 38 | tokio::time::sleep(Duration::from_secs(10)).await; 39 | } 40 | -------------------------------------------------------------------------------- /examples/custom-listener/.gitignore: -------------------------------------------------------------------------------- 1 | /http_out 2 | -------------------------------------------------------------------------------- /examples/custom-listener/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-listener" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | boluo = { path = "../../boluo" } 8 | tokio = { version = "1", features = ["full"] } 9 | -------------------------------------------------------------------------------- /examples/custom-listener/http/1: -------------------------------------------------------------------------------- 1 | POST /sub/1/2 HTTP/1.1 2 | 3 | -------------------------------------------------------------------------------- /examples/custom-listener/http/2: -------------------------------------------------------------------------------- 1 | POST /div/6/2 HTTP/1.1 2 | 3 | -------------------------------------------------------------------------------- /examples/custom-listener/src/listener.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use boluo::BoxError; 4 | use boluo::listener::Listener; 5 | use tokio::fs::{File, ReadDir}; 6 | 7 | pub struct FileListener { 8 | idir: ReadDir, 9 | odir: PathBuf, 10 | } 11 | 12 | impl FileListener { 13 | pub async fn new(idir: impl AsRef, odir: impl Into) -> Result { 14 | Ok(Self { 15 | idir: tokio::fs::read_dir(idir).await?, 16 | odir: odir.into(), 17 | }) 18 | } 19 | 20 | async fn next_file_path(&mut self) -> Result, BoxError> { 21 | while let Some(entry) = self.idir.next_entry().await? { 22 | let path = entry.path(); 23 | if path.is_file() { 24 | return Ok(Some(path)); 25 | } 26 | } 27 | Ok(None) 28 | } 29 | } 30 | 31 | impl Listener for FileListener { 32 | type IO = File; 33 | type Addr = (); 34 | type Error = BoxError; 35 | 36 | async fn accept(&mut self) -> Result<(Self::IO, Self::Addr), Self::Error> { 37 | let Some(path) = self.next_file_path().await? else { 38 | std::future::pending().await 39 | }; 40 | 41 | let mut temp = self.odir.clone(); 42 | temp.push(path.file_name().unwrap()); 43 | 44 | tokio::fs::create_dir_all(&self.odir).await?; 45 | tokio::fs::copy(path, &temp).await?; 46 | 47 | Ok((File::options().read(true).write(true).open(temp).await?, ())) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/custom-listener/src/main.rs: -------------------------------------------------------------------------------- 1 | mod listener; 2 | use listener::FileListener; 3 | 4 | use boluo::extract::Path; 5 | use boluo::response::IntoResponse; 6 | use boluo::route::Router; 7 | use boluo::server::Server; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | let listener = FileListener::new("custom-listener/http", "custom-listener/http_out") 12 | .await 13 | .unwrap(); 14 | 15 | let app = Router::new().mount(basic); 16 | 17 | Server::new(listener).run(app).await.unwrap(); 18 | } 19 | 20 | #[boluo::route("/{op}/{a}/{b}", method = "POST")] 21 | async fn basic(Path((op, a, b)): Path<(String, i64, i64)>) -> impl IntoResponse { 22 | match op.as_str() { 23 | "add" => format!("{a} + {b} = {}", a + b), 24 | "sub" => format!("{a} - {b} = {}", a - b), 25 | "mul" => format!("{a} * {b} = {}", a * b), 26 | "div" => format!("{a} / {b} = {}", a / b), 27 | _ => format!("Does not support `{op}`"), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/custom-middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-middleware" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | boluo = { path = "../../boluo" } 8 | tokio = { version = "1", features = ["full"] } 9 | -------------------------------------------------------------------------------- /examples/custom-middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::BoxError; 2 | use boluo::http::HeaderName; 3 | use boluo::http::header::AUTHORIZATION; 4 | use boluo::middleware::around_with_state_fn; 5 | use boluo::request::Request; 6 | use boluo::response::{IntoResponse, Response}; 7 | use boluo::route::Router; 8 | use boluo::server::Server; 9 | use boluo::service::{Service, ServiceExt}; 10 | use tokio::net::TcpListener; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 15 | 16 | let app = Router::new() 17 | .mount(hello) 18 | // 将中间件挂载到服务上 19 | .with(around_with_state_fn(AUTHORIZATION, required_request_header)); 20 | 21 | Server::new(listener).run(app).await.unwrap(); 22 | } 23 | 24 | #[boluo::route("/", method = "GET")] 25 | async fn hello() -> impl IntoResponse { 26 | "Hello, World!" 27 | } 28 | 29 | /// 一个携带状态的中间件。如果指定的请求头不存在,则拒绝此请求。 30 | async fn required_request_header( 31 | header_name: &HeaderName, 32 | req: Request, 33 | service: &S, 34 | ) -> Result 35 | where 36 | S: Service, 37 | { 38 | if !req.headers().contains_key(header_name) { 39 | return Err(format!("missing request header `{header_name}`").into()); 40 | } 41 | service.call(req).await 42 | } 43 | -------------------------------------------------------------------------------- /examples/extract-path/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extract-path" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | serde = { version = "1", features = ["derive"] } 12 | -------------------------------------------------------------------------------- /examples/extract-path/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::extract::Path; 2 | use boluo::response::IntoResponse; 3 | use boluo::route::Router; 4 | use boluo::server::Server; 5 | use tokio::net::TcpListener; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 10 | 11 | let app = Router::new().mount(basic_arithmetic); 12 | 13 | Server::new(listener).run(app).await.unwrap(); 14 | } 15 | 16 | #[boluo::route("/{f}/{a}/{b}", method = "GET")] 17 | async fn basic_arithmetic(Path((f, a, b)): Path<(BasicArithmetic, i32, i32)>) -> impl IntoResponse { 18 | match f { 19 | BasicArithmetic::Add => format!("{a} + {b} = {}", a + b), 20 | BasicArithmetic::Sub => format!("{a} - {b} = {}", a - b), 21 | BasicArithmetic::Mul => format!("{a} * {b} = {}", a * b), 22 | BasicArithmetic::Div => format!("{a} / {b} = {}", a / b), 23 | } 24 | } 25 | 26 | #[derive(serde::Deserialize)] 27 | #[serde(rename_all = "snake_case")] 28 | enum BasicArithmetic { 29 | Add, 30 | Sub, 31 | Mul, 32 | Div, 33 | } 34 | -------------------------------------------------------------------------------- /examples/graceful-shutdown/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graceful-shutdown" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/graceful-shutdown/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use boluo::response::IntoResponse; 4 | use boluo::route::Router; 5 | use boluo::server::{RunError, Server}; 6 | use tokio::net::TcpListener; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 11 | 12 | let app = Router::new().mount(hello); 13 | 14 | let shutdown = async { 15 | tokio::signal::ctrl_c().await.ok(); // 等待 ctrl + c 信号 16 | Some(Duration::from_secs(60)) 17 | }; 18 | 19 | let res = Server::new(listener) 20 | // 根据 ctrl + c 信号优雅的关闭服务器 21 | .run_with_graceful_shutdown(app, shutdown) 22 | .await; 23 | 24 | // 如果服务器内部的监听器出错,那么等待服务器内部的请求处理完成 25 | if let Err(RunError::Listener(_, graceful)) = res { 26 | graceful.shutdown(Some(Duration::from_secs(60))).await; 27 | } 28 | } 29 | 30 | #[boluo::route("/", method = "GET")] 31 | async fn hello() -> impl IntoResponse { 32 | "Hello, World!" 33 | } 34 | -------------------------------------------------------------------------------- /examples/handle-error/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "handle-error" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | serde = { version = "1", features = ["derive"] } 12 | -------------------------------------------------------------------------------- /examples/handle-error/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::BoxError; 2 | use boluo::extract::Path; 3 | use boluo::http::StatusCode; 4 | use boluo::response::{IntoResponse, Response}; 5 | use boluo::route::Router; 6 | use boluo::server::Server; 7 | use boluo::service::ServiceExt; 8 | use tokio::net::TcpListener; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 13 | 14 | let app = Router::new() 15 | .mount(basic_arithmetic) 16 | // 添加错误处理程序 17 | .or_else(handle_error); 18 | 19 | Server::new(listener).run(app).await.unwrap(); 20 | } 21 | 22 | #[boluo::route("/{f}/{a}/{b}", method = "GET")] 23 | async fn basic_arithmetic( 24 | Path((f, a, b)): Path<(BasicArithmetic, i32, i32)>, 25 | ) -> Result { 26 | if let BasicArithmetic::Div = f { 27 | if b == 0 { 28 | // 返回除零错误 29 | return Err(DivisionByZeroError); 30 | } 31 | } 32 | 33 | match f { 34 | BasicArithmetic::Add => Ok(format!("{a} + {b} = {}", a + b)), 35 | BasicArithmetic::Sub => Ok(format!("{a} - {b} = {}", a - b)), 36 | BasicArithmetic::Mul => Ok(format!("{a} * {b} = {}", a * b)), 37 | BasicArithmetic::Div => Ok(format!("{a} / {b} = {}", a / b)), 38 | } 39 | } 40 | 41 | #[derive(serde::Deserialize)] 42 | #[serde(rename_all = "snake_case")] 43 | enum BasicArithmetic { 44 | Add, 45 | Sub, 46 | Mul, 47 | Div, 48 | } 49 | 50 | async fn handle_error(error: BoxError) -> Result { 51 | // 捕获除零错误,将错误转换为用户友好的响应消息 52 | if let Some(e) = error.downcast_ref::() { 53 | return (StatusCode::BAD_REQUEST, e.to_string()).into_response(); 54 | } 55 | Err(error) 56 | } 57 | 58 | /// 自定义除零错误 59 | #[derive(Debug, Clone, Copy)] 60 | struct DivisionByZeroError; 61 | 62 | impl std::fmt::Display for DivisionByZeroError { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | write!(f, "division by zero") 65 | } 66 | } 67 | 68 | impl std::error::Error for DivisionByZeroError {} 69 | -------------------------------------------------------------------------------- /examples/hello/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/hello/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::handler::handler_fn; 2 | use boluo::server::Server; 3 | use tokio::net::TcpListener; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 8 | 9 | let f = handler_fn(|| async { "Hello, World!" }); 10 | 11 | Server::new(listener).run(f).await.unwrap(); 12 | } 13 | -------------------------------------------------------------------------------- /examples/log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "log" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/log/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::BoxError; 2 | use boluo::middleware::around_fn; 3 | use boluo::request::Request; 4 | use boluo::response::{IntoResponse, Response}; 5 | use boluo::route::Router; 6 | use boluo::server::Server; 7 | use boluo::service::{Service, ServiceExt}; 8 | use tokio::net::TcpListener; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 13 | 14 | let app = Router::new().mount(hello).with(around_fn(log)); 15 | 16 | Server::new(listener).run(app).await.unwrap(); 17 | } 18 | 19 | #[boluo::route("/", method = "GET")] 20 | async fn hello() -> impl IntoResponse { 21 | "Hello, World!" 22 | } 23 | 24 | /// 简单的日志中间件。 25 | async fn log(req: Request, service: &S) -> Result 26 | where 27 | S: Service, 28 | { 29 | println!(">> {} {}", req.method(), req.uri().path()); 30 | let result = service.call(req).await; 31 | match &result { 32 | Ok(res) => println!(":: {}", res.status()), 33 | Err(err) => println!("!! {}", err), 34 | }; 35 | result 36 | } 37 | -------------------------------------------------------------------------------- /examples/route/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "route" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/route/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::BoxError; 2 | use boluo::handler::handler_fn; 3 | use boluo::request::Request; 4 | use boluo::response::IntoResponse; 5 | use boluo::route::Router; 6 | use boluo::server::Server; 7 | use boluo::service::Service; 8 | use tokio::net::TcpListener; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | Server::new(TcpListener::bind("127.0.0.1:3000").await.unwrap()) 13 | .run(app()) 14 | .await 15 | .unwrap(); 16 | } 17 | 18 | fn app() -> impl Service> { 19 | let ab = Router::new() 20 | .route("/a", handler_fn(|| async { "a" })) 21 | .route("/b", handler_fn(|| async { "b" })); 22 | 23 | let cd = Router::new() 24 | .route("/c", handler_fn(|| async { "c" })) 25 | .route("/d", handler_fn(|| async { "d" })); 26 | 27 | Router::new() 28 | // 路由 29 | .route("/a", handler_fn(|| async { "a" })) 30 | .route("/b", handler_fn(|| async { "b" })) 31 | // 嵌套路由 32 | .scope("/x", ab) 33 | // 将其他路由器的路由合并到当前路由器 34 | .merge(cd) 35 | // 挂载宏定义路由 36 | .mount(f) 37 | } 38 | 39 | #[boluo::route("/f", method = "GET")] 40 | async fn f() -> impl IntoResponse { 41 | "f" 42 | } 43 | -------------------------------------------------------------------------------- /examples/sse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sse" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo", features = ["sse"] } 10 | tokio = { version = "1", features = ["full"] } 11 | tokio-stream = "0.1" 12 | futures-util = "0.3" 13 | -------------------------------------------------------------------------------- /examples/sse/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use boluo::response::IntoResponse; 4 | use boluo::response::sse::{Event, KeepAlive, Sse}; 5 | use boluo::route::Router; 6 | use boluo::server::Server; 7 | use futures_util::stream::repeat_with; 8 | use tokio::net::TcpListener; 9 | use tokio_stream::StreamExt; 10 | 11 | #[tokio::main] 12 | async fn main() { 13 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 14 | 15 | let app = Router::new().mount(sse); 16 | 17 | Server::new(listener).run(app).await.unwrap(); 18 | } 19 | 20 | #[boluo::route("/", method = "GET")] 21 | async fn sse() -> impl IntoResponse { 22 | let stream = repeat_with(|| Event::builder().data("hi!").build()) 23 | // 在事件之间添加3秒的延迟 24 | .throttle(Duration::from_secs(3)); 25 | 26 | Sse::new(stream).keep_alive(KeepAlive::default()) 27 | } 28 | -------------------------------------------------------------------------------- /examples/state/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "state" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo" } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/state/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use boluo::data::Extension; 4 | use boluo::response::IntoResponse; 5 | use boluo::route::Router; 6 | use boluo::server::Server; 7 | use boluo::service::ServiceExt; 8 | use tokio::net::TcpListener; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let state = Count::new(); // 统计访问次数 13 | 14 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 15 | 16 | let app = Router::new() 17 | .mount(hello) 18 | .mount(count) 19 | .with(Extension(state)); // 添加扩展 20 | 21 | Server::new(listener).run(app).await.unwrap(); 22 | } 23 | 24 | #[boluo::route("/", method = "GET")] 25 | async fn hello(Extension(state): Extension) -> impl IntoResponse { 26 | state.inc(); // 每次访问将计数器加一 27 | } 28 | 29 | #[boluo::route("/count", method = "GET")] 30 | async fn count(Extension(state): Extension) -> impl IntoResponse { 31 | format!("{}", state.as_u64()) // 返回访问次数 32 | } 33 | 34 | #[derive(Debug, Clone)] 35 | struct Count(Arc>); 36 | 37 | impl Count { 38 | fn new() -> Self { 39 | Self(Arc::new(Mutex::new(0))) 40 | } 41 | 42 | fn inc(&self) { 43 | *self.0.lock().unwrap() += 1; 44 | } 45 | 46 | fn as_u64(&self) -> u64 { 47 | *self.0.lock().unwrap() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/static-file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static-file" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo", features = ["static-file"] } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/static-file/assets/hello.txt: -------------------------------------------------------------------------------- 1 | hello -------------------------------------------------------------------------------- /examples/static-file/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello world 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /examples/static-file/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Write; 2 | use std::path::PathBuf; 3 | 4 | use boluo::BoxError; 5 | use boluo::handler::handler_fn; 6 | use boluo::response::{Html, IntoResponse}; 7 | use boluo::route::Router; 8 | use boluo::server::Server; 9 | use boluo::static_file::{ServeDir, ServeFile}; 10 | use tokio::net::TcpListener; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 15 | 16 | let app = Router::new() 17 | // 显示首页。 18 | .route("/", ServeFile::new(assets_dir().join("index.html"))) 19 | // 列出`assets`目录中的文件。 20 | .route("/assets/", handler_fn(list)) 21 | // 提供`assets`目录中的文件。 22 | .scope("/assets/{*}", ServeDir::new(assets_dir())); 23 | 24 | Server::new(listener).run(app).await.unwrap(); 25 | } 26 | 27 | async fn list() -> Result { 28 | let mut list = String::new(); 29 | 30 | for path in std::fs::read_dir(assets_dir())? 31 | .filter_map(|v| v.ok()) 32 | .filter(|v| v.path().is_file()) 33 | .map(|v| v.file_name()) 34 | { 35 | if let Some(path) = path.to_str() { 36 | write!(&mut list, "
  • {path}
  • ")?; 37 | } 38 | } 39 | 40 | Ok(Html(format!("
      {list}
    "))) 41 | } 42 | 43 | fn assets_dir() -> PathBuf { 44 | PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets") 45 | } 46 | -------------------------------------------------------------------------------- /examples/tls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tls" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | boluo = { path = "../../boluo" } 8 | tokio = { version = "1", features = ["full"] } 9 | tokio-rustls = "0.26" 10 | -------------------------------------------------------------------------------- /examples/tls/cert/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDkzCCAnugAwIBAgIUXVYkRCrM/ge03DVymDtXCuybp7gwDQYJKoZIhvcNAQEL 3 | BQAwWTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X 5 | DTIxMDczMTE0MjIxMloXDTIyMDczMTE0MjIxMlowWTELMAkGA1UEBhMCVVMxEzAR 6 | BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 7 | IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 8 | MIIBCgKCAQEA02V5ZjmqLB/VQwTarrz/35qsa83L+DbAoa0001+jVmmC+G9Nufi0 9 | daroFWj/Uicv2fZWETU8JoZKUrX4BK9og5cg5rln/CtBRWCUYIwRgY9R/CdBGPn4 10 | kp+XkSJaCw74ZIyLy/Zfux6h8ES1m9YRnBza+s7U+ImRBRf4MRPtXQ3/mqJxAZYq 11 | dOnKnvssRyD2qutgVTAxwMUvJWIivRhRYDj7WOpS4CEEeQxP1iH1/T5P7FdtTGdT 12 | bVBABCA8JhL96uFGPpOYHcM/7R5EIA3yZ5FNg931QzoDITjtXGtQ6y9/l/IYkWm6 13 | J67RWcN0IoTsZhz0WNU4gAeslVtJLofn8QIDAQABo1MwUTAdBgNVHQ4EFgQUzFnK 14 | NfS4LAYuKeWwHbzooER0yZ0wHwYDVR0jBBgwFoAUzFnKNfS4LAYuKeWwHbzooER0 15 | yZ0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAk4O+e9jia59W 16 | ZwetN4GU7OWcYhmOgSizRSs6u7mTfp62LDMt96WKU3THksOnZ44HnqWQxsSfdFVU 17 | XJD12tjvVU8Z4FWzQajcHeemUYiDze8EAh6TnxnUcOrU8IcwiKGxCWRY/908jnWg 18 | +MMscfMCMYTRdeTPqD8fGzAlUCtmyzH6KLE3s4Oo/r5+NR+Uvrwpdvb7xe0MwwO9 19 | Q/zR4N8ep/HwHVEObcaBofE1ssZLksX7ZgCP9wMgXRWpNAtC5EWxMbxYjBfWFH24 20 | fDJlBMiGJWg8HHcxK7wQhFh+fuyNzE+xEWPsI9VL1zDftd9x8/QsOagyEOnY8Vxr 21 | AopvZ09uEQ== 22 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /examples/tls/cert/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTZXlmOaosH9VD 3 | BNquvP/fmqxrzcv4NsChrTTTX6NWaYL4b025+LR1qugVaP9SJy/Z9lYRNTwmhkpS 4 | tfgEr2iDlyDmuWf8K0FFYJRgjBGBj1H8J0EY+fiSn5eRIloLDvhkjIvL9l+7HqHw 5 | RLWb1hGcHNr6ztT4iZEFF/gxE+1dDf+aonEBlip06cqe+yxHIPaq62BVMDHAxS8l 6 | YiK9GFFgOPtY6lLgIQR5DE/WIfX9Pk/sV21MZ1NtUEAEIDwmEv3q4UY+k5gdwz/t 7 | HkQgDfJnkU2D3fVDOgMhOO1ca1DrL3+X8hiRabonrtFZw3QihOxmHPRY1TiAB6yV 8 | W0kuh+fxAgMBAAECggEADltu8k1qTFLhJgsXWxTFAAe+PBgfCT2WuaRM2So+qqjB 9 | 12Of0MieYPt5hbK63HaC3nfHgqWt7yPhulpXfOH45C8IcgMXl93MMg0MJr58leMI 10 | +2ojFrIrerHSFm5R1TxwDEwrVm/mMowzDWFtQCc6zPJ8wNn5RuP48HKfTZ3/2fjw 11 | zEjSwPO2wFMfo1EJNTjlI303lFbdFBs67NaX6puh30M7Tn+gznHKyO5a7F57wkIt 12 | fkgnEy/sgMedQlwX7bRpUoD6f0fZzV8Qz4cHFywtYErczZJh3VGitJoO/VCIDdty 13 | RPXOAqVDd7EpP1UUehZlKVWZ0OZMEfRgKbRCel5abQKBgQDwgwrIQ5+BiZv6a0VT 14 | ETeXB+hRbvBinRykNo/RvLc3j1enRh9/zO/ShadZIXgOAiM1Jnr5Gp8KkNGca6K1 15 | myhtad7xYPODYzNXXp6T1OPgZxHZLIYzVUj6ypXeV64Te5ZiDaJ1D49czsq+PqsQ 16 | XRcgBJSNpFtDFiXWpjXWfx8PxwKBgQDhAnLY5Sl2eeQo+ud0MvjwftB/mN2qCzJY 17 | 5AlQpRI4ThWxJgGPuHTR29zVa5iWNYuA5LWrC1y/wx+t5HKUwq+5kxvs+npYpDJD 18 | ZX/w0Glc6s0Jc/mFySkbw9B2LePedL7lRF5OiAyC6D106Sc9V2jlL4IflmOzt4CD 19 | ZTNbLtC6hwKBgHfIzBXxl/9sCcMuqdg1Ovp9dbcZCaATn7ApfHd5BccmHQGyav27 20 | k7XF2xMJGEHhzqcqAxUNrSgV+E9vTBomrHvRvrd5Ec7eGTPqbBA0d0nMC5eeFTh7 21 | wV0miH20LX6Gjt9G6yJiHYSbeV5G1+vOcTYBEft5X/qJjU7aePXbWh0BAoGBAJlV 22 | 5tgCCuhvFloK6fHYzqZtdT6O+PfpW20SMXrgkvMF22h2YvgDFrDwqKRUB47NfHzg 23 | 3yBpxNH1ccA5/w97QO8w3gX3h6qicpJVOAPusu6cIBACFZfjRv1hyszOZwvw+Soa 24 | Fj5kHkqTY1YpkREPYS9V2dIW1Wjic1SXgZDw7VM/AoGAP/cZ3ZHTSCDTFlItqy5C 25 | rIy2AiY0WJsx+K0qcvtosPOOwtnGjWHb1gdaVdfX/IRkSsX4PAOdnsyidNC5/l/m 26 | y8oa+5WEeGFclWFhr4dnTA766o8HrM2UjIgWWYBF2VKdptGnHxFeJWFUmeQC/xeW 27 | w37pCS7ykL+7gp7V0WShYsw= 28 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /examples/tls/src/main.rs: -------------------------------------------------------------------------------- 1 | mod tls_listener; 2 | use tls_listener::{TlsListener, new_acceptor}; 3 | 4 | use std::path::PathBuf; 5 | 6 | use boluo::response::IntoResponse; 7 | use boluo::route::Router; 8 | use boluo::server::Server; 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let acceptor = new_acceptor( 13 | PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("cert/cert.pem"), 14 | PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("cert/key.pem"), 15 | ) 16 | .unwrap(); 17 | let listener = TlsListener::bind("127.0.0.1:3000", acceptor).await.unwrap(); 18 | 19 | let app = Router::new().mount(hello); 20 | 21 | Server::new(listener).run(app).await.unwrap(); 22 | } 23 | 24 | #[boluo::route("/", method = "GET")] 25 | async fn hello() -> impl IntoResponse { 26 | "Hello, World!" 27 | } 28 | -------------------------------------------------------------------------------- /examples/tls/src/tls_listener.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::sync::Arc; 3 | 4 | use boluo::BoxError; 5 | use boluo::listener::{ConnectInfo, Listener}; 6 | use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; 7 | use tokio_rustls::TlsAcceptor; 8 | use tokio_rustls::rustls::ServerConfig; 9 | use tokio_rustls::rustls::pki_types::pem::PemObject; 10 | use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; 11 | use tokio_rustls::server::TlsStream; 12 | 13 | pub struct TlsListener { 14 | listener: TcpListener, 15 | acceptor: TlsAcceptor, 16 | } 17 | 18 | impl TlsListener { 19 | pub async fn bind(addr: A, acceptor: TlsAcceptor) -> Result 20 | where 21 | A: ToSocketAddrs, 22 | { 23 | Ok(Self { 24 | listener: TcpListener::bind(addr).await?, 25 | acceptor, 26 | }) 27 | } 28 | } 29 | 30 | impl Listener for TlsListener { 31 | type IO = TlsStream; 32 | type Addr = ConnectInfo; 33 | type Error = BoxError; 34 | 35 | async fn accept(&mut self) -> Result<(Self::IO, Self::Addr), Self::Error> { 36 | loop { 37 | let (stream, remote) = self.listener.accept().await?; 38 | 39 | let stream = match self.acceptor.accept(stream).await { 40 | Ok(stream) => stream, 41 | Err(error) => { 42 | println!("error: {error}"); 43 | continue; 44 | } 45 | }; 46 | 47 | let connect_info = ConnectInfo { 48 | local: self.listener.local_addr()?, 49 | remote, 50 | }; 51 | 52 | return Ok((stream, connect_info)); 53 | } 54 | } 55 | } 56 | 57 | pub fn new_acceptor(cert: T, key: K) -> Result 58 | where 59 | T: AsRef, 60 | K: AsRef, 61 | { 62 | let certs = CertificateDer::pem_file_iter(cert)?.collect::, _>>()?; 63 | let key = PrivateKeyDer::from_pem_file(key)?; 64 | Ok(TlsAcceptor::from(Arc::new( 65 | ServerConfig::builder() 66 | .with_no_client_auth() 67 | .with_single_cert(certs, key)?, 68 | ))) 69 | } 70 | -------------------------------------------------------------------------------- /examples/ws/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ws" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | boluo = { path = "../../boluo", features = ["ws"] } 10 | tokio = { version = "1", features = ["full"] } 11 | -------------------------------------------------------------------------------- /examples/ws/src/main.rs: -------------------------------------------------------------------------------- 1 | use boluo::response::IntoResponse; 2 | use boluo::route::Router; 3 | use boluo::server::Server; 4 | use boluo::ws::WebSocketUpgrade; 5 | use tokio::net::TcpListener; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap(); 10 | 11 | let app = Router::new().mount(echo); 12 | 13 | Server::new(listener).run(app).await.unwrap(); 14 | } 15 | 16 | #[boluo::route("/", method = "GET")] 17 | async fn echo(upgrade: WebSocketUpgrade) -> impl IntoResponse { 18 | upgrade.on_upgrade(|mut socket| async move { 19 | while let Some(Ok(message)) = socket.recv().await { 20 | if let Err(e) = socket.send(message).await { 21 | eprintln!("{e}"); 22 | } 23 | } 24 | }) 25 | } 26 | --------------------------------------------------------------------------------