├── .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