├── .cursorignore ├── .github └── workflows │ ├── ci.yml │ └── clippy.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── crates ├── bencher │ ├── Cargo.toml │ ├── benches │ │ └── decoder.rs │ ├── resources │ │ └── request │ │ │ ├── get_large.txt │ │ │ └── get_small.txt │ └── src │ │ └── lib.rs ├── http │ ├── Cargo.toml │ ├── README.md │ ├── benches │ │ └── http_bench.rs │ ├── examples │ │ └── server.rs │ └── src │ │ ├── codec │ │ ├── body │ │ │ ├── chunked_decoder.rs │ │ │ ├── chunked_encoder.rs │ │ │ ├── length_decoder.rs │ │ │ ├── length_encoder.rs │ │ │ ├── mod.rs │ │ │ ├── payload_decoder.rs │ │ │ └── payload_encoder.rs │ │ ├── header │ │ │ ├── header_decoder.rs │ │ │ ├── header_encoder.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── request_decoder.rs │ │ └── response_encoder.rs │ │ ├── connection │ │ ├── http_connection.rs │ │ └── mod.rs │ │ ├── handler │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── protocol │ │ ├── body │ │ │ ├── body_channel.rs │ │ │ ├── mod.rs │ │ │ └── req_body.rs │ │ ├── error.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ ├── request.rs │ │ └── response.rs │ │ └── utils.rs └── web │ ├── Cargo.toml │ ├── README.md │ ├── examples │ ├── getting_started.rs │ ├── hello_world.rs │ ├── return_uid.rs │ └── sse_server.rs │ └── src │ ├── body.rs │ ├── date │ ├── date_service_decorator.rs │ └── mod.rs │ ├── encoding │ ├── encoder.rs │ └── mod.rs │ ├── extract │ ├── extract_body.rs │ ├── extract_header.rs │ ├── extract_tuple.rs │ ├── extract_url.rs │ ├── from_request.rs │ └── mod.rs │ ├── fn_trait.rs │ ├── handler.rs │ ├── handler │ ├── handler_decorator.rs │ └── handler_decorator_factory.rs │ ├── lib.rs │ ├── request.rs │ ├── responder.rs │ ├── responder │ └── sse.rs │ ├── router │ ├── filter.rs │ └── mod.rs │ └── server.rs ├── deploy.md └── rustfmt.toml /.cursorignore: -------------------------------------------------------------------------------- 1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Build & Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build_and_test: 15 | name: Rust ${{matrix.rust}} 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | rust: [nightly, beta, stable, 1.85] #1.85 is the MSRV 21 | timeout-minutes: 90 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | - name: Install Rust (${{ matrix.rust }}) 26 | uses: dtolnay/rust-toolchain@master 27 | with: 28 | toolchain: ${{matrix.rust}} 29 | 30 | - name: Build 31 | run: cargo build 32 | 33 | - name: Test 34 | run: cargo test 35 | 36 | clippy: 37 | name: Clippy 38 | runs-on: ubuntu-latest 39 | if: github.event_name != 'pull_request' 40 | timeout-minutes: 60 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v3 44 | - name: Install Rust 45 | uses: dtolnay/rust-toolchain@master 46 | with: 47 | toolchain: stable 48 | components: clippy 49 | - name: Clippy 50 | run: cargo clippy 51 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Clippy 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | clippy: 15 | name: Clippy 16 | runs-on: ubuntu-latest 17 | if: github.event_name != 'pull_request' 18 | timeout-minutes: 60 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | - name: Install Rust 23 | uses: dtolnay/rust-toolchain@master 24 | with: 25 | toolchain: stable 26 | components: clippy 27 | - name: Clippy 28 | run: cargo clippy 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | guide/build/ 4 | /gh-pages 5 | 6 | *.so 7 | *.out 8 | *.pyc 9 | *.pid 10 | *.sock 11 | *~ 12 | .DS_Store 13 | 14 | # These are backup files generated by rustfmt 15 | **/*.rs.bk 16 | 17 | # Configuration directory generated by CLion 18 | .idea 19 | 20 | # Configuration directory generated by VSCode 21 | .vscode 22 | 23 | flamegraph.svg 24 | micro-http.txt 25 | dhat-heap.json 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/http", "crates/web", "crates/bencher"] 3 | resolver = "3" 4 | 5 | # see: https://doc.rust-lang.org/cargo/reference/workspaces.html 6 | [workspace.package] 7 | authors = ["Zava "] 8 | edition = "2024" 9 | homepage = "https://github.com/foldright/micro-http" 10 | license = "MIT OR Apache-2.0" 11 | readme = "README.md" 12 | repository = "https://github.com/foldright/micro-http" 13 | rust-version = "1.85" 14 | 15 | [workspace.dependencies] 16 | http = "1.2.0" 17 | http-body = "1.0.1" 18 | http-body-util = "0.1.2" 19 | faf-http-date = "0.1" 20 | mime = "0.3.17" 21 | 22 | httparse = "1.10.0" 23 | 24 | tracing = "0.1.41" 25 | tracing-subscriber = "0.3.19" 26 | 27 | tokio = { version = "1", features = ["full", "tracing"] } 28 | tokio-util = { version = "0.7.13", features = ["codec", "io", "tracing"] } 29 | async-trait = "0.1.86" 30 | futures = "0.3.31" 31 | bytes = "1.10.0" 32 | pin-project-lite = "0.2.16" 33 | 34 | dynosaur = "0.1" 35 | trait-variant = "0.1" 36 | 37 | hlist2 = "0.0.15" 38 | tower-layer = "0.3" 39 | 40 | serde = { version = "1.0.217", features = ["derive"] } 41 | serde_urlencoded = "0.7.1" 42 | serde_json = "1.0.138" 43 | serde_qs = "0.13.0" 44 | 45 | flate2 = "1.0.35" 46 | zstd = "0.13.2" 47 | brotli = "7.0.0" 48 | 49 | thiserror = "2" 50 | 51 | arc-swap = "1.7.1" 52 | once_cell = "1.21.3" 53 | 54 | higher-kinded-types = "0.1" 55 | 56 | matchit = "0.8.6" 57 | 58 | mockall = "0.13.1" 59 | criterion = "0.5" 60 | 61 | codspeed-criterion-compat = { version = "2.6.0", default-features = false } 62 | 63 | dhat = "0.3" 64 | 65 | micro-web = { path = "crates/web", version = "0.1.1" } 66 | micro-http = { path = "crates/http", version = "0.1.1" } 67 | 68 | [profile.dev.build-override] 69 | debug = true 70 | [profile.release.build-override] 71 | debug = true 72 | [profile.release] 73 | opt-level = 3 74 | lto = "thin" 75 | codegen-units = 1 76 | debug = true 77 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro Http 2 | 3 | ![Crates.io](https://img.shields.io/crates/l/micro-web) 4 | ![Crates.io](https://img.shields.io/crates/v/micro-web) 5 | [![Actions Status](https://github.com/foldright/micro-http/actions/workflows/ci.yml/badge.svg)](https://github.com/foldright/micro-http/actions) 6 | [![Actions Status](https://github.com/foldright/micro-http/actions/workflows/clippy.yml/badge.svg)](https://github.com/foldright/micro-http/actions) 7 | 8 | A lightweight, efficient, and modular HTTP server implementation built on top of tokio. 9 | 10 | ## Features 11 | 12 | - Full HTTP/1.1 protocol support, HTTP/2 currently unsupported 13 | - Asynchronous I/O using tokio 14 | - Streaming request and response bodies 15 | - Chunked transfer encoding 16 | - Keep-alive connections 17 | - Expect-continue mechanism 18 | - Efficient memory usage through zero-copy parsing 19 | - Structured logging with tracing 20 | 21 | ## Crates 22 | 23 | This workspace contains the following crates: 24 | 25 | - [micro-http](crates/http/README.md): Core HTTP protocol implementation 26 | - Zero-copy parsing 27 | - Streaming bodies 28 | - Full protocol compliance 29 | - [Example server](crates/http/examples/server.rs) 30 | 31 | - [micro-web](crates/web/README.md): High-level web framework 32 | - Routing 33 | - Middleware support 34 | - Request/Response abstractions 35 | - [Getting started guide](crates/web/examples/getting_started.rs) 36 | 37 | ## Quick Start 38 | 39 | Add this to your `Cargo.toml`: 40 | 41 | ```toml 42 | [dependencies] 43 | micro-web = "0.1" 44 | tokio = { version = "1", features = ["full"] } 45 | ``` 46 | 47 | See the [getting started example](crates/web/examples/getting_started.rs) for a complete working example. 48 | 49 | ## Performance 50 | 51 | For performance benchmarks and comparisons, see (we are not in this benchmark yes, but we will): 52 | - [Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/result?l=rust) 53 | 54 | ## Development 55 | 56 | - [Deploy new version guide](deploy.md) 57 | 58 | ## MSRV 59 | 60 | The Minimum Supported Rust Version is 1.85 61 | 62 | ## License 63 | 64 | Licensed under either of: 65 | 66 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 67 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 68 | 69 | at your option. 70 | 71 | ## Contributing 72 | 73 | Contributions are welcome! Please feel free to submit a Pull Request. 74 | -------------------------------------------------------------------------------- /crates/bencher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bencher" 3 | version = "0.0.0" 4 | edition = "2021" 5 | description = "micro-http benchmarks" 6 | authors.workspace = true 7 | homepage.workspace = true 8 | license.workspace = true 9 | readme.workspace = true 10 | repository.workspace = true 11 | rust-version.workspace = true 12 | # keep the package private, see https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field 13 | publish = false 14 | 15 | [package.metadata.workspaces] 16 | independent = true 17 | 18 | [lib] 19 | bench = false 20 | test = false 21 | doctest = false 22 | 23 | [[bench]] 24 | name = "decoder" 25 | harness = false 26 | 27 | [dependencies] 28 | codspeed-criterion-compat = { workspace = true, default-features = false, optional = true } 29 | criterion = { workspace = true, default-features = false } 30 | 31 | 32 | [dev-dependencies] 33 | micro-http.workspace = true 34 | micro-web.workspace = true 35 | tokio-util.workspace = true 36 | 37 | [features] 38 | codspeed = ["codspeed-criterion-compat"] 39 | -------------------------------------------------------------------------------- /crates/bencher/benches/decoder.rs: -------------------------------------------------------------------------------- 1 | use bencher::{TestCase, TestFile}; 2 | use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput}; 3 | use micro_http::codec::RequestDecoder; 4 | use tokio_util::bytes::BytesMut; 5 | use tokio_util::codec::Decoder; 6 | 7 | static SMALL_HEADER: TestFile = TestFile::new("get_small.txt", include_str!("../resources/request/get_small.txt")); 8 | static LARGE_HEADER: TestFile = TestFile::new("get_large.txt", include_str!("../resources/request/get_large.txt")); 9 | 10 | fn create_test_cases() -> Vec { 11 | vec![TestCase::normal("small_header_decoder", SMALL_HEADER.clone()), TestCase::normal("large_header_decoder", LARGE_HEADER.clone())] 12 | } 13 | 14 | fn benchmark_request_decoder(criterion: &mut Criterion) { 15 | let test_cases = create_test_cases(); 16 | let mut group = criterion.benchmark_group("request_decoder"); 17 | 18 | for case in test_cases { 19 | group.throughput(Throughput::Bytes(case.file().content().len() as u64)); 20 | group.bench_with_input(BenchmarkId::from_parameter(case.name()), &case, |b, case| { 21 | let mut request_decoder = RequestDecoder::new(); 22 | b.iter_batched_ref( 23 | || BytesMut::from(case.file().content()), 24 | |bytes_mut| { 25 | let header = request_decoder.decode(bytes_mut).expect("input should be valide http request header").unwrap(); 26 | let body = request_decoder.decode(bytes_mut).expect("input should be valide http request body").unwrap(); 27 | black_box((header, body)); 28 | }, 29 | BatchSize::SmallInput, 30 | ); 31 | }); 32 | } 33 | 34 | group.finish(); 35 | } 36 | 37 | criterion_group!(decoder, benchmark_request_decoder); 38 | criterion_main!(decoder); 39 | -------------------------------------------------------------------------------- /crates/bencher/resources/request/get_large.txt: -------------------------------------------------------------------------------- 1 | GET /test HTTP/1.1 2 | Host: example.com 3 | User-Agent: perf-test/1.0 4 | Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, image/apng, /;q=0.8 5 | Accept-Encoding: gzip, deflate, br 6 | Accept-Language: en-US, en;q=0.9 7 | Connection: keep-alive 8 | Cache-Control: no-cache 9 | Pragma: no-cache 10 | DNT: 1 11 | Referer: https://example.com/path/to/resource?param1=value1¶m2=value2&tracking=verylongtrackingcode1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMN&extra_tracking=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 12 | Upgrade-Insecure-Requests: 1 13 | X-Forwarded-For: 192.168.1.1, 203.0.113.195, 10.0.0.1, 172.16.0.1 14 | X-Real-IP: 203.0.113.195 15 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp...superlongtokenvalue1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ 16 | Cookie: sessionid=abc123; csrftoken=xyz456; theme=dark; user_id=987654321; lang=en-US; pref=high-performance-mode; 17 | tracking_id: long-tracking-cookie-0123456789abcdef0123456789abcdef; 18 | security_token: secureRandomValueHere; 19 | analytics_session: another-long-session-value-abcdef1234567890; 20 | user_prefs: theme=dark; layout=compact; timezone=UTC+0; 21 | experiment_group: beta-user; device_type=desktop; 22 | test_cookie: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789; 23 | extended_cookie: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRST; 24 | Content-Type: application/json 25 | Content-Length: 16384 26 | X-Requested-With: XMLHttpRequest 27 | X-Device-ID: unique-device-id-9876543210-extendedvalue0987654321 28 | X-API-Version: v1.42.9999 29 | X-Request-ID: req-abcdefgh-1234-ijkl-5678-mnopqrstuvwx-extended 30 | X-Session-Hash: sha256-df78a3cbd0123456789abcdef0123456789abcdef-extendedhash 31 | X-Debug-Flag: true 32 | X-Cache-Control: max-age=0, must-revalidate 33 | X-Client-IP: 198.51.100.77 34 | X-Original-URL: /original/request/path?query=param&morequery=data1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ 35 | Forwarded: for=198.51.100.77; proto=https 36 | Custom-Header-1: This is a long custom header value meant to test server performance under load with an even longer extension to make sure the test is valid and comprehensive. 37 | Custom-Header-2: Another extended custom header with random payload 0123456789ABCDEF plus additional extended data for load testing. 38 | Custom-Header-3: Yet another custom header with a mix of alphanumeric values and symbols!@#$%^&*() that has been extended significantly for stress testing. 39 | Custom-Header-4: {"json_field_1": "long_value_1234567890_extended", "json_field_2": "another_value_abcdef_extended"} 40 | Custom-Header-5: lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua now with an even longer body to increase the request size significantly. 41 | Custom-Header-6: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 42 | Custom-Header-7: More random data to increase size 1234567890abcdefABCDEF! and even more appended to make it a longer value for testing purposes. 43 | Custom-Header-8: Performance testing header with long values repeated multiple times! Performance testing header with long values repeated multiple times! Performance testing header with long values repeated multiple times! 44 | Custom-Header-9: Additional header for stress testing purposes with excessive length to push server limits and simulate real-world large header sizes. 45 | Custom-Header-10: JSON payload {"key1": "verylongvalue1_extended", "key2": "verylongvalue2_extended", "key3": "verylongvalue3_extended"} 46 | Custom-Header-11: Extended stress test header with a long string of alphanumeric characters: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 47 | Custom-Header-12: Additional custom header to ensure the request size is sufficiently large to test server performance and scaling capabilities under load. 48 | 49 | -------------------------------------------------------------------------------- /crates/bencher/resources/request/get_small.txt: -------------------------------------------------------------------------------- 1 | GET /user/123 HTTP/1.1 2 | Host: example.com 3 | User-Agent: perf-test/1.0 4 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 5 | Accept-Encoding: gzip, deflate, br 6 | Accept-Language: en-US,en;q=0.9 7 | Connection: keep-alive 8 | Cache-Control: no-cache 9 | Pragma: no-cache 10 | DNT: 1 11 | Referer: https://example.com/ 12 | Upgrade-Insecure-Requests: 1 13 | X-Forwarded-For: 192.168.1.1, 203.0.113.195 14 | X-Real-IP: 203.0.113.195 15 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp... 16 | Cookie: sessionid=abc123; csrftoken=xyz456; theme=dark 17 | Content-Type: application/json 18 | Content-Length: 1024 19 | Custom-Header-1: A very long custom header value to simulate load 20 | Custom-Header-2: Another long header with random data 0123456789abcdef 21 | 22 | -------------------------------------------------------------------------------- /crates/bencher/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone)] 2 | pub struct TestCase { 3 | name: &'static str, 4 | group: TestGroup, 5 | file: TestFile, 6 | } 7 | 8 | impl TestCase { 9 | pub fn new(name: &'static str, group: TestGroup, file: TestFile) -> Self { 10 | Self { name, group, file } 11 | } 12 | 13 | pub fn small(name: &'static str, file: TestFile) -> Self { 14 | Self::new(name, TestGroup::Small, file) 15 | } 16 | 17 | pub fn normal(name: &'static str, file: TestFile) -> Self { 18 | Self::new(name, TestGroup::Normal, file) 19 | } 20 | 21 | pub fn large(name: &'static str, file: TestFile) -> Self { 22 | Self::new(name, TestGroup::Large, file) 23 | } 24 | 25 | pub fn name(&self) -> &'static str { 26 | self.name 27 | } 28 | 29 | pub fn group(&self) -> TestGroup { 30 | self.group 31 | } 32 | 33 | pub fn file(&self) -> &TestFile { 34 | &self.file 35 | } 36 | 37 | pub fn file_name(&self) -> &'static str { 38 | self.file().file_name 39 | } 40 | } 41 | 42 | #[derive(Debug, Copy, Clone)] 43 | pub struct TestFile { 44 | file_name: &'static str, 45 | content: &'static str, 46 | } 47 | 48 | impl TestFile { 49 | pub const fn new(file_name: &'static str, content: &'static str) -> Self { 50 | Self { file_name, content } 51 | } 52 | 53 | pub fn content(&self) -> &'static str { 54 | self.content 55 | } 56 | 57 | pub fn file_name(&self) -> &'static str { 58 | self.file_name 59 | } 60 | } 61 | 62 | #[derive(Clone, Copy, Debug)] 63 | pub enum TestGroup { 64 | Small, 65 | Normal, 66 | Large, 67 | } 68 | -------------------------------------------------------------------------------- /crates/http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "micro-http" 3 | version = "0.1.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | rust-version.workspace = true 10 | readme = "README.md" 11 | keywords = ["http", "async", "web"] 12 | categories = ["web-programming::http-server", "network-programming"] 13 | description = "the async micro http server" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | httparse.workspace = true 19 | http.workspace = true 20 | http-body.workspace = true 21 | http-body-util.workspace = true 22 | mime.workspace = true 23 | 24 | tracing.workspace = true 25 | tracing-subscriber.workspace = true 26 | 27 | bytes.workspace = true 28 | tokio.workspace = true 29 | tokio-util = {workspace = true, features = ["tracing"]} 30 | futures.workspace = true 31 | trait-variant.workspace = true 32 | 33 | thiserror.workspace = true 34 | 35 | [dev-dependencies] 36 | indoc = "2.0.5" 37 | criterion = { workspace = true, features = ["async_tokio", "html_reports"] } 38 | 39 | 40 | [[bench]] 41 | name = "http_bench" 42 | harness = false 43 | -------------------------------------------------------------------------------- /crates/http/README.md: -------------------------------------------------------------------------------- 1 | # Micro HTTP 2 | 3 | A lightweight, efficient, and modular HTTP/1.1 server implementation built on top of tokio. 4 | 5 | ## Features 6 | 7 | - Full HTTP/1.1 protocol support 8 | - Asynchronous I/O using tokio 9 | - Streaming request and response bodies 10 | - Chunked transfer encoding 11 | - Keep-alive connections 12 | - Expect-continue mechanism 13 | - Efficient memory usage through zero-copy parsing 14 | - Clean error handling 15 | - Structured logging with tracing 16 | 17 | ## Quick Start 18 | 19 | Add this to your `Cargo.toml`: 20 | 21 | ```toml 22 | [dependencies] 23 | micro-http = "0.1" 24 | tokio = { version = "1", features = ["full"] } 25 | http = "1" 26 | http-body = "1" 27 | tracing = "0.1" 28 | tracing-subscriber = "0.3" 29 | ``` 30 | 31 | ## Example 32 | 33 | Here's a simple HTTP server that responds with "Hello World!": 34 | 35 | ```rust 36 | use http::{Request, Response, StatusCode}; 37 | use http_body_util::BodyExt; 38 | use std::error::Error; 39 | use std::sync::Arc; 40 | 41 | use tokio::net::TcpListener; 42 | 43 | use micro_http::connection::HttpConnection; 44 | use micro_http::handler::make_handler; 45 | use micro_http::protocol::body::ReqBody; 46 | use tracing::{error, info, warn, Level}; 47 | use tracing_subscriber::FmtSubscriber; 48 | 49 | #[tokio::main] 50 | async fn main() { 51 | let subscriber = FmtSubscriber::builder().with_max_level(Level::INFO).finish(); 52 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 53 | 54 | info!(port = 8080, "start listening"); 55 | let tcp_listener = match TcpListener::bind("127.0.0.1:8080").await { 56 | Ok(tcp_listener) => tcp_listener, 57 | Err(e) => { 58 | error!(cause = %e, "bind server error"); 59 | return; 60 | } 61 | }; 62 | 63 | let handler = make_handler(simple_handler); 64 | let handler = Arc::new(handler); 65 | loop { 66 | let (tcp_stream, _remote_addr) = match tcp_listener.accept().await { 67 | Ok(stream_and_addr) => stream_and_addr, 68 | Err(e) => { 69 | warn!(cause = %e, "failed to accept"); 70 | continue; 71 | } 72 | }; 73 | 74 | let handler = handler.clone(); 75 | 76 | // one connection per task 77 | tokio::spawn(async move { 78 | let (reader, writer) = tcp_stream.into_split(); 79 | let connection = HttpConnection::new(reader, writer); 80 | match connection.process(handler).await { 81 | Ok(_) => { 82 | info!("finished process, connection shutdown"); 83 | } 84 | Err(e) => { 85 | error!("service has error, cause {}, connection shutdown", e); 86 | } 87 | } 88 | }); 89 | } 90 | } 91 | 92 | async fn simple_handler(request: Request) -> Result, Box> { 93 | let path = request.uri().path().to_string(); 94 | info!("request path {}", path); 95 | 96 | let (_header, body) = request.into_parts(); 97 | 98 | let body_bytes = body.collect().await?.to_bytes(); 99 | info!(body = std::str::from_utf8(&body_bytes[..]).unwrap(), "receiving request body"); 100 | 101 | let response_body = "Hello World!\r\n"; 102 | let response = Response::builder() 103 | .status(StatusCode::OK) 104 | .header(http::header::CONTENT_LENGTH, response_body.len()) 105 | .body(response_body.to_string()) 106 | .unwrap(); 107 | 108 | Ok(response) 109 | } 110 | ``` 111 | 112 | ## Architecture 113 | 114 | The crate is organized into several key modules: 115 | 116 | - `connection`: Core connection handling and lifecycle management 117 | - `protocol`: Protocol types and abstractions 118 | - `codec`: Protocol encoding/decoding implementation 119 | - `handler`: Request handler traits and utilities 120 | 121 | ## Performance Considerations 122 | 123 | The implementation focuses on performance through: 124 | 125 | - Zero-copy parsing where possible 126 | - Efficient buffer management 127 | - Streaming processing of bodies 128 | - Concurrent request/response handling 129 | - Connection keep-alive 130 | 131 | ## Limitations 132 | 133 | - HTTP/1.1 only (HTTP/2 or HTTP/3 currently not supported yet) 134 | - No TLS support (use a reverse proxy for HTTPS) 135 | - Maximum header size: 8KB 136 | - Maximum number of headers: 64 137 | 138 | ## Safety 139 | 140 | The crate uses unsafe code in a few well-documented places for performance optimization, particularly in header parsing. All unsafe usage is carefully reviewed and tested. 141 | 142 | ## License 143 | 144 | This project is licensed under the MIT License or Apache-2.0 License, pick one. 145 | 146 | ## Contributing 147 | 148 | Contributions are welcome! Please feel free to submit a Pull Request. 149 | -------------------------------------------------------------------------------- /crates/http/benches/http_bench.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use criterion::{Criterion, black_box, criterion_group, criterion_main}; 3 | use futures::executor::block_on; 4 | use http::{Request, Response, StatusCode}; 5 | use micro_http::handler::make_handler; 6 | use micro_http::{ 7 | codec::{RequestDecoder, ResponseEncoder}, 8 | connection::HttpConnection, 9 | protocol::{Message, PayloadSize, ResponseHead, body::ReqBody}, 10 | }; 11 | use std::{ 12 | error::Error, 13 | io, 14 | pin::Pin, 15 | sync::Arc, 16 | task::{Context, Poll}, 17 | }; 18 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 19 | use tokio_util::codec::{Decoder, Encoder}; 20 | 21 | // Mock IO for testing 22 | #[derive(Clone)] 23 | struct MockIO { 24 | read_data: Vec, 25 | write_data: Vec, 26 | read_pos: usize, 27 | } 28 | 29 | impl MockIO { 30 | fn new(read_data: Vec) -> Self { 31 | Self { read_data, write_data: Vec::new(), read_pos: 0 } 32 | } 33 | } 34 | 35 | impl AsyncRead for MockIO { 36 | fn poll_read(mut self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { 37 | let remaining = &self.read_data[self.read_pos..]; 38 | let amt = std::cmp::min(remaining.len(), buf.remaining()); 39 | buf.put_slice(&remaining[..amt]); 40 | self.read_pos += amt; 41 | Poll::Ready(Ok(())) 42 | } 43 | } 44 | 45 | impl AsyncWrite for MockIO { 46 | fn poll_write(mut self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll> { 47 | self.write_data.extend_from_slice(buf); 48 | Poll::Ready(Ok(buf.len())) 49 | } 50 | 51 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 52 | Poll::Ready(Ok(())) 53 | } 54 | 55 | fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 56 | Poll::Ready(Ok(())) 57 | } 58 | } 59 | 60 | // Test handler 61 | async fn test_handler(_req: Request) -> Result, Box> { 62 | let response = Response::builder().status(StatusCode::OK).body("Hello World!".to_string()).unwrap(); 63 | Ok(response) 64 | } 65 | 66 | use indoc::indoc; 67 | 68 | static REQUEST: &'static str = indoc! {r##" 69 | GET /user/123 HTTP/1.1 70 | Host: 127.0.0.1:3000 71 | Sec-Fetch-Dest: document 72 | Sec-Fetch-Mode: navigate 73 | Sec-Fetch-Site: none 74 | Sec-Fetch-User: ?1 75 | sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132" 76 | sec-ch-ua-mobile: ?0 77 | sec-ch-ua-platform: "macOS" 78 | Cache-Control: max-age=0 79 | Connection: keep-alive 80 | Upgrade-Insecure-Requests: 1 81 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0 82 | Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 83 | Accept-Encoding: gzip, deflate, br, zstd 84 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 85 | 86 | "##}; 87 | 88 | fn bench_request_decoder(c: &mut Criterion) { 89 | c.bench_function("decode_simple_request", |b| { 90 | b.iter(|| { 91 | let request = REQUEST.as_bytes(); 92 | let mut decoder = RequestDecoder::new(); 93 | let mut bytes = bytes::BytesMut::from(&request[..]); 94 | black_box(decoder.decode(&mut bytes).unwrap()); 95 | }); 96 | }); 97 | } 98 | 99 | fn bench_response_encoder(c: &mut Criterion) { 100 | let response = Response::builder().status(StatusCode::OK).body("Hello World!".to_string()).unwrap(); 101 | 102 | c.bench_function("encode_simple_response", |b| { 103 | b.iter(|| { 104 | let mut encoder = ResponseEncoder::new(); 105 | let mut bytes = bytes::BytesMut::new(); 106 | let (header, body) = response.clone().into_parts(); 107 | let payload_size = body.as_bytes().len(); 108 | let message = Message::<_, Bytes>::Header((ResponseHead::from_parts(header, ()), PayloadSize::Length(payload_size as u64))); 109 | black_box(encoder.encode(message, &mut bytes).unwrap()); 110 | }); 111 | }); 112 | } 113 | 114 | fn bench_http_connection(c: &mut Criterion) { 115 | let request = REQUEST.as_bytes(); 116 | let handler = Arc::new(make_handler(test_handler)); 117 | 118 | c.bench_function("process_simple_request", |b| { 119 | b.iter(|| { 120 | let mock_io = MockIO::new(request.to_vec()); 121 | let (reader, writer) = (mock_io.clone(), mock_io); 122 | let connection = HttpConnection::new(reader, writer); 123 | black_box(block_on(connection.process(handler.clone())).unwrap()); 124 | }); 125 | }); 126 | } 127 | 128 | criterion_group!(benches, bench_request_decoder, bench_response_encoder, bench_http_connection); 129 | criterion_main!(benches); 130 | -------------------------------------------------------------------------------- /crates/http/examples/server.rs: -------------------------------------------------------------------------------- 1 | use http::{Request, Response, StatusCode}; 2 | use http_body_util::BodyExt; 3 | use std::error::Error; 4 | use std::sync::Arc; 5 | 6 | use tokio::net::TcpListener; 7 | 8 | use micro_http::connection::HttpConnection; 9 | use micro_http::handler::make_handler; 10 | use micro_http::protocol::body::ReqBody; 11 | use tracing::{Level, error, info, warn}; 12 | use tracing_subscriber::FmtSubscriber; 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | let subscriber = FmtSubscriber::builder().with_max_level(Level::INFO).finish(); 17 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 18 | 19 | info!(port = 8080, "start listening"); 20 | let tcp_listener = match TcpListener::bind("127.0.0.1:8080").await { 21 | Ok(tcp_listener) => tcp_listener, 22 | Err(e) => { 23 | error!(cause = %e, "bind server error"); 24 | return; 25 | } 26 | }; 27 | 28 | let handler = make_handler(simple_handler); 29 | let handler = Arc::new(handler); 30 | loop { 31 | let (tcp_stream, _remote_addr) = match tcp_listener.accept().await { 32 | Ok(stream_and_addr) => stream_and_addr, 33 | Err(e) => { 34 | warn!(cause = %e, "failed to accept"); 35 | continue; 36 | } 37 | }; 38 | 39 | let handler = handler.clone(); 40 | 41 | // one connection per task 42 | tokio::spawn(async move { 43 | let (reader, writer) = tcp_stream.into_split(); 44 | let connection = HttpConnection::new(reader, writer); 45 | match connection.process(handler).await { 46 | Ok(_) => { 47 | info!("finished process, connection shutdown"); 48 | } 49 | Err(e) => { 50 | error!("service has error, cause {}, connection shutdown", e); 51 | } 52 | } 53 | }); 54 | } 55 | } 56 | 57 | async fn simple_handler(request: Request) -> Result, Box> { 58 | let path = request.uri().path().to_string(); 59 | info!("request path {}", path); 60 | 61 | let (_header, body) = request.into_parts(); 62 | 63 | let body_bytes = body.collect().await?.to_bytes(); 64 | info!(body = std::str::from_utf8(&body_bytes[..]).unwrap(), "receiving request body"); 65 | 66 | let response_body = "Hello World!\r\n"; 67 | let response = Response::builder() 68 | .status(StatusCode::OK) 69 | .header(http::header::CONTENT_LENGTH, response_body.len()) 70 | .body(response_body.to_string()) 71 | .unwrap(); 72 | 73 | Ok(response) 74 | } 75 | -------------------------------------------------------------------------------- /crates/http/src/codec/body/chunked_encoder.rs: -------------------------------------------------------------------------------- 1 | //! Encoder implementation for HTTP chunked transfer encoding. 2 | //! 3 | //! This module provides functionality to encode HTTP messages using chunked transfer encoding 4 | //! as specified in [RFC 7230 Section 4.1](https://tools.ietf.org/html/rfc7230#section-4.1). 5 | //! 6 | //! The chunked encoding allows the sender to transmit message data in a series of chunks, 7 | //! where each chunk is prefixed with its size in hexadecimal format. 8 | 9 | use crate::protocol::{PayloadItem, SendError}; 10 | use bytes::{Buf, BytesMut}; 11 | use std::io::Write; 12 | use tokio_util::codec::Encoder; 13 | 14 | /// An encoder for handling HTTP chunked transfer encoding. 15 | /// 16 | /// The encoder converts message data into chunks according to the chunked format: 17 | /// - Each chunk starts with its size in hexadecimal 18 | /// - Followed by CRLF 19 | /// - Then the chunk data and CRLF 20 | /// - A zero-sized chunk indicates the end of the message 21 | #[derive(Debug, Clone, PartialEq, Eq)] 22 | pub struct ChunkedEncoder { 23 | /// Indicates if the final zero-length chunk has been sent 24 | eof: bool, 25 | /// Size of the current chunk being sent 26 | send_size: usize, 27 | } 28 | 29 | impl ChunkedEncoder { 30 | /// Creates a new ChunkedEncoder instance. 31 | /// 32 | /// The encoder starts in a non-EOF state, ready to encode chunks. 33 | pub fn new() -> Self { 34 | Self { eof: false, send_size: 0 } 35 | } 36 | 37 | /// Returns whether the encoder has finished sending all chunks. 38 | /// 39 | /// Returns true if the final zero-length chunk has been sent. 40 | pub fn is_finish(&self) -> bool { 41 | self.eof 42 | } 43 | } 44 | 45 | /// Implementation of the Encoder trait for chunked transfer encoding. 46 | /// 47 | /// This implementation handles encoding of PayloadItems into chunked format: 48 | /// - For PayloadItem::Chunk, writes the chunk size, data and terminating CRLF 49 | /// - For PayloadItem::Eof, writes the final zero-length chunk 50 | impl Encoder> for ChunkedEncoder { 51 | type Error = SendError; 52 | 53 | /// Encodes a PayloadItem into chunked transfer encoding format. 54 | /// 55 | /// # Arguments 56 | /// * `item` - The PayloadItem to encode (either Chunk or Eof) 57 | /// * `dst` - The output buffer to write the encoded data to 58 | /// 59 | /// # Returns 60 | /// * `Ok(())` if encoding succeeds 61 | /// * `Err(SendError)` if encoding fails 62 | fn encode(&mut self, item: PayloadItem, dst: &mut BytesMut) -> Result<(), Self::Error> { 63 | if self.eof { 64 | return Ok(()); 65 | } 66 | 67 | match item { 68 | PayloadItem::Chunk(bytes) => { 69 | // Write chunk size in hex followed by CRLF 70 | write!(helper::Writer(dst), "{:X}\r\n", bytes.remaining())?; 71 | dst.reserve(bytes.remaining() + 2); 72 | // Write chunk data 73 | dst.extend_from_slice(bytes.chunk()); 74 | // Write chunk terminating CRLF 75 | dst.extend_from_slice(b"\r\n"); 76 | Ok(()) 77 | } 78 | PayloadItem::Eof => { 79 | self.eof = true; 80 | // Write final zero-length chunk 81 | dst.extend_from_slice(b"0\r\n\r\n"); 82 | Ok(()) 83 | } 84 | } 85 | } 86 | } 87 | 88 | /// Helper module providing a Writer implementation for BytesMut. 89 | /// 90 | /// This allows using std::fmt::Write with BytesMut for writing 91 | /// chunk sizes in hexadecimal format. 92 | mod helper { 93 | use bytes::{BufMut, BytesMut}; 94 | use std::io; 95 | 96 | pub struct Writer<'a>(pub &'a mut BytesMut); 97 | 98 | impl io::Write for Writer<'_> { 99 | fn write(&mut self, buf: &[u8]) -> io::Result { 100 | self.0.put_slice(buf); 101 | Ok(buf.len()) 102 | } 103 | 104 | fn flush(&mut self) -> io::Result<()> { 105 | Ok(()) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /crates/http/src/codec/body/length_decoder.rs: -------------------------------------------------------------------------------- 1 | //! Decoder implementation for HTTP messages with Content-Length header. 2 | //! 3 | //! This module provides functionality to decode HTTP messages where the payload size 4 | //! is specified by the Content-Length header, as defined in 5 | //! [RFC 7230 Section 3.3.2](https://tools.ietf.org/html/rfc7230#section-3.3.2). 6 | 7 | use std::cmp; 8 | 9 | use crate::protocol::{ParseError, PayloadItem}; 10 | use bytes::BytesMut; 11 | use tokio_util::codec::Decoder; 12 | 13 | /// A decoder for handling HTTP messages with a known content length. 14 | /// 15 | /// The decoder tracks the remaining bytes to be read and ensures the total 16 | /// payload matches the specified content length. 17 | #[derive(Debug, Clone, PartialEq, Eq)] 18 | pub struct LengthDecoder { 19 | /// The number of bytes remaining to be read from the payload 20 | length: u64, 21 | } 22 | 23 | impl LengthDecoder { 24 | /// Creates a new LengthDecoder instance. 25 | /// 26 | /// # Arguments 27 | /// * `length` - The total content length to decode, specified by Content-Length header 28 | pub fn new(length: u64) -> Self { 29 | Self { length } 30 | } 31 | } 32 | 33 | /// Implementation of the Decoder trait for content-length based decoding. 34 | /// 35 | /// This implementation tracks the remaining bytes to read and ensures the total 36 | /// payload matches the specified content length. 37 | impl Decoder for LengthDecoder { 38 | type Item = PayloadItem; 39 | type Error = ParseError; 40 | 41 | /// Decodes bytes from the input buffer according to the content length. 42 | /// 43 | /// # Arguments 44 | /// * `src` - Source buffer containing the payload data 45 | /// 46 | /// # Returns 47 | /// * `Ok(Some(PayloadItem::Eof))` when all bytes have been read 48 | /// * `Ok(Some(PayloadItem::Chunk(bytes)))` when a chunk is successfully decoded 49 | /// * `Ok(None)` when more data is needed 50 | /// * `Err(ParseError)` if decoding fails 51 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 52 | if self.length == 0 { 53 | return Ok(Some(PayloadItem::Eof)); 54 | } 55 | 56 | if src.is_empty() { 57 | return Ok(None); 58 | } 59 | 60 | // Read the minimum of remaining length and available bytes 61 | let len = cmp::min(self.length, src.len() as u64); 62 | let bytes = src.split_to(len as usize).freeze(); 63 | 64 | self.length -= bytes.len() as u64; 65 | Ok(Some(PayloadItem::Chunk(bytes))) 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::*; 72 | 73 | #[test] 74 | fn test_basic() { 75 | let mut buffer: BytesMut = BytesMut::from(&b"101234567890abcdef\r\n\r\n"[..]); 76 | 77 | let mut length_decoder = LengthDecoder::new(10); 78 | let item = length_decoder.decode(&mut buffer); 79 | 80 | let payload = item.unwrap().unwrap(); 81 | assert!(payload.is_chunk()); 82 | 83 | let bytes = payload.as_bytes().unwrap(); 84 | 85 | assert_eq!(bytes.len(), 10); 86 | 87 | assert_eq!(&bytes[..], b"1012345678"); 88 | assert_eq!(&buffer[..], b"90abcdef\r\n\r\n"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/http/src/codec/body/length_encoder.rs: -------------------------------------------------------------------------------- 1 | //! Encoder implementation for HTTP messages with Content-Length header. 2 | //! 3 | //! This module provides functionality to encode HTTP messages where the payload size 4 | //! is specified by the Content-Length header, ensuring the total bytes sent matches 5 | //! the declared content length. 6 | 7 | use crate::protocol::{PayloadItem, SendError}; 8 | use bytes::{Buf, BytesMut}; 9 | use tokio_util::codec::Encoder; 10 | use tracing::warn; 11 | 12 | /// An encoder for handling HTTP messages with a known content length. 13 | /// 14 | /// The encoder tracks the remaining bytes to be sent and ensures the total 15 | /// payload matches the specified content length. 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | pub struct LengthEncoder { 18 | /// Indicates if the final EOF marker has been received 19 | received_eof: bool, 20 | /// The number of bytes remaining to be sent 21 | length: u64, 22 | } 23 | 24 | impl LengthEncoder { 25 | /// Creates a new LengthEncoder instance. 26 | /// 27 | /// # Arguments 28 | /// * `length` - The total content length to encode, specified by Content-Length header 29 | pub fn new(length: u64) -> Self { 30 | Self { received_eof: false, length } 31 | } 32 | 33 | /// Returns whether the encoder has finished sending all data. 34 | /// 35 | /// Returns true if all bytes have been sent and EOF has been received. 36 | pub fn is_finish(&self) -> bool { 37 | self.length == 0 && self.received_eof 38 | } 39 | } 40 | 41 | /// Implementation of the Encoder trait for content-length based encoding. 42 | /// 43 | /// This implementation tracks the remaining bytes to send and ensures the total 44 | /// payload matches the specified content length. 45 | impl Encoder> for LengthEncoder { 46 | type Error = SendError; 47 | 48 | /// Encodes a PayloadItem according to the content length. 49 | /// 50 | /// # Arguments 51 | /// * `item` - The PayloadItem to encode (either Chunk or Eof) 52 | /// * `dst` - The output buffer to write the encoded data to 53 | /// 54 | /// # Returns 55 | /// * `Ok(())` if encoding succeeds 56 | /// * `Err(SendError)` if encoding fails 57 | fn encode(&mut self, item: PayloadItem, dst: &mut BytesMut) -> Result<(), Self::Error> { 58 | if self.length == 0 && !item.is_eof() { 59 | warn!("encode payload_item but no need to encode anymore"); 60 | return Ok(()); 61 | } 62 | 63 | match item { 64 | PayloadItem::Chunk(bytes) => { 65 | if !bytes.has_remaining() { 66 | return Ok(()); 67 | } 68 | dst.extend_from_slice(bytes.chunk()); 69 | self.length -= bytes.remaining() as u64; 70 | Ok(()) 71 | } 72 | PayloadItem::Eof => { 73 | self.received_eof = true; 74 | Ok(()) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/http/src/codec/body/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP body handling module for processing request and response payloads 2 | //! 3 | //! This module provides functionality for encoding and decoding HTTP message bodies 4 | //! using different transfer strategies. It supports both chunked transfer encoding 5 | //! and content-length based transfers. 6 | //! 7 | //! # Components 8 | //! 9 | //! ## Decoders 10 | //! - [`chunked_decoder::ChunkedDecoder`]: Handles chunked transfer encoded payloads 11 | //! - [`length_decoder::LengthDecoder`]: Processes fixed-length payloads 12 | //! - [`payload_decoder::PayloadDecoder`]: Main decoder that coordinates different decoding strategies 13 | //! 14 | //! ## Encoders 15 | //! - [`chunked_encoder::ChunkedEncoder`]: Implements chunked transfer encoding 16 | //! - [`length_encoder::LengthEncoder`]: Handles fixed-length payload encoding 17 | //! - [`payload_encoder::PayloadEncoder`]: Main encoder that manages different encoding strategies 18 | //! 19 | //! # Features 20 | //! 21 | //! - Support for chunked transfer encoding (RFC 7230) 22 | //! - Content-Length based payload handling 23 | //! - Streaming processing of message bodies 24 | //! - Efficient memory usage through BytesMut 25 | //! - State machine based processing 26 | 27 | mod chunked_decoder; 28 | mod chunked_encoder; 29 | mod length_decoder; 30 | mod length_encoder; 31 | mod payload_decoder; 32 | mod payload_encoder; 33 | 34 | pub use payload_decoder::PayloadDecoder; 35 | pub use payload_encoder::PayloadEncoder; 36 | -------------------------------------------------------------------------------- /crates/http/src/codec/body/payload_decoder.rs: -------------------------------------------------------------------------------- 1 | //! Decoder implementation for HTTP message payloads. 2 | //! 3 | //! This module provides a unified decoder for handling different types of HTTP message bodies: 4 | //! - Content-Length based payloads 5 | //! - Chunked transfer encoding 6 | //! - Messages with no body 7 | //! 8 | //! The decoder automatically handles the appropriate decoding strategy based on the message headers. 9 | 10 | use crate::codec::body::chunked_decoder::ChunkedDecoder; 11 | use crate::codec::body::length_decoder::LengthDecoder; 12 | use crate::protocol::{ParseError, PayloadItem, PayloadSize}; 13 | use bytes::BytesMut; 14 | use tokio_util::codec::Decoder; 15 | 16 | /// A unified decoder for handling HTTP message payloads. 17 | /// 18 | /// This decoder supports three payload types: 19 | /// - Fixed length payloads (using Content-Length) 20 | /// - Chunked transfer encoding 21 | /// - No body 22 | #[derive(Debug, Clone, PartialEq, Eq)] 23 | pub struct PayloadDecoder { 24 | /// The specific decoding strategy to use 25 | kind: Kind, 26 | } 27 | 28 | /// Enum representing different payload decoding strategies. 29 | #[derive(Debug, Clone, PartialEq, Eq)] 30 | enum Kind { 31 | /// Decode payload with a fixed content length 32 | Length(LengthDecoder), 33 | 34 | /// Decode payload using chunked transfer encoding 35 | Chunked(ChunkedDecoder), 36 | 37 | /// Handle messages with no body 38 | NoBody, 39 | } 40 | 41 | impl PayloadDecoder { 42 | /// Creates a PayloadDecoder for messages with no body. 43 | #[allow(unused)] 44 | pub fn empty() -> Self { 45 | Self { kind: Kind::NoBody } 46 | } 47 | 48 | /// Creates a PayloadDecoder for chunked transfer encoding. 49 | pub fn chunked() -> Self { 50 | Self { kind: Kind::Chunked(ChunkedDecoder::new()) } 51 | } 52 | 53 | /// Creates a PayloadDecoder for a fixed-length payload. 54 | /// 55 | /// # Arguments 56 | /// * `size` - The expected content length in bytes 57 | #[allow(unused)] 58 | pub fn fix_length(size: u64) -> Self { 59 | Self { kind: Kind::Length(LengthDecoder::new(size)) } 60 | } 61 | 62 | /// Returns whether this decoder handles chunked transfer encoding. 63 | #[allow(unused)] 64 | pub fn is_chunked(&self) -> bool { 65 | match &self.kind { 66 | Kind::Length(_) => false, 67 | Kind::Chunked(_) => true, 68 | Kind::NoBody => false, 69 | } 70 | } 71 | 72 | /// Returns whether this decoder handles messages with no body. 73 | #[allow(unused)] 74 | pub fn is_empty(&self) -> bool { 75 | match &self.kind { 76 | Kind::Length(_) => false, 77 | Kind::Chunked(_) => false, 78 | Kind::NoBody => true, 79 | } 80 | } 81 | 82 | /// Returns whether this decoder handles fixed-length payloads. 83 | #[allow(unused)] 84 | pub fn is_fix_length(&self) -> bool { 85 | match &self.kind { 86 | Kind::Length(_) => true, 87 | Kind::Chunked(_) => false, 88 | Kind::NoBody => false, 89 | } 90 | } 91 | } 92 | 93 | impl From for PayloadDecoder { 94 | fn from(payload_size: PayloadSize) -> Self { 95 | match payload_size { 96 | PayloadSize::Length(u64) => PayloadDecoder::fix_length(u64), 97 | PayloadSize::Chunked => PayloadDecoder::chunked(), 98 | PayloadSize::Empty => PayloadDecoder::empty(), 99 | } 100 | } 101 | } 102 | 103 | /// Implementation of the Decoder trait for HTTP payloads. 104 | /// 105 | /// Delegates to the appropriate decoder based on the payload type. 106 | impl Decoder for PayloadDecoder { 107 | type Item = PayloadItem; 108 | type Error = ParseError; 109 | 110 | /// Decodes bytes from the input buffer using the appropriate strategy. 111 | /// 112 | /// # Arguments 113 | /// * `src` - Source buffer containing the payload data 114 | /// 115 | /// # Returns 116 | /// * Delegates to the specific decoder implementation, or 117 | /// * Returns EOF immediately for no-body messages 118 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 119 | match &mut self.kind { 120 | Kind::Length(length_decoder) => length_decoder.decode(src), 121 | Kind::Chunked(chunked_decoder) => chunked_decoder.decode(src), 122 | Kind::NoBody => Ok(Some(PayloadItem::Eof)), 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /crates/http/src/codec/body/payload_encoder.rs: -------------------------------------------------------------------------------- 1 | //! Encoder implementation for HTTP message payloads. 2 | //! 3 | //! This module provides a unified encoder for handling different types of HTTP message bodies: 4 | //! - Content-Length based payloads 5 | //! - Chunked transfer encoding 6 | //! - Messages with no body 7 | //! 8 | //! The encoder automatically handles the appropriate encoding strategy based on the message headers. 9 | 10 | use crate::codec::body::chunked_encoder::ChunkedEncoder; 11 | use crate::codec::body::length_encoder::LengthEncoder; 12 | use crate::protocol::{PayloadItem, SendError}; 13 | use bytes::{Buf, BytesMut}; 14 | use tokio_util::codec::Encoder; 15 | 16 | /// A unified encoder for handling HTTP message payloads. 17 | /// 18 | /// This encoder supports three payload types: 19 | /// - Fixed length payloads (using Content-Length) 20 | /// - Chunked transfer encoding 21 | /// - No body 22 | #[derive(Debug, Clone, PartialEq, Eq)] 23 | pub struct PayloadEncoder { 24 | /// The specific encoding strategy to use 25 | kind: Kind, 26 | } 27 | 28 | /// Enum representing different payload encoding strategies. 29 | #[derive(Debug, Clone, PartialEq, Eq)] 30 | enum Kind { 31 | /// Encode payload with a fixed content length 32 | Length(LengthEncoder), 33 | 34 | /// Encode payload using chunked transfer encoding 35 | Chunked(ChunkedEncoder), 36 | 37 | /// Handle messages with no body 38 | NoBody, 39 | } 40 | 41 | impl PayloadEncoder { 42 | /// Creates a PayloadEncoder for messages with no body. 43 | pub fn empty() -> Self { 44 | Self { kind: Kind::NoBody } 45 | } 46 | 47 | /// Creates a PayloadEncoder for chunked transfer encoding. 48 | pub fn chunked() -> Self { 49 | Self { kind: Kind::Chunked(ChunkedEncoder::new()) } 50 | } 51 | 52 | /// Creates a PayloadEncoder for a fixed-length payload. 53 | /// 54 | /// # Arguments 55 | /// * `size` - The expected content length in bytes 56 | #[allow(unused)] 57 | pub fn fix_length(size: u64) -> Self { 58 | Self { kind: Kind::Length(LengthEncoder::new(size)) } 59 | } 60 | 61 | /// Returns whether this encoder handles chunked transfer encoding. 62 | #[allow(unused)] 63 | pub fn is_chunked(&self) -> bool { 64 | match &self.kind { 65 | Kind::Length(_) => false, 66 | Kind::Chunked(_) => true, 67 | Kind::NoBody => false, 68 | } 69 | } 70 | 71 | /// Returns whether this encoder handles messages with no body. 72 | #[allow(unused)] 73 | pub fn is_empty(&self) -> bool { 74 | match &self.kind { 75 | Kind::Length(_) => false, 76 | Kind::Chunked(_) => false, 77 | Kind::NoBody => true, 78 | } 79 | } 80 | 81 | /// Returns whether this encoder handles fixed-length payloads. 82 | #[allow(unused)] 83 | pub fn is_fix_length(&self) -> bool { 84 | match &self.kind { 85 | Kind::Length(_) => true, 86 | Kind::Chunked(_) => false, 87 | Kind::NoBody => false, 88 | } 89 | } 90 | 91 | /// Returns whether the encoder has finished sending all data. 92 | pub fn is_finish(&self) -> bool { 93 | match &self.kind { 94 | Kind::Length(encoder) => encoder.is_finish(), 95 | Kind::Chunked(encoder) => encoder.is_finish(), 96 | Kind::NoBody => true, 97 | } 98 | } 99 | } 100 | 101 | /// Implementation of the Encoder trait for HTTP payloads. 102 | /// 103 | /// Delegates to the appropriate encoder based on the payload type. 104 | impl Encoder> for PayloadEncoder { 105 | type Error = SendError; 106 | 107 | /// Encodes a PayloadItem using the appropriate strategy. 108 | /// 109 | /// # Arguments 110 | /// * `item` - The PayloadItem to encode 111 | /// * `dst` - The output buffer to write the encoded data to 112 | /// 113 | /// # Returns 114 | /// * Delegates to the specific encoder implementation, or 115 | /// * Returns Ok(()) immediately for no-body messages 116 | fn encode(&mut self, item: PayloadItem, dst: &mut BytesMut) -> Result<(), Self::Error> { 117 | match &mut self.kind { 118 | Kind::Length(encoder) => encoder.encode(item, dst), 119 | Kind::Chunked(encoder) => encoder.encode(item, dst), 120 | Kind::NoBody => Ok(()), 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crates/http/src/codec/header/header_encoder.rs: -------------------------------------------------------------------------------- 1 | //! HTTP header encoder implementation for serializing HTTP response headers 2 | //! 3 | //! This module provides functionality for encoding HTTP response headers into raw bytes. 4 | //! It handles serialization of status line, headers and manages content length or 5 | //! transfer encoding headers according to HTTP/1.1 specification. 6 | //! 7 | //! # Features 8 | //! 9 | //! - Efficient header serialization 10 | //! - Automatic handling of Content-Length and Transfer-Encoding headers 11 | //! - Support for HTTP/1.1 responses 12 | //! - Chunked transfer encoding support 13 | 14 | use crate::protocol::{PayloadSize, ResponseHead, SendError}; 15 | 16 | use bytes::{BufMut, Bytes, BytesMut}; 17 | 18 | use http::{HeaderValue, Version, header}; 19 | use std::io; 20 | use std::io::{ErrorKind, Write}; 21 | use tokio_util::codec::Encoder; 22 | use tracing::error; 23 | 24 | /// Initial buffer size allocated for header serialization 25 | const INIT_HEADER_SIZE: usize = 4 * 1024; 26 | 27 | /// Encoder for HTTP response headers implementing the [`Encoder`] trait. 28 | /// 29 | /// This encoder serializes a [`ResponseHead`] and [`PayloadSize`] into raw bytes, 30 | /// automatically handling Content-Length or Transfer-Encoding headers based on the 31 | /// payload size. 32 | pub struct HeaderEncoder; 33 | 34 | impl Encoder<(ResponseHead, PayloadSize)> for HeaderEncoder { 35 | type Error = SendError; 36 | 37 | /// Encodes HTTP response headers into the provided bytes buffer. 38 | /// 39 | /// # Arguments 40 | /// 41 | /// * `item` - Tuple of response header and payload size information 42 | /// * `dst` - Mutable reference to the destination buffer 43 | /// 44 | /// # Returns 45 | /// 46 | /// Returns `Ok(())` if encoding succeeds, or `Err(SendError)` if encoding fails 47 | /// 48 | /// # Errors 49 | /// 50 | /// Returns error if: 51 | /// - HTTP version is not supported (only HTTP/1.1 supported) 52 | /// - Writing to buffer fails 53 | fn encode(&mut self, item: (ResponseHead, PayloadSize), dst: &mut BytesMut) -> Result<(), Self::Error> { 54 | let (mut header, payload_size) = item; 55 | 56 | dst.reserve(INIT_HEADER_SIZE); 57 | match header.version() { 58 | Version::HTTP_11 => { 59 | write!(FastWrite(dst), "HTTP/1.1 {} {}\r\n", header.status().as_str(), header.status().canonical_reason().unwrap())?; 60 | } 61 | v => { 62 | error!(http_version = ?v, "unsupported http version"); 63 | return Err(io::Error::from(ErrorKind::Unsupported).into()); 64 | } 65 | } 66 | 67 | // Set appropriate content length or transfer encoding header 68 | match payload_size { 69 | PayloadSize::Length(n) => match header.headers_mut().get_mut(header::CONTENT_LENGTH) { 70 | Some(value) => *value = n.into(), 71 | None => { 72 | header.headers_mut().insert(header::CONTENT_LENGTH, n.into()); 73 | } 74 | }, 75 | 76 | PayloadSize::Chunked => match header.headers_mut().get_mut(header::TRANSFER_ENCODING) { 77 | Some(value) => *value = unsafe { HeaderValue::from_maybe_shared_unchecked(Bytes::from_static("chunked".as_bytes())) }, 78 | None => { 79 | header.headers_mut().insert(header::TRANSFER_ENCODING, unsafe { 80 | HeaderValue::from_maybe_shared_unchecked(Bytes::from_static("chunked".as_bytes())) 81 | }); 82 | } 83 | }, 84 | PayloadSize::Empty => match header.headers_mut().get_mut(header::CONTENT_LENGTH) { 85 | Some(value) => *value = 0.into(), 86 | None => { 87 | const ZERO_VALUE: HeaderValue = HeaderValue::from_static("0"); 88 | header.headers_mut().insert(header::CONTENT_LENGTH, ZERO_VALUE); 89 | } 90 | }, 91 | } 92 | 93 | // Write all headers 94 | for (header_name, header_value) in header.headers().iter() { 95 | dst.put_slice(header_name.as_ref()); 96 | dst.put_slice(b": "); 97 | dst.put_slice(header_value.as_ref()); 98 | dst.put_slice(b"\r\n"); 99 | } 100 | dst.put_slice(b"\r\n"); 101 | Ok(()) 102 | } 103 | } 104 | 105 | /// Fast writer implementation for writing to BytesMut. 106 | /// 107 | /// This is an optimization to avoid unnecessary bounds checking when writing 108 | /// to the bytes buffer, since we've already reserved enough space. 109 | struct FastWrite<'a>(&'a mut BytesMut); 110 | 111 | impl Write for FastWrite<'_> { 112 | /// Writes a buffer into this writer, returning how many bytes were written. 113 | fn write(&mut self, buf: &[u8]) -> io::Result { 114 | self.0.put_slice(buf); 115 | Ok(buf.len()) 116 | } 117 | 118 | /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. 119 | fn flush(&mut self) -> io::Result<()> { 120 | Ok(()) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/http/src/codec/header/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP header processing module for encoding and decoding headers 2 | //! 3 | //! This module provides functionality for handling HTTP headers in both requests 4 | //! and responses. It includes efficient parsing and encoding mechanisms for HTTP 5 | //! header fields. 6 | //! 7 | //! # Components 8 | //! 9 | //! - [`HeaderDecoder`]: Decodes HTTP headers from raw bytes 10 | //! - Supports standard HTTP/1.1 header format 11 | //! - Handles header field validation 12 | //! - Manages header size limits 13 | //! 14 | //! - [`HeaderEncoder`]: Encodes HTTP headers to bytes 15 | //! - Implements standard HTTP/1.1 header formatting 16 | //! - Handles header field serialization 17 | //! - Manages content-length and transfer-encoding headers 18 | //! 19 | //! 20 | //! # Features 21 | //! 22 | //! - Efficient header parsing and encoding 23 | //! - Support for standard HTTP headers 24 | //! - Memory-efficient processing 25 | //! - Header validation 26 | //! - Size limit enforcement 27 | 28 | mod header_decoder; 29 | mod header_encoder; 30 | 31 | pub use header_decoder::HeaderDecoder; 32 | pub use header_encoder::HeaderEncoder; 33 | -------------------------------------------------------------------------------- /crates/http/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP codec module for encoding and decoding HTTP messages 2 | //! 3 | //! This module provides functionality for streaming HTTP message processing, 4 | //! including request decoding and response encoding. It uses a state machine 5 | //! pattern to handle both headers and payload data efficiently. 6 | //! 7 | //! # Architecture 8 | //! 9 | //! The codec module is organized into several components: 10 | //! 11 | //! - Request handling: 12 | //! - [`RequestDecoder`]: Decodes incoming HTTP requests 13 | //! - Header parsing via [`header`] module 14 | //! - Payload decoding via [`body`] module 15 | //! 16 | //! - Response handling: 17 | //! - [`ResponseEncoder`]: Encodes outgoing HTTP responses 18 | //! - Header encoding via [`header`] module 19 | //! - Payload encoding via [`body`] module 20 | //! 21 | //! # Example 22 | //! 23 | //! ```no_run 24 | //! use micro_http::codec::{RequestDecoder, ResponseEncoder}; 25 | //! use tokio_util::codec::{Decoder, Encoder}; 26 | //! use bytes::BytesMut; 27 | //! 28 | //! // Decode incoming request 29 | //! let mut decoder = RequestDecoder::new(); 30 | //! let mut request_buffer = BytesMut::new(); 31 | //! let request = decoder.decode(&mut request_buffer); 32 | //! 33 | //! // Encode outgoing response 34 | //! let mut encoder = ResponseEncoder::new(); 35 | //! let mut response_buffer = BytesMut::new(); 36 | //! // ... encode response ... 37 | //! ``` 38 | //! 39 | //! # Features 40 | //! 41 | //! - Streaming processing of HTTP messages 42 | //! - Support for chunked transfer encoding 43 | //! - Content-Length based payload handling 44 | //! - Efficient header parsing and encoding 45 | //! - State machine based processing 46 | 47 | mod body; 48 | mod header; 49 | mod request_decoder; 50 | mod response_encoder; 51 | 52 | pub use request_decoder::RequestDecoder; 53 | pub use response_encoder::ResponseEncoder; 54 | -------------------------------------------------------------------------------- /crates/http/src/codec/request_decoder.rs: -------------------------------------------------------------------------------- 1 | //! HTTP request decoder module 2 | //! 3 | //! This module provides functionality for decoding HTTP requests using a streaming approach. 4 | //! It handles both header parsing and payload decoding through a state machine pattern. 5 | //! 6 | //! # Components 7 | //! 8 | //! - [`RequestDecoder`]: Main decoder that coordinates header and payload parsing 9 | //! - Header parsing: Uses [`HeaderDecoder`] for parsing request headers 10 | //! - Payload handling: Uses [`PayloadDecoder`] for handling request bodies if any 11 | //! 12 | //! # Example 13 | //! 14 | //! ```no_run 15 | //! use micro_http::codec::RequestDecoder; 16 | //! use tokio_util::codec::Decoder; 17 | //! use bytes::BytesMut; 18 | //! 19 | //! let mut decoder = RequestDecoder::new(); 20 | //! let mut buffer = BytesMut::new(); 21 | //! // ... add request data to buffer ... 22 | //! let result = decoder.decode(&mut buffer); 23 | //! ``` 24 | 25 | use crate::codec::body::PayloadDecoder; 26 | use crate::codec::header::HeaderDecoder; 27 | use crate::protocol::{Message, ParseError, PayloadItem, PayloadSize, RequestHeader}; 28 | use bytes::BytesMut; 29 | use tokio_util::codec::Decoder; 30 | 31 | /// A decoder for HTTP requests that handles both headers and payload 32 | /// 33 | /// The decoder operates in two phases: 34 | /// 1. Header parsing: Decodes the request headers using [`HeaderDecoder`] 35 | /// 2. Payload parsing: If present, decodes the request body using [`PayloadDecoder`] 36 | /// 37 | /// # State Machine 38 | /// 39 | /// The decoder maintains its state through the `payload_decoder` field: 40 | /// - `None`: Currently parsing headers 41 | /// - `Some(PayloadDecoder)`: Currently parsing payload 42 | pub struct RequestDecoder { 43 | header_decoder: HeaderDecoder, 44 | payload_decoder: Option, 45 | } 46 | 47 | impl RequestDecoder { 48 | /// Creates a new `RequestDecoder` instance 49 | pub fn new() -> Self { 50 | Default::default() 51 | } 52 | } 53 | 54 | impl Default for RequestDecoder { 55 | fn default() -> Self { 56 | Self { header_decoder: HeaderDecoder, payload_decoder: None } 57 | } 58 | } 59 | 60 | impl Decoder for RequestDecoder { 61 | type Item = Message<(RequestHeader, PayloadSize)>; 62 | type Error = ParseError; 63 | 64 | /// Attempts to decode an HTTP request from the provided buffer 65 | /// 66 | /// # Returns 67 | /// 68 | /// - `Ok(Some(Message::Header(_)))`: Successfully decoded request headers 69 | /// - `Ok(Some(Message::Payload(_)))`: Successfully decoded a payload chunk 70 | /// - `Ok(None)`: Need more data to proceed 71 | /// - `Err(_)`: Encountered a parsing error 72 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 73 | // parse payload if have payload_decoder 74 | if let Some(payload_decoder) = &mut self.payload_decoder { 75 | let message = match payload_decoder.decode(src)? { 76 | Some(item @ PayloadItem::Chunk(_)) => Some(Message::Payload(item)), 77 | Some(item @ PayloadItem::Eof) => { 78 | // no need payload decoder in this request now 79 | self.payload_decoder.take(); 80 | Some(Message::Payload(item)) 81 | } 82 | None => None, 83 | }; 84 | 85 | return Ok(message); 86 | } 87 | 88 | // parse request 89 | let message = match self.header_decoder.decode(src)? { 90 | Some((header, payload_size)) => { 91 | self.payload_decoder = Some(payload_size.into()); 92 | Some(Message::Header((header, payload_size))) 93 | } 94 | None => None, 95 | }; 96 | 97 | Ok(message) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/http/src/codec/response_encoder.rs: -------------------------------------------------------------------------------- 1 | //! HTTP response encoder module 2 | //! 3 | //! This module provides functionality for encoding HTTP responses using a streaming approach. 4 | //! It handles both header encoding and payload encoding through a state machine pattern. 5 | //! 6 | //! # Components 7 | //! 8 | //! - [`ResponseEncoder`]: Main encoder that coordinates header and payload encoding 9 | //! - Header encoding: Uses [`HeaderEncoder`] for encoding response headers 10 | //! - Payload handling: Uses [`PayloadEncoder`] for encoding response bodies 11 | //! 12 | //! # Example 13 | //! 14 | //! ```no_run 15 | //! use micro_http::codec::ResponseEncoder; 16 | //! use tokio_util::codec::Encoder; 17 | //! use bytes::BytesMut; 18 | //! 19 | //! let mut encoder = ResponseEncoder::new(); 20 | //! let mut buffer = BytesMut::new(); 21 | //! // ... encode response data to buffer ... 22 | //! ``` 23 | 24 | use crate::codec::body::PayloadEncoder; 25 | use crate::codec::header::HeaderEncoder; 26 | use crate::protocol::{Message, PayloadSize, ResponseHead, SendError}; 27 | use bytes::{Buf, BytesMut}; 28 | use std::io; 29 | use std::io::ErrorKind; 30 | use tokio_util::codec::Encoder; 31 | use tracing::error; 32 | 33 | /// A encoder for HTTP responses that handles both headers and payload 34 | /// 35 | /// The encoder operates in two phases: 36 | /// 1. Header encoding: Encodes the response headers using [`HeaderEncoder`] 37 | /// 2. Payload encoding: If present, encodes the response body using [`PayloadEncoder`] 38 | pub struct ResponseEncoder { 39 | /// Encoder for HTTP response headers 40 | header_encoder: HeaderEncoder, 41 | /// Encoder for HTTP response payload (body) 42 | payload_encoder: Option, 43 | } 44 | 45 | impl ResponseEncoder { 46 | /// Creates a new `ResponseEncoder` instance 47 | pub fn new() -> Self { 48 | Default::default() 49 | } 50 | } 51 | 52 | impl Default for ResponseEncoder { 53 | fn default() -> Self { 54 | Self { header_encoder: HeaderEncoder, payload_encoder: None } 55 | } 56 | } 57 | 58 | impl Encoder> for ResponseEncoder { 59 | type Error = SendError; 60 | 61 | /// Attempts to encode an HTTP response to the provided buffer 62 | /// 63 | /// # Arguments 64 | /// 65 | /// * `item` - The message to encode, either headers or payload 66 | /// * `dst` - The buffer to write the encoded data to 67 | /// 68 | /// # Returns 69 | /// 70 | /// - `Ok(())`: Successfully encoded the message 71 | /// - `Err(_)`: Encountered an encoding error 72 | fn encode(&mut self, item: Message<(ResponseHead, PayloadSize), D>, dst: &mut BytesMut) -> Result<(), Self::Error> { 73 | match item { 74 | Message::Header((head, payload_size)) => { 75 | // If a payload encoder already exists, it's an error 76 | if self.payload_encoder.is_some() { 77 | error!("expect payload item but receive response head"); 78 | return Err(io::Error::from(ErrorKind::InvalidInput).into()); 79 | } 80 | 81 | // Create a payload encoder based on the payload size 82 | let payload_encoder = parse_payload_encoder(payload_size); 83 | self.payload_encoder = Some(payload_encoder); 84 | // Encode the response headers 85 | self.header_encoder.encode((head, payload_size), dst) 86 | } 87 | 88 | Message::Payload(payload_item) => { 89 | // Get the payload encoder, return error if it doesn't exist 90 | let payload_encoder = if let Some(encoder) = &mut self.payload_encoder { 91 | encoder 92 | } else { 93 | error!("expect response header but receive payload item"); 94 | return Err(io::Error::from(ErrorKind::InvalidInput).into()); 95 | }; 96 | 97 | // Encode the payload 98 | let result = payload_encoder.encode(payload_item, dst); 99 | 100 | // Check if the payload encoder is finished 101 | let is_eof = payload_encoder.is_finish(); 102 | // If finished, remove the payload encoder 103 | if is_eof { 104 | self.payload_encoder.take(); 105 | } 106 | 107 | result 108 | } 109 | } 110 | } 111 | } 112 | 113 | /// Creates a payload encoder based on the payload size 114 | /// 115 | /// # Arguments 116 | /// 117 | /// * `payload_size` - The size specification for the payload 118 | /// 119 | /// # Returns 120 | /// 121 | /// Returns a [`PayloadEncoder`] configured according to the payload size 122 | fn parse_payload_encoder(payload_size: PayloadSize) -> PayloadEncoder { 123 | match payload_size { 124 | PayloadSize::Length(size) => PayloadEncoder::fix_length(size), 125 | PayloadSize::Chunked => PayloadEncoder::chunked(), 126 | PayloadSize::Empty => PayloadEncoder::empty(), 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /crates/http/src/connection/http_connection.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::Display; 3 | 4 | use bytes::Bytes; 5 | use std::sync::Arc; 6 | 7 | use futures::{SinkExt, StreamExt}; 8 | use http::header::EXPECT; 9 | use http::{Response, StatusCode}; 10 | use http_body::Body; 11 | use http_body_util::{BodyExt, Empty}; 12 | use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; 13 | 14 | use crate::codec::{RequestDecoder, ResponseEncoder}; 15 | use crate::handler::Handler; 16 | use crate::protocol::body::ReqBody; 17 | use crate::protocol::{HttpError, Message, ParseError, PayloadItem, PayloadSize, RequestHeader, ResponseHead, SendError}; 18 | 19 | use tokio_util::codec::{FramedRead, FramedWrite}; 20 | use tracing::{error, info}; 21 | 22 | /// An HTTP connection that manages request processing and response streaming 23 | /// 24 | /// `HttpConnection` handles the full lifecycle of an HTTP connection, including: 25 | /// - Reading and decoding requests 26 | /// - Processing request headers and bodies 27 | /// - Handling expect-continue mechanism 28 | /// - Streaming responses back to clients 29 | /// 30 | /// # Type Parameters 31 | /// 32 | /// * `R`: The async readable stream type 33 | /// * `W`: The async writable stream type 34 | /// 35 | pub struct HttpConnection { 36 | framed_read: FramedRead, 37 | framed_write: FramedWrite, 38 | } 39 | 40 | impl HttpConnection 41 | where 42 | R: AsyncRead + Unpin, 43 | W: AsyncWrite + Unpin, 44 | { 45 | pub fn new(reader: R, writer: W) -> Self { 46 | Self { 47 | framed_read: FramedRead::with_capacity(reader, RequestDecoder::new(), 8 * 1024), 48 | framed_write: FramedWrite::new(writer, ResponseEncoder::new()), 49 | } 50 | } 51 | 52 | pub async fn process(mut self, mut handler: Arc) -> Result<(), HttpError> 53 | where 54 | H: Handler, 55 | H::RespBody: Body + Unpin, 56 | ::Error: Display, 57 | { 58 | loop { 59 | match self.framed_read.next().await { 60 | Some(Ok(Message::Header((header, payload_size)))) => { 61 | self.do_process(header, payload_size, &mut handler).await?; 62 | } 63 | 64 | Some(Ok(Message::Payload(PayloadItem::Eof))) => continue, 65 | 66 | Some(Ok(Message::Payload(_))) => { 67 | error!("error status because chunked has read in do_process"); 68 | let error_response = build_error_response(StatusCode::BAD_REQUEST); 69 | self.do_send_response(error_response).await?; 70 | return Err(ParseError::invalid_body("need header while receive body").into()); 71 | } 72 | 73 | Some(Err(e)) => { 74 | error!("can't receive next request, cause {}", e); 75 | let error_response = build_error_response(StatusCode::BAD_REQUEST); 76 | self.do_send_response(error_response).await?; 77 | return Err(e.into()); 78 | } 79 | 80 | None => { 81 | info!("cant read more request, break this connection down"); 82 | return Ok(()); 83 | } 84 | } 85 | } 86 | } 87 | 88 | async fn do_process(&mut self, header: RequestHeader, payload_size: PayloadSize, handler: &mut Arc) -> Result<(), HttpError> 89 | where 90 | H: Handler, 91 | H::RespBody: Body + Unpin, 92 | ::Error: Display, 93 | { 94 | // Check if the request header contains the "Expect: 100-continue" field. 95 | if let Some(value) = header.headers().get(EXPECT) { 96 | let slice = value.as_bytes(); 97 | // Verify if the value of the "Expect" field is "100-continue". 98 | if slice.len() >= 4 && &slice[0..4] == b"100-" { 99 | let writer = self.framed_write.get_mut(); 100 | // Send a "100 Continue" response to the client. 101 | let _ = writer.write(b"HTTP/1.1 100 Continue\r\n\r\n").await.map_err(SendError::io)?; 102 | writer.flush().await.map_err(SendError::io)?; 103 | // Log the event of sending a "100 Continue" response. 104 | info!("receive expect request header, sent continue response"); 105 | } 106 | } 107 | 108 | let (req_body, maybe_body_sender) = ReqBody::create_req_body(&mut self.framed_read, payload_size); 109 | let request = header.body(req_body); 110 | 111 | let response_result = match maybe_body_sender { 112 | None => handler.call(request).await, 113 | Some(mut body_sender) => { 114 | let (handler_result, body_send_result) = tokio::join!(handler.call(request), body_sender.start()); 115 | 116 | // check if body sender has error 117 | body_send_result?; 118 | handler_result 119 | } 120 | }; 121 | 122 | self.send_response(response_result).await 123 | } 124 | 125 | async fn send_response(&mut self, response_result: Result, E>) -> Result<(), HttpError> 126 | where 127 | T: Body + Unpin, 128 | T::Error: Display, 129 | E: Into>, 130 | { 131 | match response_result { 132 | Ok(response) => self.do_send_response(response).await, 133 | Err(e) => { 134 | error!("handle response error, cause: {}", e.into()); 135 | let error_response = build_error_response(StatusCode::INTERNAL_SERVER_ERROR); 136 | self.do_send_response(error_response).await 137 | } 138 | } 139 | } 140 | 141 | async fn do_send_response(&mut self, response: Response) -> Result<(), HttpError> 142 | where 143 | T: Body + Unpin, 144 | T::Error: Display, 145 | { 146 | let (header_parts, mut body) = response.into_parts(); 147 | 148 | let payload_size = { 149 | let size_hint = body.size_hint(); 150 | match size_hint.exact() { 151 | Some(0) => PayloadSize::Empty, 152 | Some(length) => PayloadSize::Length(length), 153 | None => PayloadSize::Chunked, 154 | } 155 | }; 156 | 157 | let header = Message::<_, T::Data>::Header((ResponseHead::from_parts(header_parts, ()), payload_size)); 158 | if !payload_size.is_empty() { 159 | self.framed_write.feed(header).await?; 160 | } else { 161 | // using send instead of feed, because we want to flush the underlying IO 162 | // when response only has header, we need to send header, 163 | // otherwise, we just feed header to the buffer 164 | self.framed_write.send(header).await?; 165 | } 166 | 167 | loop { 168 | match body.frame().await { 169 | Some(Ok(frame)) => { 170 | let payload_item = 171 | frame.into_data().map(PayloadItem::Chunk).map_err(|_e| SendError::invalid_body("resolve body response error"))?; 172 | 173 | self.framed_write 174 | .send(Message::Payload(payload_item)) 175 | .await 176 | .map_err(|_e| SendError::invalid_body("can't send response"))?; 177 | } 178 | Some(Err(e)) => return Err(SendError::invalid_body(format!("resolve response body error: {e}")).into()), 179 | None => { 180 | self.framed_write 181 | // using feed instead of send, because we don't want to flush the underlying IO 182 | .feed(Message::Payload(PayloadItem::::Eof)) 183 | .await 184 | .map_err(|e| SendError::invalid_body(format!("can't send eof response: {}", e)))?; 185 | return Ok(()); 186 | } 187 | } 188 | } 189 | } 190 | } 191 | 192 | fn build_error_response(status_code: StatusCode) -> Response> { 193 | Response::builder().status(status_code).body(Empty::::new()).unwrap() 194 | } 195 | -------------------------------------------------------------------------------- /crates/http/src/connection/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP connection handling module 2 | //! 3 | //! This module provides functionality for managing HTTP connections and processing 4 | //! HTTP requests and responses. It implements the core connection handling logic 5 | //! for the HTTP server. 6 | //! 7 | //! # Components 8 | //! 9 | //! - [`HttpConnection`]: Main connection handler that: 10 | //! - Manages the lifecycle of HTTP connections 11 | //! - Processes incoming requests 12 | //! - Handles response streaming 13 | //! - Supports keep-alive connections 14 | //! - Implements expect-continue handling 15 | //! 16 | //! # Features 17 | //! 18 | //! - Asynchronous I/O handling 19 | //! - Streaming request and response processing 20 | //! - Keep-alive connection support 21 | //! - Error handling and recovery 22 | //! - Expect-continue mechanism 23 | //! - Efficient memory usage through buffering 24 | 25 | mod http_connection; 26 | 27 | pub use http_connection::HttpConnection; 28 | -------------------------------------------------------------------------------- /crates/http/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP request handler module 2 | //! 3 | //! This module provides traits and utilities for handling HTTP requests. It defines 4 | //! the core abstraction for request processing through the [`Handler`] trait and 5 | //! provides utilities for creating handlers from async functions. 6 | //! 7 | //! # Examples 8 | //! 9 | //! ```no_run 10 | //! use micro_http::handler::{Handler, make_handler}; 11 | //! use micro_http::protocol::body::ReqBody; 12 | //! use http::{Request, Response}; 13 | //! use std::error::Error; 14 | //! 15 | //! // Define an async handler function 16 | //! async fn hello_handler(req: Request) -> Result, Box> { 17 | //! Ok(Response::new("Hello World!".to_string())) 18 | //! } 19 | //! 20 | //! // Create a handler from the function 21 | //! let handler = make_handler(hello_handler); 22 | //! ``` 23 | 24 | use crate::protocol::body::ReqBody; 25 | use http::{Request, Response}; 26 | use http_body::Body; 27 | use std::error::Error; 28 | use std::future::Future; 29 | 30 | /// A trait for handling HTTP requests 31 | /// 32 | /// This trait defines the core interface for processing HTTP requests and generating responses. 33 | /// Implementors of this trait can be used to handle requests in the HTTP server. 34 | /// 35 | /// # Type Parameters 36 | /// 37 | /// * `RespBody`: The response body type that implements [`Body`] 38 | /// * `Error`: The error type that can be converted into a boxed error 39 | /// * `Fut`: The future type returned by the handler 40 | #[trait_variant::make(Send)] 41 | pub trait Handler: Sync { 42 | /// The type of the response body 43 | type RespBody: Body; 44 | 45 | /// The error type returned by the handler 46 | type Error: Into>; 47 | 48 | async fn call(&self, req: Request) -> Result, Self::Error>; 49 | } 50 | 51 | /// A wrapper type for function-based handlers 52 | /// 53 | /// This type implements [`Handler`] for functions that take a [`Request`] and 54 | /// return a [`Future`] resolving to a [`Response`]. 55 | #[derive(Debug)] 56 | pub struct HandlerFn { 57 | f: F, 58 | } 59 | 60 | impl Handler for HandlerFn 61 | where 62 | RespBody: Body, 63 | F: Fn(Request) -> Fut + Send + Sync, 64 | Err: Into>, 65 | Fut: Future, Err>> + Send, 66 | { 67 | type RespBody = RespBody; 68 | type Error = Err; 69 | 70 | async fn call(&self, req: Request) -> Result, Self::Error> { 71 | (self.f)(req).await 72 | } 73 | } 74 | 75 | /// Creates a new handler from an async function 76 | /// 77 | /// This function wraps an async function in a [`HandlerFn`] type that implements 78 | /// the [`Handler`] trait. 79 | /// 80 | /// # Arguments 81 | /// 82 | /// * `f` - An async function that takes a [`Request`] and returns a [`Future`] resolving to a [`Response`] 83 | /// 84 | /// # Examples 85 | /// 86 | /// ```no_run 87 | /// use micro_http::handler::make_handler; 88 | /// use http::{Request, Response}; 89 | /// use micro_http::protocol::body::ReqBody; 90 | /// use std::error::Error; 91 | /// 92 | /// async fn my_handler(req: Request) -> Result, Box> { 93 | /// Ok(Response::new("Hello".to_string())) 94 | /// } 95 | /// 96 | /// let handler = make_handler(my_handler); 97 | /// ``` 98 | pub fn make_handler(f: F) -> HandlerFn 99 | where 100 | RespBody: Body, 101 | Err: Into>, 102 | Ret: Future, Err>>, 103 | F: Fn(Request) -> Ret, 104 | { 105 | HandlerFn { f } 106 | } 107 | -------------------------------------------------------------------------------- /crates/http/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An asynchronous micro HTTP server implementation 2 | //! 3 | //! This crate provides a lightweight, efficient, and modular HTTP/1.1 server implementation 4 | //! built on top of tokio. It focuses on providing a clean API while maintaining high performance 5 | //! through careful memory management and asynchronous processing. 6 | //! 7 | //! # Features 8 | //! 9 | //! - Full HTTP/1.1 protocol support 10 | //! - Asynchronous I/O using tokio 11 | //! - Streaming request and response bodies 12 | //! - Chunked transfer encoding 13 | //! - Keep-alive connections 14 | //! - Expect-continue mechanism 15 | //! - Efficient memory usage through zero-copy parsing 16 | //! - Clean error handling 17 | //! 18 | //! 19 | //! # Example 20 | //! 21 | //! ```no_run 22 | //! use http::{Request, Response, StatusCode}; 23 | //! use http_body_util::BodyExt; 24 | //! use std::error::Error; 25 | //! use std::sync::Arc; 26 | //! use tokio::net::TcpListener; 27 | //! use tracing::{error, info, warn, Level}; 28 | //! use tracing_subscriber::FmtSubscriber; 29 | //! use micro_http::connection::HttpConnection; 30 | //! use micro_http::handler::make_handler; 31 | //! use micro_http::protocol::body::ReqBody; 32 | //! 33 | //! #[tokio::main] 34 | //! async fn main() { 35 | //! // Initialize logging 36 | //! let subscriber = FmtSubscriber::builder() 37 | //! .with_max_level(Level::INFO) 38 | //! .finish(); 39 | //! tracing::subscriber::set_global_default(subscriber) 40 | //! .expect("setting default subscriber failed"); 41 | //! 42 | //! info!(port = 8080, "start listening"); 43 | //! let tcp_listener = match TcpListener::bind("127.0.0.1:8080").await { 44 | //! Ok(tcp_listener) => tcp_listener, 45 | //! Err(e) => { 46 | //! error!(cause = %e, "bind server error"); 47 | //! return; 48 | //! } 49 | //! }; 50 | //! 51 | //! let handler = Arc::new(make_handler(hello_world)); 52 | //! 53 | //! loop { 54 | //! let (tcp_stream, _remote_addr) = match tcp_listener.accept().await { 55 | //! Ok(stream_and_addr) => stream_and_addr, 56 | //! Err(e) => { 57 | //! warn!(cause = %e, "failed to accept"); 58 | //! continue; 59 | //! } 60 | //! }; 61 | //! 62 | //! let handler = handler.clone(); 63 | //! 64 | //! tokio::spawn(async move { 65 | //! let (reader, writer) = tcp_stream.into_split(); 66 | //! let connection = HttpConnection::new(reader, writer); 67 | //! match connection.process(handler).await { 68 | //! Ok(_) => { 69 | //! info!("finished process, connection shutdown"); 70 | //! } 71 | //! Err(e) => { 72 | //! error!("service has error, cause {}, connection shutdown", e); 73 | //! } 74 | //! } 75 | //! }); 76 | //! } 77 | //! } 78 | //! 79 | //! async fn hello_world(request: Request) -> Result, Box> { 80 | //! let path = request.uri().path().to_string(); 81 | //! info!("request path {}", path); 82 | //! 83 | //! let (_header, body) = request.into_parts(); 84 | //! 85 | //! let body_bytes = body.collect().await?.to_bytes(); 86 | //! info!(body = std::str::from_utf8(&body_bytes[..]).unwrap(), "receiving request body"); 87 | //! 88 | //! let response_body = "Hello World!\r\n"; 89 | //! let response = Response::builder() 90 | //! .status(StatusCode::OK) 91 | //! .header(http::header::CONTENT_LENGTH, response_body.len()) 92 | //! .body(response_body.to_string()) 93 | //! .unwrap(); 94 | //! 95 | //! Ok(response) 96 | //! } 97 | //! ``` 98 | //! 99 | //! 100 | //! # Architecture 101 | //! 102 | //! The crate is organized into several key modules: 103 | //! 104 | //! - [`connection`]: Core connection handling and lifecycle management 105 | //! - [`protocol`]: Protocol types and abstractions 106 | //! - [`codec`]: Protocol encoding/decoding implementation 107 | //! - [`handler`]: Request handler traits and utilities 108 | //! 109 | //! 110 | //! 111 | //! # Core Components 112 | //! 113 | //! ## Connection Handling 114 | //! 115 | //! The [`connection::HttpConnection`] type is the main entry point for processing HTTP connections. 116 | //! It manages the full lifecycle of connections including: 117 | //! 118 | //! - Request parsing 119 | //! - Body streaming 120 | //! - Response generation 121 | //! - Keep-alive handling 122 | //! 123 | //! ## Request Processing 124 | //! 125 | //! Requests are processed through handler functions that implement the [`handler::Handler`] trait. 126 | //! The crate provides utilities for creating handlers from async functions through 127 | //! [`handler::make_handler`]. 128 | //! 129 | //! ## Body Streaming 130 | //! 131 | //! Request and response bodies are handled through streaming interfaces that implement 132 | //! the `http_body::Body` trait. This enables efficient processing of large payloads 133 | //! without buffering entire bodies in memory. 134 | //! 135 | //! ## Error Handling 136 | //! 137 | //! The crate uses custom error types that implement `std::error::Error`: 138 | //! 139 | //! - [`protocol::HttpError`]: Top-level error type 140 | //! - [`protocol::ParseError`]: Request parsing errors 141 | //! - [`protocol::SendError`]: Response sending errors 142 | //! 143 | //! # Performance Considerations 144 | //! 145 | //! The implementation focuses on performance through: 146 | //! 147 | //! - Zero-copy parsing where possible 148 | //! - Efficient buffer management 149 | //! - Streaming processing of bodies 150 | //! - Concurrent request/response handling 151 | //! - Connection keep-alive 152 | //! 153 | //! # Limitations 154 | //! 155 | //! - HTTP/1.1 only (currently HTTP/2 or HTTP/3 is not supported) 156 | //! - No TLS support (use a reverse proxy for HTTPS) 157 | //! - Maximum header size: 8KB 158 | //! - Maximum number of headers: 64 159 | //! 160 | //! # Safety 161 | //! 162 | //! The crate uses unsafe code in a few well-documented places for performance 163 | //! optimization, particularly in header parsing. All unsafe usage is carefully 164 | //! reviewed and tested. 165 | 166 | pub mod codec; 167 | pub mod connection; 168 | pub mod handler; 169 | pub mod protocol; 170 | 171 | mod utils; 172 | pub(crate) use utils::ensure; 173 | -------------------------------------------------------------------------------- /crates/http/src/protocol/body/body_channel.rs: -------------------------------------------------------------------------------- 1 | use crate::protocol::{Message, ParseError, PayloadItem, PayloadSize, RequestHeader}; 2 | use bytes::Bytes; 3 | use futures::{SinkExt, Stream, StreamExt, channel::mpsc}; 4 | use http_body::{Body, Frame, SizeHint}; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | use tracing::error; 9 | 10 | pub(crate) fn create_body_sender_receiver(body_stream: &mut S, payload_size: PayloadSize) -> (BodySender, BodyReceiver) 11 | where 12 | S: Stream, ParseError>> + Unpin, 13 | { 14 | let (signal_sender, signal_receiver) = mpsc::channel(8); 15 | let (data_sender, data_receiver) = mpsc::channel(8); 16 | 17 | (BodySender::new(body_stream, signal_receiver, data_sender), BodyReceiver::new(signal_sender, data_receiver, payload_size)) 18 | } 19 | 20 | pub(crate) enum BodyRequestSignal { 21 | RequestData, 22 | #[allow(dead_code)] 23 | Enough, 24 | } 25 | 26 | pub(crate) struct BodySender<'conn, S> { 27 | payload_stream: &'conn mut S, 28 | signal_receiver: mpsc::Receiver, 29 | data_sender: mpsc::Sender>, 30 | eof: bool, 31 | } 32 | 33 | impl<'conn, S> BodySender<'conn, S> 34 | where 35 | S: Stream, ParseError>> + Unpin, 36 | { 37 | pub fn new( 38 | payload_stream: &'conn mut S, 39 | signal_receiver: mpsc::Receiver, 40 | data_sender: mpsc::Sender>, 41 | ) -> Self { 42 | Self { payload_stream, signal_receiver, data_sender, eof: false } 43 | } 44 | 45 | pub(crate) async fn start(&mut self) -> Result<(), ParseError> { 46 | if self.eof { 47 | return Ok(()); 48 | } 49 | 50 | while let Some(signal) = self.signal_receiver.next().await { 51 | match signal { 52 | BodyRequestSignal::RequestData => match self.read_data().await { 53 | Ok(payload_item) => { 54 | self.eof = payload_item.is_eof(); 55 | if let Err(e) = self.data_sender.send(Ok(payload_item)).await { 56 | error!("failed to send payload body through channel, {}", e); 57 | return Err(ParseError::invalid_body("send body data error")); 58 | } 59 | 60 | if self.eof { 61 | return Ok(()); 62 | } 63 | } 64 | 65 | Err(e) => { 66 | error!("failed to read data from body stream, {}", e); 67 | if let Err(send_error) = self.data_sender.send(Err(e)).await { 68 | error!("failed to send error through channel, {}", send_error); 69 | return Err(ParseError::invalid_body("failed to send error through channel")); 70 | } 71 | break; 72 | } 73 | }, 74 | 75 | BodyRequestSignal::Enough => { 76 | break; 77 | } 78 | } 79 | } 80 | 81 | self.skip_data().await 82 | } 83 | 84 | pub(crate) async fn read_data(&mut self) -> Result { 85 | match self.payload_stream.next().await { 86 | Some(Ok(Message::Payload(payload_item))) => Ok(payload_item), 87 | Some(Ok(Message::Header(_))) => { 88 | error!("should not receive header in BodySender"); 89 | Err(ParseError::invalid_body("should not receive header in BodySender")) 90 | } 91 | Some(Err(e)) => Err(e), 92 | None => { 93 | error!("should not receive None in BodySender"); 94 | Err(ParseError::invalid_body("should not receive None in BodySender")) 95 | } 96 | } 97 | } 98 | 99 | pub(crate) async fn skip_data(&mut self) -> Result<(), ParseError> { 100 | if self.eof { 101 | return Ok(()); 102 | } 103 | 104 | loop { 105 | match self.read_data().await { 106 | Ok(payload_item) if payload_item.is_eof() => { 107 | self.eof = true; 108 | return Ok(()); 109 | } 110 | Ok(_payload_item) => { 111 | // drop payload_item 112 | } 113 | Err(e) => return Err(e), 114 | } 115 | } 116 | } 117 | } 118 | 119 | pub(crate) struct BodyReceiver { 120 | signal_sender: mpsc::Sender, 121 | data_receiver: mpsc::Receiver>, 122 | payload_size: PayloadSize, 123 | } 124 | 125 | impl BodyReceiver { 126 | pub(crate) fn new( 127 | signal_sender: mpsc::Sender, 128 | data_receiver: mpsc::Receiver>, 129 | payload_size: PayloadSize, 130 | ) -> Self { 131 | Self { signal_sender, data_receiver, payload_size } 132 | } 133 | } 134 | 135 | impl BodyReceiver { 136 | pub async fn receive_data(&mut self) -> Result { 137 | if let Err(e) = self.signal_sender.send(BodyRequestSignal::RequestData).await { 138 | error!("failed to send request_more through channel, {}", e); 139 | return Err(ParseError::invalid_body("failed to send signal when receive body data")); 140 | } 141 | 142 | self.data_receiver 143 | .next() 144 | .await 145 | .unwrap_or_else(|| Err(ParseError::invalid_body("body stream should not receive None when receive data"))) 146 | } 147 | } 148 | 149 | impl Body for BodyReceiver { 150 | type Data = Bytes; 151 | type Error = ParseError; 152 | 153 | fn poll_frame(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, Self::Error>>> { 154 | let this = self.get_mut(); 155 | 156 | tokio::pin! { 157 | let future = this.receive_data(); 158 | } 159 | 160 | match future.poll(cx) { 161 | Poll::Ready(Ok(PayloadItem::Chunk(bytes))) => Poll::Ready(Some(Ok(Frame::data(bytes)))), 162 | Poll::Ready(Ok(PayloadItem::Eof)) => Poll::Ready(None), 163 | Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))), 164 | Poll::Pending => Poll::Pending, 165 | } 166 | } 167 | 168 | fn size_hint(&self) -> SizeHint { 169 | self.payload_size.into() 170 | } 171 | } 172 | 173 | impl From for PayloadSize { 174 | fn from(size_hint: SizeHint) -> Self { 175 | match size_hint.exact() { 176 | Some(0) => PayloadSize::new_empty(), 177 | Some(length) => PayloadSize::new_length(length), 178 | None => PayloadSize::new_chunked(), 179 | } 180 | } 181 | } 182 | 183 | impl From for SizeHint { 184 | fn from(payload_size: PayloadSize) -> Self { 185 | match payload_size { 186 | PayloadSize::Length(length) => SizeHint::with_exact(length), 187 | PayloadSize::Chunked => SizeHint::new(), 188 | PayloadSize::Empty => SizeHint::with_exact(0), 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /crates/http/src/protocol/body/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP request body handling implementation. 2 | //! 3 | //! This module provides the core abstractions and implementations for handling HTTP request bodies 4 | //! in an efficient streaming manner. The design focuses on: 5 | //! 6 | //! - Memory efficiency through streaming 7 | //! - Protocol correctness 8 | //! - Clean abstraction boundaries 9 | //! - Concurrent processing capabilities 10 | //! 11 | //! # Architecture 12 | //! 13 | //! The body handling system consists of two main components: 14 | //! 15 | //! - [`ReqBody`]: The consumer side that implements `http_body::Body` trait 16 | //! - [`ReqBodySender`]: The producer side that reads from the raw payload stream 17 | //! 18 | //! These components communicate through channels to enable concurrent processing while 19 | //! maintaining backpressure. 20 | //! 21 | //! # Design Goals 22 | //! 23 | //! 1. **Memory Efficiency** 24 | //! - Stream body chunks instead of buffering entire payload 25 | //! - Implement backpressure to prevent overwhelming memory 26 | //! 27 | //! 2. **Protocol Correctness** 28 | //! - Ensure complete body consumption even if handler abandons reading 29 | //! - Maintain proper connection state for keep-alive support 30 | //! 31 | //! 3. **Concurrent Processing** 32 | //! - Allow request handling to proceed while body streams 33 | //! - Support cancellation and cleanup in error cases 34 | //! 35 | //! 4. **Clean Abstractions** 36 | //! - Hide channel complexity from consumers 37 | //! - Provide standard http_body::Body interface 38 | //! 39 | //! # Implementation Details 40 | //! 41 | //! The body handling implementation uses: 42 | //! 43 | //! - MPSC channel for signaling between consumer and producer 44 | //! - Oneshot channels for individual chunk transfers 45 | //! - EOF tracking to ensure complete body consumption 46 | //! - Automatic cleanup of unread data 47 | //! 48 | //! See individual component documentation for more details. 49 | 50 | //mod req_body_2; 51 | mod body_channel; 52 | mod req_body; 53 | 54 | //pub use req_body_2::ReqBody2; 55 | pub use req_body::ReqBody; 56 | //pub use req_body_2::ReqBodySender; 57 | -------------------------------------------------------------------------------- /crates/http/src/protocol/body/req_body.rs: -------------------------------------------------------------------------------- 1 | use crate::protocol::body::body_channel::{BodyReceiver, BodySender, create_body_sender_receiver}; 2 | use crate::protocol::{Message, ParseError, PayloadSize, RequestHeader}; 3 | use bytes::Bytes; 4 | use futures::Stream; 5 | use http_body::{Body, Frame, SizeHint}; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | 9 | pub struct ReqBody { 10 | inner: ReqBodyRepr, 11 | } 12 | pub(crate) enum ReqBodyRepr { 13 | Receiver(BodyReceiver), 14 | NoBody, 15 | } 16 | 17 | impl ReqBody { 18 | pub(crate) fn create_req_body(body_stream: &mut S, payload_size: PayloadSize) -> (ReqBody, Option>) 19 | where 20 | S: Stream, ParseError>> + Unpin, 21 | { 22 | match payload_size { 23 | PayloadSize::Empty | PayloadSize::Length(0) => (ReqBody::no_body(), None), 24 | _ => { 25 | let (sender, receiver) = create_body_sender_receiver(body_stream, payload_size); 26 | (ReqBody::receiver(receiver), Some(sender)) 27 | } 28 | } 29 | } 30 | 31 | pub(crate) fn no_body() -> Self { 32 | Self { inner: ReqBodyRepr::NoBody } 33 | } 34 | 35 | pub(crate) fn receiver(receiver: BodyReceiver) -> Self { 36 | Self { inner: ReqBodyRepr::Receiver(receiver) } 37 | } 38 | } 39 | 40 | impl Body for ReqBody { 41 | type Data = Bytes; 42 | type Error = ParseError; 43 | 44 | fn poll_frame(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, Self::Error>>> { 45 | let this = self.get_mut(); 46 | match &mut this.inner { 47 | ReqBodyRepr::Receiver(body_receiver) => Pin::new(body_receiver).poll_frame(cx), 48 | ReqBodyRepr::NoBody => Poll::Ready(None), 49 | } 50 | } 51 | 52 | fn is_end_stream(&self) -> bool { 53 | match &self.inner { 54 | ReqBodyRepr::NoBody => true, 55 | ReqBodyRepr::Receiver(body_receiver) => body_receiver.is_end_stream(), 56 | } 57 | } 58 | 59 | fn size_hint(&self) -> SizeHint { 60 | match &self.inner { 61 | ReqBodyRepr::NoBody => SizeHint::with_exact(0), 62 | ReqBodyRepr::Receiver(body_receiver) => body_receiver.size_hint(), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/http/src/protocol/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for HTTP protocol handling 2 | //! 3 | //! This module provides error types for handling various error conditions that may occur 4 | //! during HTTP request processing and response generation. 5 | //! 6 | //! # Error Types 7 | //! 8 | //! - [`HttpError`]: The top-level error type that wraps all other error types 9 | //! - [`ParseError`]: Errors that occur during request parsing and processing 10 | //! - [`SendError`]: Errors that occur during response generation and sending 11 | //! 12 | //! The error types form a hierarchy where `HttpError` is the top-level error that can 13 | //! contain either a `ParseError` or `SendError`. This allows for granular error handling 14 | //! while still providing a unified error type at the API boundary. 15 | use std::io; 16 | use thiserror::Error; 17 | 18 | /// The top-level error type for HTTP operations 19 | /// 20 | /// This enum represents all possible errors that can occur during HTTP request 21 | /// processing and response generation. 22 | #[derive(Debug, Error)] 23 | pub enum HttpError { 24 | /// Errors that occur during request parsing and processing 25 | #[error("request error: {source}")] 26 | RequestError { 27 | #[from] 28 | source: ParseError, 29 | }, 30 | 31 | /// Errors that occur during response generation and sending 32 | #[error("response error: {source}")] 33 | ResponseError { 34 | #[from] 35 | source: SendError, 36 | }, 37 | } 38 | 39 | /// Errors that occur during HTTP request parsing 40 | /// 41 | /// This enum represents various error conditions that can occur while parsing 42 | /// and processing HTTP requests. 43 | #[derive(Error, Debug)] 44 | pub enum ParseError { 45 | /// Header size exceeds the maximum allowed size 46 | #[error("header size too large, current: {current_size} exceed the limit {max_size}")] 47 | TooLargeHeader { current_size: usize, max_size: usize }, 48 | 49 | /// Number of headers exceeds the maximum allowed 50 | #[error("header number exceed the limit {max_num}")] 51 | TooManyHeaders { max_num: usize }, 52 | 53 | /// Invalid header format or content 54 | #[error("invalid header: {reason}")] 55 | InvalidHeader { reason: String }, 56 | 57 | /// Unsupported HTTP version 58 | #[error("invalid http version: {0:?}")] 59 | InvalidVersion(Option), 60 | 61 | /// Invalid or unsupported HTTP method 62 | #[error("invalid http method")] 63 | InvalidMethod, 64 | 65 | /// Invalid URI format 66 | #[error("invalid http uri")] 67 | InvalidUri, 68 | 69 | /// Invalid Content-Length header 70 | #[error("invalid content-length header: {reason}")] 71 | InvalidContentLength { reason: String }, 72 | 73 | /// Invalid request body 74 | #[error("invalid body: {reason}")] 75 | InvalidBody { reason: String }, 76 | 77 | /// I/O error during parsing 78 | #[error("io error: {source}")] 79 | Io { 80 | #[from] 81 | source: io::Error, 82 | }, 83 | } 84 | 85 | impl ParseError { 86 | /// Creates a new TooLargeHeader error 87 | pub fn too_large_header(current_size: usize, max_size: usize) -> Self { 88 | Self::TooLargeHeader { current_size, max_size } 89 | } 90 | 91 | /// Creates a new TooManyHeaders error 92 | pub fn too_many_headers(max_num: usize) -> Self { 93 | Self::TooManyHeaders { max_num } 94 | } 95 | 96 | /// Creates a new InvalidHeader error 97 | pub fn invalid_header(str: S) -> Self { 98 | Self::InvalidHeader { reason: str.to_string() } 99 | } 100 | 101 | /// Creates a new InvalidBody error 102 | pub fn invalid_body(str: S) -> Self { 103 | Self::InvalidBody { reason: str.to_string() } 104 | } 105 | 106 | /// Creates a new InvalidContentLength error 107 | pub fn invalid_content_length(str: S) -> Self { 108 | Self::InvalidContentLength { reason: str.to_string() } 109 | } 110 | 111 | /// Creates a new I/O error 112 | pub fn io>(e: E) -> Self { 113 | Self::Io { source: e.into() } 114 | } 115 | } 116 | 117 | /// Errors that occur during HTTP response generation and sending 118 | /// 119 | /// This enum represents error conditions that can occur while generating 120 | /// and sending HTTP responses. 121 | #[derive(Error, Debug)] 122 | pub enum SendError { 123 | /// Invalid response body 124 | #[error("invalid body: {reason}")] 125 | InvalidBody { reason: String }, 126 | 127 | /// I/O error during sending 128 | #[error("io error: {source}")] 129 | Io { 130 | #[from] 131 | source: io::Error, 132 | }, 133 | } 134 | 135 | impl SendError { 136 | /// Creates a new InvalidBody error 137 | pub fn invalid_body(str: S) -> Self { 138 | Self::InvalidBody { reason: str.to_string() } 139 | } 140 | 141 | /// Creates a new I/O error 142 | pub fn io>(e: E) -> Self { 143 | Self::Io { source: e.into() } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /crates/http/src/protocol/message.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | /// Represents a HTTP message that can either be a header or payload. 4 | /// 5 | /// This enum is used to handle both request and response messages in the HTTP protocol. 6 | /// The generic parameter `T` typically represents the header type (request or response header), 7 | /// while `Data` represents the type of the payload data (defaults to `Bytes`). 8 | pub enum Message { 9 | /// Contains the header information of type `T` 10 | Header(T), 11 | /// Contains a chunk of payload data or EOF marker 12 | Payload(PayloadItem), 13 | } 14 | 15 | /// Represents an item in the HTTP message payload stream. 16 | /// 17 | /// This enum is used by the payload decoder to produce either data chunks 18 | /// or signal the end of the payload stream (EOF). 19 | #[derive(Debug, Clone, PartialEq, Eq)] 20 | pub enum PayloadItem { 21 | /// A chunk of payload data 22 | Chunk(Data), 23 | /// Marks the end of the payload stream 24 | Eof, 25 | } 26 | 27 | /// Represents the size information of an HTTP payload. 28 | /// 29 | /// This enum is used to determine how the payload should be processed: 30 | /// - Known length: Process exact number of bytes 31 | /// - Chunked: Process using chunked transfer encoding 32 | /// - Empty: No payload to process 33 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 34 | pub enum PayloadSize { 35 | /// Payload with known length in bytes 36 | Length(u64), 37 | /// Payload using chunked transfer encoding 38 | Chunked, 39 | /// Empty payload (no body) 40 | Empty, 41 | } 42 | 43 | impl PayloadSize { 44 | #[inline(always)] 45 | pub fn new_chunked() -> Self { 46 | PayloadSize::Chunked 47 | } 48 | 49 | #[inline(always)] 50 | pub fn new_empty() -> Self { 51 | PayloadSize::Empty 52 | } 53 | 54 | #[inline(always)] 55 | pub fn new_length(length: u64) -> Self { 56 | PayloadSize::Length(length) 57 | } 58 | 59 | /// Returns true if the payload uses chunked transfer encoding 60 | #[inline] 61 | pub fn is_chunked(&self) -> bool { 62 | matches!(self, PayloadSize::Chunked) 63 | } 64 | 65 | /// Returns true if the payload is empty 66 | #[inline] 67 | pub fn is_empty(&self) -> bool { 68 | matches!(self, PayloadSize::Empty) 69 | } 70 | } 71 | 72 | impl Message { 73 | /// Returns true if this message contains payload data 74 | #[inline] 75 | pub fn is_payload(&self) -> bool { 76 | matches!(self, Message::Payload(_)) 77 | } 78 | 79 | /// Returns true if this message contains header information 80 | #[inline] 81 | pub fn is_header(&self) -> bool { 82 | matches!(self, Message::Header(_)) 83 | } 84 | 85 | /// Converts the message into a PayloadItem if it contains payload data 86 | /// 87 | /// Returns None if the message contains header information 88 | pub fn into_payload_item(self) -> Option { 89 | match self { 90 | Message::Header(_) => None, 91 | Message::Payload(payload_item) => Some(payload_item), 92 | } 93 | } 94 | } 95 | 96 | /// Converts bytes into a Message 97 | /// 98 | /// This allows bytes to be directly converted into a Message for sending payload data. 99 | /// The generic type T is unused since this only creates payload messages. 100 | impl From for Message { 101 | fn from(bytes: Bytes) -> Self { 102 | Self::Payload(PayloadItem::Chunk(bytes)) 103 | } 104 | } 105 | 106 | impl PayloadItem { 107 | /// Returns true if this item represents the end of the payload stream 108 | #[inline] 109 | pub fn is_eof(&self) -> bool { 110 | matches!(self, PayloadItem::Eof) 111 | } 112 | 113 | /// Returns true if this item contains chunk data 114 | #[inline] 115 | pub fn is_chunk(&self) -> bool { 116 | matches!(self, PayloadItem::Chunk(_)) 117 | } 118 | } 119 | 120 | impl PayloadItem { 121 | /// Returns a reference to the contained bytes if this is a Chunk 122 | /// 123 | /// Returns None if this is an EOF marker 124 | pub fn as_bytes(&self) -> Option<&Bytes> { 125 | match self { 126 | PayloadItem::Chunk(bytes) => Some(bytes), 127 | PayloadItem::Eof => None, 128 | } 129 | } 130 | 131 | /// Returns a mutable reference to the contained bytes if this is a Chunk 132 | /// 133 | /// Returns None if this is an EOF marker 134 | pub fn as_mut_bytes(&mut self) -> Option<&mut Bytes> { 135 | match self { 136 | PayloadItem::Chunk(bytes) => Some(bytes), 137 | PayloadItem::Eof => None, 138 | } 139 | } 140 | 141 | /// Consumes the PayloadItem and returns the contained bytes if this is a Chunk 142 | /// 143 | /// Returns None if this is an EOF marker 144 | pub fn into_bytes(self) -> Option { 145 | match self { 146 | PayloadItem::Chunk(bytes) => Some(bytes), 147 | PayloadItem::Eof => None, 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/http/src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | //! Core HTTP protocol abstractions and implementations. 2 | //! 3 | //! This module provides the fundamental building blocks for HTTP protocol handling, 4 | //! including request/response processing, body streaming, and error handling. 5 | //! The design focuses on providing clean abstractions while maintaining high performance 6 | //! and memory efficiency. 7 | //! 8 | //! # Architecture 9 | //! 10 | //! The protocol module is organized into several key components: 11 | //! 12 | //! - **Message Handling** ([`message`]): Core message types and payload processing 13 | //! - [`Message`]: Represents either headers or payload chunks 14 | //! - [`PayloadItem`]: Handles individual payload chunks and EOF 15 | //! - [`PayloadSize`]: Tracks payload size information 16 | //! 17 | //! - **Request Processing** ([`request`]): Request header handling 18 | //! - [`RequestHeader`]: Wraps HTTP request headers with additional functionality 19 | //! 20 | //! - **Response Processing** ([`response`]): Response header handling 21 | //! - [`ResponseHead`]: Type alias for response headers before body attachment 22 | //! 23 | //! - **Body Streaming** ([`body`]): Efficient body handling implementation 24 | //! - [`ReqBody`]: Consumer side implementing `http_body::Body` 25 | //! - [`ReqBodySender`]: Producer side for streaming body chunks 26 | //! 27 | //! - **Error Handling** ([`error`]): Comprehensive error types 28 | //! - [`HttpError`]: Top-level error type 29 | //! - [`ParseError`]: Request parsing errors 30 | //! - [`SendError`]: Response sending errors 31 | //! 32 | //! # Design Goals 33 | //! 34 | //! 1. **Memory Efficiency** 35 | //! - Stream request/response bodies instead of buffering 36 | //! - Implement proper backpressure mechanisms 37 | //! 38 | //! 2. **Clean Abstractions** 39 | //! - Provide intuitive interfaces for protocol handling 40 | //! - Hide implementation complexity from consumers 41 | //! 42 | //! 3. **Protocol Correctness** 43 | //! - Ensure proper HTTP protocol compliance 44 | //! - Handle edge cases and error conditions gracefully 45 | //! 46 | //! 4. **Performance** 47 | //! - Minimize allocations and copies 48 | //! - Support concurrent processing where beneficial 49 | //! 50 | //! # Example Usage 51 | //! 52 | //! The protocol module is typically used through the connection layer rather than 53 | //! directly. However, understanding its components is crucial for implementing 54 | //! custom handlers or extending functionality. 55 | //! 56 | //! See individual module documentation for specific usage examples. 57 | 58 | mod message; 59 | pub use message::Message; 60 | pub use message::PayloadItem; 61 | pub use message::PayloadSize; 62 | 63 | mod request; 64 | pub use request::RequestHeader; 65 | 66 | mod response; 67 | pub use response::ResponseHead; 68 | 69 | mod error; 70 | pub use error::HttpError; 71 | pub use error::ParseError; 72 | pub use error::SendError; 73 | 74 | pub mod body; 75 | -------------------------------------------------------------------------------- /crates/http/src/protocol/response.rs: -------------------------------------------------------------------------------- 1 | //! HTTP response header handling implementation. 2 | //! 3 | //! This module provides type definitions for HTTP response headers. 4 | //! It uses the standard `http::Response` type with an empty body placeholder 5 | //! to represent response headers before the actual response body is attached. 6 | 7 | use http::Response; 8 | 9 | /// Type alias for HTTP response headers. 10 | /// 11 | /// This type represents the header portion of an HTTP response, using 12 | /// `http::Response<()>` with an empty body placeholder. The actual response 13 | /// body can be attached later using the response builder pattern. 14 | pub type ResponseHead = Response<()>; 15 | -------------------------------------------------------------------------------- /crates/http/src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utility macros and functions for the HTTP crate. 2 | //! 3 | //! This module provides helper macros and functions that are used internally 4 | //! by the HTTP crate implementation. 5 | 6 | /// A macro for early returns with an error if a condition is not met. 7 | /// 8 | /// This is similar to the `assert!` macro, but returns an error instead of panicking. 9 | /// It's useful for validation checks where you want to return early with an error 10 | /// if some condition is not satisfied. 11 | /// 12 | /// # Arguments 13 | /// 14 | /// * `$predicate` - A boolean expression that should evaluate to true 15 | /// * `$error` - The error value to return if the predicate is false 16 | /// 17 | /// ``` 18 | macro_rules! ensure { 19 | ($predicate:expr, $error:expr) => { 20 | if !$predicate { 21 | return Err($error); 22 | } 23 | }; 24 | } 25 | 26 | pub(crate) use ensure; 27 | -------------------------------------------------------------------------------- /crates/web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "micro-web" 3 | version = "0.1.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | rust-version.workspace = true 10 | readme = "README.md" 11 | keywords = ["http", "async", "web"] 12 | categories = ["web-programming::http-server", "network-programming"] 13 | description = "the async micro web framework" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | micro-http.workspace = true 19 | http.workspace = true 20 | http-body.workspace = true 21 | http-body-util.workspace = true 22 | mime.workspace = true 23 | faf-http-date.workspace = true 24 | serde.workspace = true 25 | serde_urlencoded.workspace = true 26 | serde_json.workspace = true 27 | serde_qs.workspace = true 28 | 29 | # compress lib, maybe we need to set as feature optional dependency: 30 | flate2.workspace = true 31 | zstd.workspace = true 32 | brotli.workspace = true 33 | 34 | tracing.workspace = true 35 | tracing-subscriber.workspace = true 36 | 37 | bytes.workspace = true 38 | 39 | hlist2.workspace = true 40 | tower-layer.workspace = true 41 | 42 | dhat.workspace = true 43 | 44 | pin-project-lite.workspace = true 45 | 46 | tokio = { workspace = true} 47 | futures.workspace = true 48 | async-trait.workspace = true 49 | trait-variant.workspace = true 50 | dynosaur.workspace = true 51 | arc-swap.workspace = true 52 | once_cell.workspace = true 53 | 54 | higher-kinded-types.workspace = true 55 | 56 | matchit.workspace = true 57 | 58 | thiserror.workspace = true 59 | 60 | [dev-dependencies] 61 | mockall.workspace = true 62 | 63 | [features] 64 | dhat-heap = [] # if you are doing heap profiling 65 | -------------------------------------------------------------------------------- /crates/web/examples/getting_started.rs: -------------------------------------------------------------------------------- 1 | //! Getting Started Example 2 | //! 3 | //! This example demonstrates the basic usage of the micro_web framework, including: 4 | //! - Route handling with different HTTP methods 5 | //! - Form and JSON data extraction 6 | //! - Request filtering 7 | //! - Response encoding 8 | //! - Default handler setup 9 | //! 10 | //! To run this example: 11 | //! ```bash 12 | //! cargo run --example getting_started 13 | //! ``` 14 | 15 | use http::Method; 16 | use micro_web::encoding::encoder::EncodeDecorator; 17 | use micro_web::extract::{Form, Json}; 18 | use micro_web::router::filter::header; 19 | use micro_web::router::{Router, get, post}; 20 | use micro_web::{Server, handler_fn}; 21 | use serde::Deserialize; 22 | 23 | /// User struct for demonstrating data extraction 24 | #[allow(dead_code)] 25 | #[derive(Deserialize, Debug)] 26 | pub struct User { 27 | name: String, 28 | zip: String, 29 | } 30 | 31 | /// Simple GET handler that demonstrates method and optional string extraction 32 | /// 33 | /// Example request: 34 | /// ```bash 35 | /// curl http://127.0.0.1:8080/ 36 | /// ``` 37 | async fn simple_get(method: &Method, str: Option, str2: Option) -> String { 38 | println!("receive body: {}, {}", str.is_some(), str2.is_some()); 39 | format!("receive from method: {}\r\n", method) 40 | } 41 | 42 | /// Handler that extracts form data into a User struct 43 | /// 44 | /// Example request: 45 | /// ```bash 46 | /// curl -v -H "Transfer-Encoding: chunked" \ 47 | /// -d "name=hello&zip=world&c=abc" \ 48 | /// http://127.0.0.1:8080/ 49 | /// ``` 50 | async fn simple_handler_form_data(method: &Method, Form(user): Form) -> String { 51 | format!("receive from method: {}, receive use: {:?}\r\n", method, user) 52 | } 53 | 54 | /// Handler that extracts JSON data into a User struct 55 | /// 56 | /// Example request: 57 | /// ```bash 58 | /// curl -v -H "Transfer-Encoding: chunked" \ 59 | /// -H 'Content-Type: application/json' \ 60 | /// -d '{"name":"hello","zip":"world"}' \ 61 | /// http://127.0.0.1:8080/ 62 | /// ``` 63 | async fn simple_handler_json_data(method: &Method, Json(user): Json) -> String { 64 | format!("receive from method: {}, receive use: {:?}\r\n", method, user) 65 | } 66 | 67 | /// Simple POST handler demonstrating method and optional string extraction 68 | /// 69 | /// Example request: 70 | /// ```bash 71 | /// curl -X POST http://127.0.0.1:8080/ 72 | /// ``` 73 | async fn simple_handler_post(method: &Method, str: Option, str2: Option) -> String { 74 | println!("receive body: {:?}, {:?}", str, str2); 75 | format!("receive from method: {}\r\n", method) 76 | } 77 | 78 | /// Another GET handler for a different route 79 | /// 80 | /// Example request: 81 | /// ```bash 82 | /// curl http://127.0.0.1:8080/4 83 | /// ``` 84 | async fn simple_another_get(method: &Method, str: Option, str2: Option) -> String { 85 | println!("receive body: {:?}, {:?}", str, str2); 86 | format!("receive from method: {}\r\n", method) 87 | } 88 | 89 | /// Default handler for unmatched routes 90 | /// 91 | /// Example request: 92 | /// ```bash 93 | /// curl http://127.0.0.1:8080/non-existent-path 94 | /// ``` 95 | async fn default_handler() -> &'static str { 96 | "404 not found" 97 | } 98 | 99 | #[tokio::main] 100 | async fn main() { 101 | // Build router with multiple routes and handlers 102 | let router = Router::builder() 103 | // Basic GET route 104 | .route("/", get(handler_fn(simple_get))) 105 | // POST route for form data with content-type filter 106 | .route( 107 | "/", 108 | post(handler_fn(simple_handler_form_data)) 109 | .with(header(http::header::CONTENT_TYPE, mime::APPLICATION_WWW_FORM_URLENCODED.as_ref())), 110 | ) 111 | // POST route for JSON data with content-type filter 112 | .route("/", post(handler_fn(simple_handler_json_data)).with(header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()))) 113 | // Default POST route 114 | .route("/", post(handler_fn(simple_handler_post))) 115 | // Additional GET route 116 | .route("/4", get(handler_fn(simple_another_get))) 117 | // Add response encoding wrapper 118 | .with_global_decorator(EncodeDecorator) 119 | .build(); 120 | 121 | // Configure and start the server 122 | Server::builder().router(router).bind("127.0.0.1:8080").default_handler(handler_fn(default_handler)).build().unwrap().start().await; 123 | } 124 | -------------------------------------------------------------------------------- /crates/web/examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | //! Basic example demonstrating how to create a simple web server using micro_web. 2 | //! This example shows: 3 | //! - How to define route handlers 4 | //! - How to set up a router with middleware 5 | //! - How to configure and start a server 6 | 7 | use micro_web::date::DateServiceDecorator; 8 | use micro_web::router::{Router, get}; 9 | use micro_web::{Server, handler_fn}; 10 | 11 | /// A simple handler that returns "hello world" 12 | async fn hello_world() -> &'static str { 13 | "hello world" 14 | } 15 | 16 | /// Default handler for unmatched handlers (404 responses) 17 | /// 18 | /// This handler is called when no other handlers match the incoming request 19 | async fn default_handler() -> &'static str { 20 | "404 not found" 21 | } 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | // Create a new router using the builder 26 | let router = Router::builder() 27 | // Add a route that matches GET requests to the root path "/" 28 | // handler_fn converts our async function into a handler 29 | .route("/", get(handler_fn(hello_world))) 30 | // Add middleware that will add date headers to responses 31 | .with_global_decorator(DateServiceDecorator) 32 | .build(); 33 | 34 | // Configure and start the server 35 | Server::builder() 36 | // Attach our router to handle incoming requests 37 | .router(router) 38 | // Set the address and port to listen on 39 | .bind("127.0.0.1:3000") 40 | // Set a handler for requests that don't match any routes 41 | .default_handler(handler_fn(default_handler)) 42 | // Build the server 43 | .build() 44 | .unwrap() 45 | // Start the server and wait for it to finish 46 | .start() 47 | .await; 48 | } 49 | -------------------------------------------------------------------------------- /crates/web/examples/return_uid.rs: -------------------------------------------------------------------------------- 1 | use micro_web::date::DateServiceDecorator; 2 | use micro_web::responder::Responder; 3 | use micro_web::router::{Router, get, post}; 4 | use micro_web::{PathParams, Server, handler_fn, responder}; 5 | 6 | async fn empty_body() -> &'static str { 7 | "" 8 | } 9 | 10 | async fn echo_uid<'s, 'r>(path_params: &PathParams<'s, 'r>) -> String { 11 | path_params.get("id").map(|s| s.to_owned()).unwrap() 12 | } 13 | 14 | async fn default_handler() -> impl Responder { 15 | responder::NotFound 16 | } 17 | 18 | 19 | #[cfg(feature = "dhat-heap")] 20 | #[global_allocator] 21 | static ALLOC: dhat::Alloc = dhat::Alloc; 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | #[cfg(feature = "dhat-heap")] 26 | let _profiler = dhat::Profiler::new_heap(); 27 | 28 | // Build router with multiple routes and handlers 29 | let router = Router::builder() 30 | .route("/", get(handler_fn(empty_body))) 31 | .route("/user", post(handler_fn(empty_body))) 32 | .route("/user/{id}", get(handler_fn(echo_uid))) 33 | .with_global_decorator(DateServiceDecorator) 34 | .build(); 35 | 36 | // Configure and start the server 37 | Server::builder().router(router).bind("127.0.0.1:3000").default_handler(handler_fn(default_handler)).build().unwrap().start().await; 38 | } 39 | -------------------------------------------------------------------------------- /crates/web/examples/sse_server.rs: -------------------------------------------------------------------------------- 1 | use futures::Stream; 2 | use micro_web::encoding::encoder::EncodeDecorator; 3 | use micro_web::responder::sse::{Event, SseStream, build_sse_stream_emitter}; 4 | use micro_web::router::{Router, get}; 5 | use micro_web::{Server, handler_fn}; 6 | 7 | async fn sse_process() -> SseStream> { 8 | let (stream, mut emitter) = build_sse_stream_emitter(2); 9 | 10 | tokio::spawn(async move { 11 | for i in 0..5 { 12 | tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 13 | let _ = emitter.send(Event::from_data(format!("{}", i))).await; 14 | } 15 | 16 | let _ = emitter.close().await; 17 | }); 18 | 19 | stream 20 | } 21 | 22 | async fn default_handler() -> &'static str { 23 | "404 not found" 24 | } 25 | 26 | #[tokio::main] 27 | async fn main() { 28 | // Build router with multiple routes and handlers 29 | let router = Router::builder() 30 | // Basic GET route 31 | .route("/sse", get(handler_fn(sse_process))) 32 | // Add response encoding wrapper 33 | .with_global_decorator(EncodeDecorator) 34 | .build(); 35 | 36 | // Configure and start the server 37 | Server::builder().router(router).bind("127.0.0.1:8080").default_handler(handler_fn(default_handler)).build().unwrap().start().await; 38 | } 39 | -------------------------------------------------------------------------------- /crates/web/src/body.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use http_body::Body as HttpBody; 3 | use http_body::{Frame, SizeHint}; 4 | use http_body_util::combinators::UnsyncBoxBody; 5 | use micro_http::protocol::body::ReqBody; 6 | use micro_http::protocol::{HttpError, ParseError}; 7 | use std::future::Future; 8 | use std::pin::Pin; 9 | use std::sync::Arc; 10 | use std::task::{Context, Poll}; 11 | use tokio::sync::Mutex; 12 | 13 | #[derive(Clone)] 14 | pub struct OptionReqBody { 15 | inner: Arc>>, 16 | } 17 | 18 | impl From for OptionReqBody { 19 | fn from(body: ReqBody) -> Self { 20 | OptionReqBody { inner: Arc::new(Mutex::new(Some(body))) } 21 | } 22 | } 23 | 24 | impl OptionReqBody { 25 | pub async fn can_consume(&self) -> bool { 26 | let guard = self.inner.lock().await; 27 | guard.is_some() 28 | } 29 | 30 | pub async fn apply(&self, f: F) -> Fut::Output 31 | where 32 | F: FnOnce(ReqBody) -> Fut, 33 | Fut: Future>, 34 | { 35 | let mut guard = self.inner.lock().await; 36 | if guard.is_none() { 37 | return Err(ParseError::invalid_body("body has been consumed")); 38 | } 39 | 40 | let req_body = (*guard).take().unwrap(); 41 | 42 | f(req_body).await 43 | } 44 | } 45 | 46 | pub struct ResponseBody { 47 | inner: Kind, 48 | } 49 | 50 | enum Kind { 51 | Once(Option), 52 | Stream(UnsyncBoxBody), 53 | } 54 | 55 | impl ResponseBody { 56 | pub fn empty() -> Self { 57 | Self { inner: Kind::Once(None) } 58 | } 59 | 60 | pub fn once(bytes: Bytes) -> Self { 61 | Self { inner: Kind::Once(Some(bytes)) } 62 | } 63 | 64 | pub fn stream(body: B) -> Self 65 | where 66 | B: HttpBody + Send + 'static, 67 | { 68 | Self { inner: Kind::Stream(UnsyncBoxBody::new(body)) } 69 | } 70 | 71 | pub fn is_empty(&self) -> bool { 72 | match &self.inner { 73 | Kind::Once(None) => false, 74 | Kind::Once(Some(bytes)) => bytes.is_empty(), 75 | Kind::Stream(body) => body.is_end_stream(), 76 | } 77 | } 78 | 79 | pub fn take(&mut self) -> Self { 80 | self.replace(ResponseBody::empty()) 81 | } 82 | 83 | pub fn replace(&mut self, body: Self) -> Self { 84 | std::mem::replace(self, body) 85 | } 86 | } 87 | 88 | impl From for ResponseBody { 89 | fn from(value: String) -> Self { 90 | ResponseBody { inner: Kind::Once(Some(Bytes::from(value))) } 91 | } 92 | } 93 | 94 | impl From<()> for ResponseBody { 95 | fn from(_: ()) -> Self { 96 | Self::empty() 97 | } 98 | } 99 | 100 | impl From> for ResponseBody { 101 | fn from(option: Option) -> Self { 102 | match option { 103 | Some(bytes) => Self::once(bytes), 104 | None => Self::empty(), 105 | } 106 | } 107 | } 108 | 109 | impl From<&'static str> for ResponseBody { 110 | fn from(value: &'static str) -> Self { 111 | if value.is_empty() { Self::empty() } else { Self::once(value.as_bytes().into()) } 112 | } 113 | } 114 | 115 | impl HttpBody for ResponseBody { 116 | type Data = Bytes; 117 | type Error = HttpError; 118 | 119 | fn poll_frame(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll, Self::Error>>> { 120 | let kind = &mut self.get_mut().inner; 121 | match kind { 122 | Kind::Once(option_bytes) if option_bytes.is_none() => Poll::Ready(None), 123 | Kind::Once(option_bytes) => Poll::Ready(Some(Ok(Frame::data(option_bytes.take().unwrap())))), 124 | Kind::Stream(box_body) => { 125 | let pin = Pin::new(box_body); 126 | pin.poll_frame(cx) 127 | } 128 | } 129 | } 130 | 131 | fn is_end_stream(&self) -> bool { 132 | let kind = &self.inner; 133 | match kind { 134 | Kind::Once(option_bytes) => option_bytes.is_none(), 135 | Kind::Stream(box_body) => box_body.is_end_stream(), 136 | } 137 | } 138 | 139 | fn size_hint(&self) -> SizeHint { 140 | let kind = &self.inner; 141 | match kind { 142 | Kind::Once(None) => SizeHint::with_exact(0), 143 | Kind::Once(Some(bytes)) => SizeHint::with_exact(bytes.len() as u64), 144 | Kind::Stream(box_body) => box_body.size_hint(), 145 | } 146 | } 147 | } 148 | 149 | #[cfg(test)] 150 | mod tests { 151 | use crate::body::ResponseBody; 152 | use bytes::Bytes; 153 | use futures::TryStreamExt; 154 | use http_body::{Body as HttpBody, Frame}; 155 | use http_body_util::{BodyExt, StreamBody}; 156 | use micro_http::protocol::ParseError; 157 | use std::io; 158 | 159 | fn check_send() {} 160 | 161 | #[test] 162 | fn is_send() { 163 | check_send::(); 164 | } 165 | 166 | #[tokio::test(flavor = "multi_thread", worker_threads = 1)] 167 | async fn test_string_body() { 168 | let s = "Hello world".to_string(); 169 | let len = s.len() as u64; 170 | 171 | let mut body = ResponseBody::from(s); 172 | 173 | assert_eq!(body.size_hint().exact(), Some(len)); 174 | assert!(!body.is_end_stream()); 175 | 176 | let bytes = body.frame().await.unwrap().unwrap().into_data().unwrap(); 177 | assert_eq!(bytes, Bytes::from("Hello world")); 178 | 179 | assert!(body.is_end_stream()); 180 | assert!(body.frame().await.is_none()); 181 | } 182 | 183 | #[tokio::test(flavor = "multi_thread", worker_threads = 1)] 184 | async fn test_empty_body() { 185 | let mut body = ResponseBody::from(""); 186 | 187 | assert!(body.is_end_stream()); 188 | assert_eq!(body.size_hint().exact(), Some(0)); 189 | 190 | assert!(body.frame().await.is_none()); 191 | } 192 | 193 | #[tokio::test(flavor = "multi_thread", worker_threads = 1)] 194 | async fn test_stream_body() { 195 | let chunks: Vec> = 196 | vec![Ok(Frame::data(Bytes::from(vec![1]))), Ok(Frame::data(Bytes::from(vec![2]))), Ok(Frame::data(Bytes::from(vec![3])))]; 197 | let stream = futures::stream::iter(chunks).map_err(|err| ParseError::io(err).into()); 198 | let stream_body = StreamBody::new(stream); 199 | 200 | let mut body = ResponseBody::stream(stream_body); 201 | 202 | assert!(body.size_hint().exact().is_none()); 203 | assert!(!body.is_end_stream()); 204 | assert_eq!(body.frame().await.unwrap().unwrap().into_data().unwrap().as_ref(), [1]); 205 | assert_eq!(body.frame().await.unwrap().unwrap().into_data().unwrap().as_ref(), [2]); 206 | assert_eq!(body.frame().await.unwrap().unwrap().into_data().unwrap().as_ref(), [3]); 207 | 208 | assert!(!body.is_end_stream()); 209 | 210 | assert!(body.frame().await.is_none()); 211 | 212 | assert!(!body.is_end_stream()); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /crates/web/src/date/date_service_decorator.rs: -------------------------------------------------------------------------------- 1 | //! Module for handling HTTP response date headers. 2 | //! 3 | //! This module provides functionality for automatically adding RFC 7231 compliant 4 | //! date headers to HTTP responses. It implements a wrapper pattern that can be 5 | //! composed with other wrappers in the request handling pipeline. 6 | //! 7 | //! The main components are: 8 | //! - `DateWrapper`: A wrapper that adds date handling capability 9 | //! - `DateResponseHandler`: The actual handler that adds the Date header to responses 10 | //! 11 | //! The Date header is added according to RFC 7231 Section 7.1.1.2 12 | 13 | use crate::date::DateService; 14 | use crate::handler::RequestHandler; 15 | use crate::handler::handler_decorator::HandlerDecorator; 16 | use crate::handler::handler_decorator_factory::HandlerDecoratorFactory; 17 | use crate::{OptionReqBody, RequestContext, ResponseBody}; 18 | use async_trait::async_trait; 19 | use http::Response; 20 | 21 | /// A wrapper that adds automatic date header handling to responses. 22 | /// 23 | /// This wrapper creates a `DateResponseHandler` that will add an RFC 7231 compliant 24 | /// Date header to all HTTP responses. 25 | #[derive(Clone)] 26 | pub struct DateServiceDecorator; 27 | 28 | /// A request handler that adds the Date header to responses. 29 | /// 30 | /// This handler wraps another handler and adds the Date header to its responses. 31 | /// The Date header is generated using a shared `DateService` instance to avoid 32 | /// unnecessary system calls. 33 | pub struct DateResponseHandler { 34 | handler: H, 35 | // todo: we need to ensure data_service is singleton 36 | date_service: &'static DateService, 37 | } 38 | 39 | impl HandlerDecorator for DateServiceDecorator { 40 | type Output = DateResponseHandler; 41 | 42 | fn decorate(&self, raw: H) -> Self::Output { 43 | DateResponseHandler { handler: raw, date_service: DateService::get_global_instance() } 44 | } 45 | } 46 | 47 | impl HandlerDecoratorFactory for DateServiceDecorator { 48 | type Output 49 | = DateServiceDecorator 50 | where 51 | In: RequestHandler; 52 | 53 | fn create_decorator(&self) -> Self::Output 54 | where 55 | In: RequestHandler, 56 | { 57 | DateServiceDecorator 58 | } 59 | } 60 | 61 | #[async_trait] 62 | impl RequestHandler for DateResponseHandler { 63 | async fn invoke<'server, 'req>(&self, req: &mut RequestContext<'server, 'req>, req_body: OptionReqBody) -> Response { 64 | let mut resp = self.handler.invoke(req, req_body).await; 65 | 66 | self.date_service.with_http_date(|date_header_value| { 67 | resp.headers_mut().insert(http::header::DATE, date_header_value); 68 | }); 69 | 70 | resp 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/web/src/date/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP date header value management service. 2 | //! 3 | //! This module provides a service for efficiently managing and updating HTTP date header values 4 | //! in a concurrent environment. It updates the date string periodically to avoid repeated 5 | //! date string formatting operations in high-concurrency scenarios. 6 | 7 | use arc_swap::ArcSwap; 8 | use bytes::Bytes; 9 | use http::HeaderValue; 10 | use once_cell::sync::Lazy; 11 | use std::sync::Arc; 12 | use std::time::Duration; 13 | 14 | mod date_service_decorator; 15 | 16 | pub use date_service_decorator::DateServiceDecorator; 17 | 18 | /// A service that maintains and periodically updates the current HTTP date string. 19 | /// 20 | /// This service runs a background task that updates the date string every 700ms, 21 | /// providing an efficient way to access formatted HTTP date strings without 22 | /// formatting them on every request. 23 | pub struct DateService { 24 | current: Arc>, 25 | handle: tokio::task::JoinHandle<()>, 26 | } 27 | 28 | static DATE_SERVICE: Lazy = Lazy::new(|| DateService::new_with_update_interval(Duration::from_millis(800))); 29 | 30 | impl DateService { 31 | /// Returns a reference to the global singleton instance of `DateService`. 32 | /// 33 | /// This method provides access to a shared `DateService` instance that can be used 34 | /// across the application to efficiently handle HTTP date headers. 35 | /// 36 | /// # Returns 37 | /// A static reference to the global `DateService` instance. 38 | pub fn get_global_instance() -> &'static DateService { 39 | &DATE_SERVICE 40 | } 41 | 42 | /// Creates a new `DateService` instance. 43 | /// 44 | /// This method initializes the service with the current system time and starts 45 | /// a background task that updates the date string every 700ms. 46 | /// 47 | /// # Returns 48 | /// Returns a new `DateService` instance with the background update task running. 49 | fn new_with_update_interval(update_interval: Duration) -> Self { 50 | let mut buf = faf_http_date::get_date_buff_no_key(); 51 | faf_http_date::get_date_no_key(&mut buf); 52 | let bytes = Bytes::from_owner(buf); 53 | 54 | let current = Arc::new(ArcSwap::from_pointee(bytes)); 55 | let current_arc = Arc::clone(¤t); 56 | 57 | let handle = tokio::spawn(async move { 58 | loop { 59 | tokio::time::sleep(update_interval).await; 60 | let mut buf = faf_http_date::get_date_buff_no_key(); 61 | faf_http_date::get_date_no_key(&mut buf); 62 | let bytes = Bytes::from_owner(buf); 63 | current_arc.store(Arc::new(bytes)); 64 | } 65 | }); 66 | 67 | DateService { current, handle } 68 | } 69 | 70 | /// Provides access to the current HTTP date string through a callback function. 71 | /// 72 | /// This method allows safe access to the current date string without exposing 73 | /// the internal synchronization mechanisms. 74 | pub(crate) fn with_http_date(&self, mut f: F) 75 | where 76 | F: FnMut(HeaderValue), 77 | { 78 | let date = self.current.load().as_ref().clone(); 79 | // SAFE: date is created by faf_http_date, it's valid 80 | let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(date) }; 81 | f(header_value) 82 | } 83 | } 84 | 85 | /// Implements the `Drop` trait to ensure the background task is properly cleaned up 86 | /// when the `DateService` is dropped. 87 | impl Drop for DateService { 88 | fn drop(&mut self) { 89 | self.handle.abort(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/web/src/encoding/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module for handling HTTP response body encoding. 2 | //! 3 | //! This module provides functionality for encoding HTTP response bodies using different compression 4 | //! algorithms like gzip, deflate, zstd, and brotli. It works in conjunction with the encoder 5 | //! module to provide a complete encoding solution. 6 | //! 7 | //! The main components are: 8 | //! - `Writer`: An internal buffer implementation for collecting encoded data 9 | //! - `encoder`: A sub-module containing the encoding logic and request handler wrapper 10 | //! 11 | //! The implementation is inspired by the actix-http crate's encoding functionality. 12 | 13 | use bytes::{Bytes, BytesMut}; 14 | use std::io; 15 | 16 | pub mod encoder; 17 | 18 | // inspired by from actix-http 19 | pub(crate) struct Writer { 20 | buf: BytesMut, 21 | } 22 | 23 | impl Writer { 24 | fn new() -> Self { 25 | Self { buf: BytesMut::with_capacity(4096) } 26 | } 27 | 28 | fn take(&mut self) -> Bytes { 29 | self.buf.split().freeze() 30 | } 31 | } 32 | 33 | impl io::Write for Writer { 34 | fn write(&mut self, buf: &[u8]) -> io::Result { 35 | self.buf.extend_from_slice(buf); 36 | Ok(buf.len()) 37 | } 38 | 39 | fn flush(&mut self) -> io::Result<()> { 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/web/src/extract/extract_body.rs: -------------------------------------------------------------------------------- 1 | //! Body data extraction implementations 2 | //! 3 | //! This module provides implementations for extracting typed data from request bodies. 4 | //! It supports extracting raw bytes, strings, JSON data and form data. 5 | //! 6 | //! # Examples 7 | //! 8 | //! ```no_run 9 | //! # use micro_web::extract::{Json, Form}; 10 | //! # use serde::Deserialize; 11 | //! 12 | //! #[derive(Deserialize)] 13 | //! struct User { 14 | //! name: String, 15 | //! age: u32 16 | //! } 17 | //! 18 | //! // Extract JSON data 19 | //! async fn handle_json(Json(user): Json) { 20 | //! println!("Got user: {}", user.name); 21 | //! } 22 | //! 23 | //! // Extract form data 24 | //! async fn handle_form(Form(user): Form) { 25 | //! println!("Got user: {}", user.name); 26 | //! } 27 | //! ``` 28 | 29 | use crate::RequestContext; 30 | use crate::body::OptionReqBody; 31 | use crate::extract::from_request::FromRequest; 32 | use crate::extract::{Form, Json}; 33 | use bytes::Bytes; 34 | use http_body_util::BodyExt; 35 | use micro_http::protocol::ParseError; 36 | use serde::Deserialize; 37 | 38 | /// Extracts raw bytes from request body 39 | impl FromRequest for Bytes { 40 | type Output<'any> = Bytes; 41 | type Error = ParseError; 42 | 43 | async fn from_request(_req: &RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 44 | body.apply(|b| async { b.collect().await.map(|c| c.to_bytes()) }).await 45 | } 46 | } 47 | 48 | /// Extracts UTF-8 string from request body 49 | impl FromRequest for String { 50 | type Output<'any> = String; 51 | type Error = ParseError; 52 | 53 | async fn from_request(req: &RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 54 | let bytes = ::from_request(req, body).await?; 55 | // todo: using character to decode 56 | match String::from_utf8(bytes.into()) { 57 | Ok(s) => Ok(s), 58 | Err(_) => Err(ParseError::invalid_body("request body is not utf8")), 59 | } 60 | } 61 | } 62 | 63 | /// Extracts form data from request body 64 | /// 65 | /// This implementation expects the request body to be URL-encoded form data 66 | /// and deserializes it into the target type using `serde_urlencoded`. 67 | impl FromRequest for Form 68 | where 69 | T: for<'de> Deserialize<'de> + Send, 70 | { 71 | type Output<'r> = Form; 72 | type Error = ParseError; 73 | 74 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 75 | let bytes = ::from_request(req, body).await?; 76 | serde_urlencoded::from_bytes::<'_, T>(&bytes).map(|t| Form(t)).map_err(|e| ParseError::invalid_body(e.to_string())) 77 | } 78 | } 79 | 80 | /// Extracts JSON data from request body 81 | /// 82 | /// This implementation expects the request body to be valid JSON 83 | /// and deserializes it into the target type using `serde_json`. 84 | impl FromRequest for Json 85 | where 86 | T: for<'de> Deserialize<'de> + Send, 87 | { 88 | type Output<'r> = Json; 89 | type Error = ParseError; 90 | 91 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 92 | let bytes = ::from_request(req, body).await?; 93 | serde_json::from_slice::<'_, T>(&bytes).map(|t| Json(t)).map_err(|e| ParseError::invalid_body(e.to_string())) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/web/src/extract/extract_header.rs: -------------------------------------------------------------------------------- 1 | //! HTTP header extraction implementations 2 | //! 3 | //! This module provides extractors for HTTP header-related information from requests, 4 | //! including HTTP methods, header maps and raw request headers. These extractors allow 5 | //! handlers to directly access header information in a type-safe way. 6 | //! 7 | //! The extractors support both owned and borrowed access to the header data: 8 | //! - Owned extractors like `Method` and `HeaderMap` take ownership of the data 9 | //! - Borrowed extractors like `&Method` and `&HeaderMap` provide reference access 10 | //! 11 | //! # Examples 12 | //! 13 | //! ```no_run 14 | //! use http::{HeaderMap, Method}; 15 | //! 16 | //! // Access HTTP method 17 | //! async fn handle_method(method: Method) { 18 | //! match method { 19 | //! Method::GET => println!("Handling GET request"), 20 | //! Method::POST => println!("Handling POST request"), 21 | //! _ => println!("Handling other request method") 22 | //! } 23 | //! } 24 | //! 25 | //! // Access headers 26 | //! async fn handle_headers(headers: &HeaderMap) { 27 | //! if let Some(content_type) = headers.get("content-type") { 28 | //! println!("Content-Type: {:?}", content_type); 29 | //! } 30 | //! } 31 | //! ``` 32 | 33 | use crate::RequestContext; 34 | use crate::body::OptionReqBody; 35 | use crate::extract::from_request::FromRequest; 36 | use http::{HeaderMap, Method}; 37 | use micro_http::protocol::{ParseError, RequestHeader}; 38 | 39 | /// Extracts the HTTP method by value 40 | /// 41 | /// This extractor takes ownership of the request method. 42 | impl FromRequest for Method { 43 | type Output<'any> = Method; 44 | type Error = ParseError; 45 | 46 | async fn from_request(req: &RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 47 | Ok(req.method().clone()) 48 | } 49 | } 50 | 51 | /// Extracts a reference to the HTTP method 52 | /// 53 | /// This extractor borrows the request method, avoiding cloning. 54 | impl FromRequest for &Method { 55 | type Output<'r> = &'r Method; 56 | type Error = ParseError; 57 | 58 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 59 | Ok(req.method()) 60 | } 61 | } 62 | 63 | /// Extracts a reference to the raw request header 64 | /// 65 | /// Provides access to the underlying HTTP request header structure. 66 | impl FromRequest for &RequestHeader { 67 | type Output<'r> = &'r RequestHeader; 68 | type Error = ParseError; 69 | 70 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 71 | Ok(req.request_header()) 72 | } 73 | } 74 | 75 | /// Extracts a reference to the header map 76 | /// 77 | /// This extractor provides borrowed access to all HTTP headers. 78 | impl FromRequest for &HeaderMap { 79 | type Output<'r> = &'r HeaderMap; 80 | type Error = ParseError; 81 | 82 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 83 | Ok(req.headers()) 84 | } 85 | } 86 | 87 | /// Extracts the header map by value 88 | /// 89 | /// This extractor clones and takes ownership of all HTTP headers. 90 | impl FromRequest for HeaderMap { 91 | type Output<'any> = HeaderMap; 92 | type Error = ParseError; 93 | 94 | async fn from_request(req: &RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 95 | Ok(req.headers().clone()) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/web/src/extract/extract_tuple.rs: -------------------------------------------------------------------------------- 1 | use crate::body::OptionReqBody; 2 | use crate::extract::from_request::FromRequest; 3 | use crate::responder::Responder; 4 | use crate::{RequestContext, ResponseBody}; 5 | use http::Response; 6 | 7 | macro_rules! impl_from_request_for_fn { 8 | ($either:ident, $($param:ident)*) => { 9 | impl<$($param,)*> FromRequest for ($($param,)*) 10 | where 11 | $($param: FromRequest,)* 12 | $(for <'any> $param::Output<'any>: Send,)* 13 | { 14 | type Output<'r> = ($($param::Output<'r>,)*); 15 | type Error = $either<$($param::Error,)*>; 16 | 17 | #[allow(non_snake_case)] 18 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 19 | Ok(($($param::from_request(req, body.clone()).await.map_err($either::$param)?,)*)) 20 | } 21 | } 22 | 23 | pub enum $either<$($param,)*> { 24 | $( 25 | $param($param), 26 | )* 27 | } 28 | 29 | impl<$($param,)*> $either<$($param,)*> { 30 | $( 31 | #[allow(dead_code)] 32 | #[allow(non_snake_case)] 33 | fn $param($param: $param) -> Self { 34 | $either::$param($param) 35 | } 36 | )* 37 | } 38 | 39 | impl<$($param,)*> Responder for $either<$($param,)*> 40 | where 41 | $( 42 | $param: Responder, 43 | )* 44 | { 45 | #[allow(non_snake_case)] 46 | fn response_to(self, req: &RequestContext) -> Response { 47 | match self { 48 | $( 49 | $either::$param($param) => $param.response_to(req), 50 | )* 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl_from_request_for_fn! { EitherA, A } 58 | impl_from_request_for_fn! { EitherAB, A B} 59 | impl_from_request_for_fn! { EitherABC, A B C} 60 | impl_from_request_for_fn! { EitherABCD, A B C D } 61 | impl_from_request_for_fn! { EitherABCDE, A B C D E } 62 | impl_from_request_for_fn! { EitherABCDEF, A B C D E F } 63 | impl_from_request_for_fn! { EitherABCDEFG, A B C D E F G } 64 | impl_from_request_for_fn! { EitherABCDEFGH, A B C D E F G H } 65 | impl_from_request_for_fn! { EitherABCDEFGHI, A B C D E F G H I } 66 | impl_from_request_for_fn! { EitherABCDEFGHIJ, A B C D E F G H I J } 67 | impl_from_request_for_fn! { EitherABCDEFGHIJK, A B C D E F G H I J K } 68 | impl_from_request_for_fn! { EitherABCDEFGHIJKL, A B C D E F G H I J K L } 69 | -------------------------------------------------------------------------------- /crates/web/src/extract/extract_url.rs: -------------------------------------------------------------------------------- 1 | //! URL query string extraction functionality 2 | //! 3 | //! This module provides implementation for extracting typed data from URL query strings. 4 | //! It allows handlers to receive strongly-typed query parameters by implementing the 5 | //! `FromRequest` trait for the `Query` type. 6 | //! 7 | //! # Example 8 | //! ```no_run 9 | //! # use serde::Deserialize; 10 | //! # use micro_web::extract::Query; 11 | //! 12 | //! #[derive(Deserialize)] 13 | //! struct Params { 14 | //! name: String, 15 | //! age: u32, 16 | //! } 17 | //! 18 | //! async fn handler(Query(params): Query) { 19 | //! println!("Name: {}, Age: {}", params.name, params.age); 20 | //! } 21 | //! ``` 22 | 23 | use crate::extract::Query; 24 | use crate::extract::from_request::FromRequest; 25 | use crate::{OptionReqBody, PathParams, RequestContext}; 26 | use micro_http::protocol::ParseError; 27 | use serde::Deserialize; 28 | 29 | /// Implements query string extraction for any type that implements Deserialize 30 | /// 31 | /// This implementation allows automatic deserialization of query string parameters 32 | /// into a strongly-typed struct using serde_qs. 33 | impl FromRequest for Query 34 | where 35 | T: for<'de> Deserialize<'de> + Send, 36 | { 37 | type Output<'r> = T; 38 | type Error = ParseError; 39 | 40 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 41 | let query = req.uri().query().ok_or_else(|| ParseError::invalid_header("has no query string, path"))?; 42 | serde_qs::from_str::<'_, T>(query).map_err(|e| ParseError::invalid_header(e.to_string())) 43 | } 44 | } 45 | 46 | /// Implements path parameter extraction for referenced PathParams 47 | /// 48 | /// This implementation is similar to the owned version but works with references 49 | /// to PathParams. It allows handlers to receive path parameters as references 50 | /// directly from the request context. 51 | impl FromRequest for &PathParams<'_, '_> { 52 | type Output<'r> = &'r PathParams<'r, 'r>; 53 | type Error = ParseError; 54 | 55 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 56 | Ok(req.path_params()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/web/src/extract/from_request.rs: -------------------------------------------------------------------------------- 1 | use crate::responder::Responder; 2 | use crate::{OptionReqBody, RequestContext, ResponseBody}; 3 | use http::{Response, StatusCode}; 4 | use micro_http::protocol::ParseError; 5 | use std::convert::Infallible; 6 | 7 | #[trait_variant::make(Send)] 8 | pub trait FromRequest { 9 | type Output<'r>: Send; 10 | type Error: Responder + Send; 11 | 12 | #[allow(unused)] 13 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error>; 14 | } 15 | 16 | impl FromRequest for Option { 17 | type Output<'r> = Option>; 18 | type Error = T::Error; 19 | 20 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 21 | match T::from_request(req, body).await { 22 | Ok(result) => Ok(Some(result)), 23 | Err(_err) => Ok(None), 24 | } 25 | } 26 | } 27 | 28 | impl FromRequest for Result { 29 | type Output<'r> = Result, T::Error>; 30 | type Error = T::Error; 31 | 32 | async fn from_request<'r>(req: &'r RequestContext<'_, '_>, body: OptionReqBody) -> Result, Self::Error> { 33 | Ok(T::from_request(req, body).await) 34 | } 35 | } 36 | 37 | impl FromRequest for () { 38 | type Output<'r> = (); 39 | type Error = Infallible; 40 | 41 | async fn from_request<'r>(_req: &'r RequestContext<'_, '_>, _body: OptionReqBody) -> Result, Self::Error> { 42 | Ok(()) 43 | } 44 | } 45 | 46 | /// Responder implementation for ParseError to convert parsing errors to responses 47 | impl Responder for ParseError { 48 | fn response_to(self, req: &RequestContext) -> Response { 49 | match self { 50 | ParseError::TooLargeHeader { .. } => (StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, "payload too large").response_to(req), 51 | ParseError::TooManyHeaders { .. } => (StatusCode::BAD_REQUEST, "too many headers").response_to(req), 52 | ParseError::InvalidHeader { .. } => (StatusCode::BAD_REQUEST, "invalid header").response_to(req), 53 | ParseError::InvalidVersion(_) => (StatusCode::BAD_REQUEST, "invalid version").response_to(req), 54 | ParseError::InvalidMethod => (StatusCode::BAD_REQUEST, "invalid method").response_to(req), 55 | ParseError::InvalidUri => (StatusCode::BAD_REQUEST, "invalid uri").response_to(req), 56 | ParseError::InvalidContentLength { .. } => (StatusCode::BAD_REQUEST, "invalid content length").response_to(req), 57 | ParseError::InvalidBody { .. } => (StatusCode::BAD_REQUEST, "invalid body").response_to(req), 58 | ParseError::Io { .. } => (StatusCode::BAD_REQUEST, "connection error").response_to(req), 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/web/src/extract/mod.rs: -------------------------------------------------------------------------------- 1 | //! Request data extraction module 2 | //! 3 | //! This module provides functionality for extracting typed data from HTTP requests. 4 | //! It includes extractors for common data formats and patterns: 5 | //! 6 | //! - Form data (`Form`) - For `application/x-www-form-urlencoded` request bodies 7 | //! - JSON data (`Json`) - For `application/json` request bodies 8 | //! - Query parameters (`Query`) - For URL query strings 9 | //! - Headers and other request metadata 10 | //! - Raw request body as bytes or string 11 | //! 12 | //! # Core Concepts 13 | //! 14 | //! The module is built around the [`FromRequest`] trait, which defines how to extract 15 | //! typed data from requests. Types implementing this trait can be used as parameters 16 | //! in request handlers. 17 | //! 18 | //! # Common Extractors 19 | //! 20 | //! ## Form Data 21 | //! ```no_run 22 | //! # use serde::Deserialize; 23 | //! # use micro_web::extract::Form; 24 | //! #[derive(Deserialize)] 25 | //! struct LoginForm { 26 | //! username: String, 27 | //! password: String, 28 | //! } 29 | //! 30 | //! async fn handle_login(Form(form): Form) { 31 | //! println!("Login attempt from: {}", form.username); 32 | //! } 33 | //! ``` 34 | //! 35 | //! ## JSON Data 36 | //! ```no_run 37 | //! # use serde::Deserialize; 38 | //! # use micro_web::extract::Json; 39 | //! #[derive(Deserialize)] 40 | //! struct User { 41 | //! name: String, 42 | //! email: String, 43 | //! } 44 | //! 45 | //! async fn create_user(Json(user): Json) { 46 | //! println!("Creating user: {}", user.name); 47 | //! } 48 | //! ``` 49 | //! 50 | //! ## Query Parameters 51 | //! ```no_run 52 | //! # use serde::Deserialize; 53 | //! # use micro_web::extract::Query; 54 | //! #[derive(Deserialize)] 55 | //! struct Pagination { 56 | //! page: u32, 57 | //! per_page: u32, 58 | //! } 59 | //! 60 | //! async fn list_items(Query(params): Query) { 61 | //! println!("Listing page {} with {} items", params.page, params.per_page); 62 | //! } 63 | //! ``` 64 | //! 65 | //! # Optional Extraction 66 | //! 67 | //! All extractors can be made optional by wrapping them in `Option`: 68 | //! 69 | //! ``` 70 | //! # use serde::Deserialize; 71 | //! # use micro_web::extract::Json; 72 | //! #[derive(Deserialize)] 73 | //! struct UpdateUser { 74 | //! name: Option, 75 | //! email: Option, 76 | //! } 77 | //! 78 | //! async fn update_user(Json(update): Json) { 79 | //! if let Some(name) = update.name { 80 | //! println!("Updating name to: {}", name); 81 | //! } 82 | //! } 83 | //! ``` 84 | //! 85 | //! # Multiple Extractors 86 | //! 87 | //! Multiple extractors can be combined using tuples: 88 | //! 89 | //! ``` 90 | //! # use serde::Deserialize; 91 | //! # use micro_web::extract::{Json, Query}; 92 | //! # use http::Method; 93 | //! #[derive(Deserialize)] 94 | //! struct SearchParams { 95 | //! q: String, 96 | //! } 97 | //! 98 | //! #[derive(Deserialize)] 99 | //! struct Payload { 100 | //! data: String, 101 | //! } 102 | //! 103 | //! async fn handler( 104 | //! method: Method, 105 | //! Query(params): Query, 106 | //! Json(payload): Json, 107 | //! ) { 108 | //! // Access to method, query params, and JSON payload 109 | //! } 110 | //! ``` 111 | 112 | mod extract_body; 113 | mod extract_header; 114 | mod extract_tuple; 115 | mod extract_url; 116 | mod from_request; 117 | 118 | pub use from_request::FromRequest; 119 | use serde::Deserialize; 120 | 121 | /// Represented as form data 122 | /// 123 | /// when `post` as a `application/x-www-form-urlencoded`, we can using this struct to inject data, 124 | /// note: the struct must impl [`serde::Deserialize`] and [`Send`] 125 | /// 126 | /// # Example 127 | /// ``` 128 | /// # use serde::Deserialize; 129 | /// # use micro_web::extract::Form; 130 | /// # #[allow(dead_code)] 131 | /// #[derive(Deserialize, Debug)] 132 | /// struct Params { 133 | /// name: String, 134 | /// zip: String, 135 | /// } 136 | /// 137 | /// pub async fn handle(Form(params) : Form) -> String { 138 | /// format!("received params: {:?}", params) 139 | /// } 140 | /// ``` 141 | pub struct Form(pub T) 142 | where 143 | T: for<'de> Deserialize<'de> + Send; 144 | 145 | /// Represented as json data 146 | /// 147 | /// when `post` as a `application/json`, we can using this struct to inject data, 148 | /// note: the struct must impl [`serde::Deserialize`] and [`Send`] 149 | /// 150 | /// # Example 151 | /// ``` 152 | /// # use serde::Deserialize; 153 | /// # use micro_web::extract::Json; 154 | /// # #[allow(dead_code)] 155 | /// #[derive(Deserialize, Debug)] 156 | /// struct Params { 157 | /// name: String, 158 | /// zip: String, 159 | /// } 160 | /// 161 | /// pub async fn handle(Json(params) : Json) -> String { 162 | /// format!("received params: {:?}", params) 163 | /// } 164 | /// ``` 165 | pub struct Json(pub T) 166 | where 167 | T: for<'de> Deserialize<'de> + Send; 168 | 169 | /// Represented as url query data 170 | /// 171 | /// when request with url query, we can using this struct to inject data, 172 | /// note: the struct must impl [`serde::Deserialize`] and [`Send`] 173 | /// 174 | /// # Example 175 | /// ``` 176 | /// # use serde::Deserialize; 177 | /// # use micro_web::extract::Query; 178 | /// # #[allow(dead_code)] 179 | /// #[derive(Deserialize, Debug)] 180 | /// struct Params { 181 | /// name: String, 182 | /// zip: String, 183 | /// } 184 | /// 185 | /// pub async fn handle(Query(params) : Query) -> String { 186 | /// format!("received params: {:?}", params) 187 | /// } 188 | /// ``` 189 | pub struct Query(pub T) 190 | where 191 | T: for<'de> Deserialize<'de> + Send; 192 | -------------------------------------------------------------------------------- /crates/web/src/fn_trait.rs: -------------------------------------------------------------------------------- 1 | //! Function trait implementation for handling async request handlers 2 | //! 3 | //! This module provides the [`FnTrait`] trait which is used to abstract over different types 4 | //! of async functions that can serve as request handlers. It supports functions with varying 5 | //! numbers of parameters (from 0 to 12) and ensures they can be used in an async context. 6 | //! 7 | //! # Examples 8 | //! 9 | //! ```no_run 10 | //! // Handler with no parameters 11 | //! async fn handler0() -> &'static str { 12 | //! "Hello World!" 13 | //! } 14 | //! 15 | //! // Handler with two parameters 16 | //! async fn handler2(param1: &str, param2: i32) -> String { 17 | //! format!("Hello {}, number: {}", param1, param2) 18 | //! } 19 | //! ``` 20 | //! 21 | //! The trait is automatically implemented for functions that: 22 | //! - Are async functions 23 | //! - Return a Future 24 | //! - Are Send + Sync 25 | //! - Have 0-12 parameters 26 | 27 | use std::future::Future; 28 | /// A trait for abstracting over async functions with varying numbers of parameters. 29 | /// 30 | /// This trait allows the web framework to work with different types of handler functions 31 | /// in a uniform way, regardless of their parameter count or types. 32 | /// 33 | /// # Type Parameters 34 | /// 35 | /// * `Args`: The tuple type containing all function parameters 36 | /// 37 | /// # Associated Types 38 | /// 39 | /// * `Output`: The type that the function returns when resolved 40 | /// * `Fut`: The specific Future type that the function returns 41 | /// 42 | /// # Examples 43 | /// 44 | /// ```rust 45 | /// use http::Method;/// 46 | /// 47 | /// use micro_web::FnTrait; 48 | /// 49 | /// async fn my_handler(method: &Method) -> String { 50 | /// format!("Handling {} request", method) 51 | /// } 52 | /// 53 | /// // The function automatically implements FnTrait 54 | /// fn assert_handler<'r, F: FnTrait<(&'r Method,)>>(f: F) {} 55 | /// assert_handler(my_handler); 56 | /// ``` 57 | pub trait FnTrait: Send + Sync { 58 | type Output; 59 | type Fut: Future + Send; 60 | fn call(&self, args: Args) -> Self::Fut; 61 | } 62 | 63 | /// Implements `FnTrait` for functions with varying numbers of parameters. 64 | /// 65 | /// This macro generates implementations for functions with 0 to 12 parameters, 66 | /// allowing them to be used as request handlers in the web framework. 67 | /// 68 | /// The generated implementations ensure that: 69 | /// - The function is Send + Sync 70 | /// - The returned Future is Send 71 | /// - Parameters are properly passed through to the function 72 | /// 73 | /// # Example Generated Implementation 74 | /// 75 | /// ```ignore 76 | /// // For a two-parameter function: 77 | /// use micro_web::FnTrait; 78 | /// 79 | /// impl FnTrait<(A, B)> for Func 80 | /// where 81 | /// Func: Fn(A, B) -> Fut + Send + Sync, 82 | /// Fut: std::future::Future + Send, 83 | /// { 84 | /// type Output = Fut::Output; 85 | /// type Fut = Fut; 86 | /// 87 | /// fn call(&self, (a, b): (A, B)) -> Self::Fut { 88 | /// (self)(a, b) 89 | /// } 90 | /// } 91 | /// ``` 92 | macro_rules! impl_fn_trait_for_fn ({ $($param:ident)* } => { 93 | impl FnTrait<($($param,)*)> for Func 94 | where 95 | Func: Fn($($param),*) -> Fut + Send + Sync , 96 | Fut: std::future::Future + Send, 97 | { 98 | type Output = Fut::Output; 99 | type Fut = Fut; 100 | 101 | #[inline] 102 | #[allow(non_snake_case)] 103 | fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Fut { 104 | (self)($($param,)*) 105 | } 106 | } 107 | }); 108 | 109 | impl_fn_trait_for_fn! {} 110 | impl_fn_trait_for_fn! { A } 111 | impl_fn_trait_for_fn! { A B } 112 | impl_fn_trait_for_fn! { A B C } 113 | impl_fn_trait_for_fn! { A B C D } 114 | impl_fn_trait_for_fn! { A B C D E } 115 | impl_fn_trait_for_fn! { A B C D E F } 116 | impl_fn_trait_for_fn! { A B C D E F G } 117 | impl_fn_trait_for_fn! { A B C D E F G H } 118 | impl_fn_trait_for_fn! { A B C D E F G H I } 119 | impl_fn_trait_for_fn! { A B C D E F G H I J } 120 | impl_fn_trait_for_fn! { A B C D E F G H I J K } 121 | impl_fn_trait_for_fn! { A B C D E F G H I J K L } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use crate::fn_trait::FnTrait; 126 | use http::{HeaderMap, Method}; 127 | 128 | fn assert_is_fn_trait>(_f: F) { 129 | //noop 130 | } 131 | async fn foo0() {} 132 | async fn foo1(_a: ()) {} 133 | async fn foo2(_a1: &Method, _a2: &HeaderMap) {} 134 | async fn foo3(_a1: &Method, _a2: &HeaderMap, _a3: ()) {} 135 | async fn foo4(_a1: &Method, _a2: &HeaderMap, _a3: (), _a4: ()) {} 136 | async fn foo5(_a1: (), _a2: &HeaderMap, _a3: (), _a4: (), _a5: ()) {} 137 | async fn foo6(_a1: (), _a2: &HeaderMap, _a3: (), _a4: (), _a5: (), _a6: ()) {} 138 | async fn foo7(_a1: &Method, _a2: (), _a3: (), _a4: (), _a5: (), _a6: (), _a7: ()) {} 139 | #[allow(clippy::too_many_arguments)] 140 | async fn foo8(_a1: &Method, _a2: &HeaderMap, _a3: (), _a4: (), _a5: (), _a6: (), _a7: (), _a8: ()) {} 141 | #[allow(clippy::too_many_arguments)] 142 | async fn foo9(_a1: &Method, _a2: (), _a3: (), _a4: (), _a5: (), _a6: (), _a7: (), _a8: (), _a9: ()) {} 143 | #[allow(clippy::too_many_arguments)] 144 | async fn foo10(_a1: &Method, _a2: &HeaderMap, _a3: (), _a4: (), _a5: (), _a6: (), _a7: (), _a8: (), _a9: (), _a10: ()) {} 145 | #[allow(clippy::too_many_arguments)] 146 | async fn foo11(_a1: &Method, _a2: &HeaderMap, _a3: (), _a4: (), _a5: (), _a6: (), _a7: (), _a8: (), _a9: (), _a10: (), _a11: ()) {} 147 | #[allow(clippy::too_many_arguments)] 148 | async fn foo12( 149 | _a1: &Method, 150 | _a2: &HeaderMap, 151 | _a3: (), 152 | _a4: (), 153 | _a5: (), 154 | _a6: (), 155 | _a7: (), 156 | _a8: (), 157 | _a9: (), 158 | _a10: (), 159 | _a11: (), 160 | _a12: (), 161 | ) { 162 | } 163 | 164 | #[test] 165 | fn test_fn_is_fn_trait() { 166 | assert_is_fn_trait(foo0); 167 | assert_is_fn_trait(foo1); 168 | assert_is_fn_trait(foo2); 169 | assert_is_fn_trait(foo3); 170 | assert_is_fn_trait(foo4); 171 | assert_is_fn_trait(foo5); 172 | assert_is_fn_trait(foo6); 173 | assert_is_fn_trait(foo7); 174 | assert_is_fn_trait(foo8); 175 | assert_is_fn_trait(foo9); 176 | assert_is_fn_trait(foo10); 177 | assert_is_fn_trait(foo11); 178 | assert_is_fn_trait(foo12); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /crates/web/src/handler.rs: -------------------------------------------------------------------------------- 1 | //! Request handler types and traits for the web framework. 2 | //! 3 | //! This module provides the core abstractions for handling HTTP requests: 4 | //! - `RequestHandler` trait for implementing request handlers 5 | //! - `FnHandler` for wrapping async functions as handlers 6 | //! - Helper functions for creating handlers 7 | 8 | use crate::body::ResponseBody; 9 | use crate::fn_trait::FnTrait; 10 | 11 | use crate::responder::Responder; 12 | use crate::{OptionReqBody, RequestContext}; 13 | use async_trait::async_trait; 14 | use http::Response; 15 | 16 | use crate::extract::FromRequest; 17 | use std::marker::PhantomData; 18 | 19 | pub mod handler_decorator; 20 | pub mod handler_decorator_factory; 21 | 22 | /// Trait for types that can handle HTTP requests. 23 | /// 24 | /// Implementors must provide an `invoke` method that processes the request 25 | /// and returns a response. This is the core trait for request handling. 26 | #[async_trait] 27 | pub trait RequestHandler: Send + Sync { 28 | async fn invoke<'server, 'req>(&self, req: &mut RequestContext<'server, 'req>, req_body: OptionReqBody) -> Response; 29 | } 30 | 31 | #[async_trait] 32 | impl RequestHandler for Box 33 | where 34 | T: RequestHandler, 35 | { 36 | async fn invoke<'server, 'req>(&self, req: &mut RequestContext<'server, 'req>, req_body: OptionReqBody) -> Response { 37 | (**self).invoke(req, req_body).await 38 | } 39 | } 40 | 41 | #[async_trait] 42 | impl RequestHandler for Box { 43 | async fn invoke<'server, 'req>(&self, req: &mut RequestContext<'server, 'req>, req_body: OptionReqBody) -> Response { 44 | (**self).invoke(req, req_body).await 45 | } 46 | } 47 | 48 | #[async_trait] 49 | impl RequestHandler for &dyn RequestHandler { 50 | async fn invoke<'server, 'req>(&self, req: &mut RequestContext<'server, 'req>, req_body: OptionReqBody) -> Response { 51 | (**self).invoke(req, req_body).await 52 | } 53 | } 54 | 55 | /// A wrapper type that converts async functions into request handlers. 56 | /// 57 | /// This allows regular async functions to be used as request handlers 58 | /// by implementing the `RequestHandler` trait for them. 59 | /// 60 | /// Type parameters: 61 | /// - `F`: The async function type 62 | /// - `Args`: The function arguments type 63 | pub struct FnHandler { 64 | f: F, 65 | _phantom: PhantomData, 66 | } 67 | 68 | impl FnHandler 69 | where 70 | F: FnTrait, 71 | { 72 | fn new(f: F) -> Self { 73 | Self { f, _phantom: PhantomData } 74 | } 75 | } 76 | 77 | /// Creates a new `FnHandler` that wraps the given async function. 78 | /// 79 | /// This is the main way to convert an async function into a request handler. 80 | pub fn handler_fn(f: F) -> FnHandler 81 | where 82 | F: FnTrait, 83 | { 84 | FnHandler::new(f) 85 | } 86 | 87 | #[async_trait] 88 | impl RequestHandler for FnHandler 89 | where 90 | // Args must implement [`FromRequest`] trait 91 | // This allows extracting the function arguments from the HTTP request 92 | Args: FromRequest, 93 | // F must be a function [`FnTrait`] that can accept Args::Output<'r> for any lifetime 'r 94 | // This allows the function to work with arguments that have different lifetimes 95 | for<'r> F: FnTrait>, 96 | // The output of calling F must implement [`Responder`] trait 97 | // This ensures the function's return value can be converted into an HTTP response 98 | for<'r> >>::Output: Responder, 99 | { 100 | async fn invoke<'server, 'req>(&self, req: &mut RequestContext<'server, 'req>, req_body: OptionReqBody) -> Response { 101 | let args = match Args::from_request(req, req_body.clone()).await { 102 | Ok(args) => args, 103 | Err(responder) => return responder.response_to(req), 104 | }; 105 | let responder = self.f.call(args).await; 106 | responder.response_to(req) 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod test { 112 | use crate::fn_trait::FnTrait; 113 | use crate::handler::{FnHandler, RequestHandler}; 114 | 115 | use http::Method; 116 | 117 | fn assert_is_fn_handler, Args>(_handler: &FnHandler) { 118 | // no op 119 | } 120 | 121 | fn assert_is_handler(_handler: &T) { 122 | // no op 123 | } 124 | 125 | #[test] 126 | fn assert_fn_is_http_handler_1() { 127 | async fn get(_header: Method) {} 128 | 129 | let http_handler = FnHandler::new(get); 130 | assert_is_fn_handler(&http_handler); 131 | assert_is_handler(&http_handler); 132 | } 133 | 134 | #[test] 135 | fn assert_fn_is_http_handler_2() { 136 | async fn get(_header: &Method, _str: String) {} 137 | 138 | let http_handler = FnHandler::new(get); 139 | assert_is_fn_handler(&http_handler); 140 | assert_is_handler(&http_handler); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /crates/web/src/handler/handler_decorator.rs: -------------------------------------------------------------------------------- 1 | use crate::handler::RequestHandler; 2 | 3 | pub trait HandlerDecorator { 4 | type Output: RequestHandler; 5 | 6 | fn decorate(&self, handler: In) -> Self::Output; 7 | } 8 | 9 | #[allow(unused)] 10 | pub trait HandlerDecoratorExt: HandlerDecorator { 11 | fn and_then(self, decorator: D) -> HandlerDecoratorComposer 12 | where 13 | Self: Sized, 14 | { 15 | HandlerDecoratorComposer::new(self, decorator) 16 | } 17 | 18 | fn compose(self, decorator: D) -> HandlerDecoratorComposer 19 | where 20 | Self: Sized, 21 | { 22 | HandlerDecoratorComposer::new(decorator, self) 23 | } 24 | } 25 | 26 | impl + ?Sized, In: RequestHandler> HandlerDecoratorExt for T {} 27 | 28 | #[derive(Default, Copy, Clone, Debug)] 29 | pub struct IdentityHandlerDecorator; 30 | 31 | impl HandlerDecorator for IdentityHandlerDecorator { 32 | type Output = In; 33 | 34 | fn decorate(&self, handler: In) -> Self::Output { 35 | handler 36 | } 37 | } 38 | 39 | pub struct HandlerDecoratorComposer { 40 | decorator_1: D1, 41 | decorator_2: D2, 42 | } 43 | 44 | impl HandlerDecoratorComposer { 45 | pub fn new(decorator_1: D1, decorator_2: D2) -> Self { 46 | HandlerDecoratorComposer { decorator_1, decorator_2 } 47 | } 48 | } 49 | 50 | impl HandlerDecorator for HandlerDecoratorComposer 51 | where 52 | In: RequestHandler, 53 | D1: HandlerDecorator, 54 | D2: HandlerDecorator, 55 | { 56 | type Output = D2::Output; 57 | fn decorate(&self, handler: In) -> Self::Output { 58 | let output_1 = self.decorator_1.decorate(handler); 59 | self.decorator_2.decorate(output_1) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/web/src/handler/handler_decorator_factory.rs: -------------------------------------------------------------------------------- 1 | use crate::handler::RequestHandler; 2 | use crate::handler::handler_decorator::{HandlerDecorator, HandlerDecoratorComposer, IdentityHandlerDecorator}; 3 | 4 | pub trait HandlerDecoratorFactory: Sized { 5 | type Output: HandlerDecorator + 'static 6 | where 7 | In: RequestHandler; 8 | 9 | fn create_decorator(&self) -> Self::Output 10 | where 11 | In: RequestHandler; 12 | } 13 | 14 | pub trait HandlerDecoratorFactoryExt: HandlerDecoratorFactory { 15 | fn and_then(self, factory: F) -> HandlerDecoratorFactoryComposer { 16 | HandlerDecoratorFactoryComposer { factory_1: self, factory_2: factory } 17 | } 18 | 19 | #[allow(unused)] 20 | fn compose(self, factory: F) -> HandlerDecoratorFactoryComposer { 21 | HandlerDecoratorFactoryComposer { factory_1: factory, factory_2: self } 22 | } 23 | } 24 | 25 | impl HandlerDecoratorFactoryExt for T {} 26 | 27 | #[derive(Debug, Default, Copy, Clone)] 28 | pub struct IdentityHandlerDecoratorFactory; 29 | 30 | impl HandlerDecoratorFactory for IdentityHandlerDecoratorFactory { 31 | type Output 32 | = IdentityHandlerDecorator 33 | where 34 | In: RequestHandler; 35 | 36 | fn create_decorator(&self) -> Self::Output 37 | where 38 | In: RequestHandler, 39 | { 40 | IdentityHandlerDecorator 41 | } 42 | } 43 | 44 | pub struct HandlerDecoratorFactoryComposer { 45 | factory_1: F1, 46 | factory_2: F2, 47 | } 48 | 49 | impl HandlerDecoratorFactory for HandlerDecoratorFactoryComposer 50 | where 51 | F1: HandlerDecoratorFactory, 52 | F2: HandlerDecoratorFactory, 53 | { 54 | // F1::Output means: first factory's output, which is the first Decorator 55 | // F2::Output< as HandlerDecorator>::Out> means: 56 | // 1. ` as HandlerDecorator` means: the first Decorator 57 | // 2. ` as HandlerDecorator>::Out` means: the first Decorator's result 58 | // 3. `Output< as HandlerDecorator>::Out>` means: the second Decorator's param is the first Decorator's result 59 | type Output 60 | = HandlerDecoratorComposer, F2::Output< as HandlerDecorator>::Output>> 61 | where 62 | In: RequestHandler; 63 | 64 | fn create_decorator(&self) -> Self::Output 65 | where 66 | In: RequestHandler, 67 | { 68 | let decorator_1 = self.factory_1.create_decorator(); 69 | let decorator_2 = self.factory_2.create_decorator(); 70 | 71 | HandlerDecoratorComposer::new(decorator_1, decorator_2) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/web/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! High-level web framework built on top of micro-http. 2 | //! 3 | //! This crate provides an ergonomic web framework that simplifies building HTTP services 4 | //! by offering high-level abstractions while leveraging the efficient HTTP implementation 5 | //! from the micro-http crate. 6 | //! 7 | //! # Core Features 8 | //! 9 | //! - **Ergonomic Request Handling** 10 | //! - Async function handlers with automatic type conversion 11 | //! - Flexible routing with path parameters 12 | //! - Built-in support for common data formats (JSON, form data, etc.) 13 | //! 14 | //! - **Middleware System** 15 | //! - Composable request/response transformations 16 | //! - Built-in middleware for common tasks (compression, date headers, etc.) 17 | //! - Easy to implement custom middleware 18 | //! 19 | //! - **Type-Safe Extractors** 20 | //! - Automatic request data extraction into Rust types 21 | //! - Support for headers, query parameters, and request bodies 22 | //! - Custom extractor implementation possible 23 | //! 24 | //! - **Flexible Response Types** 25 | //! - Automatic conversion of Rust types to HTTP responses 26 | //! - Streaming response support 27 | //! - Content negotiation and compression 28 | //! 29 | //! # Architecture 30 | //! 31 | //! The framework is organized into several key modules: 32 | //! 33 | //! - **Core Types** ([`handler`], [`request`], [`responder`]) 34 | //! - Request handling traits and implementations 35 | //! - Context types for accessing request data 36 | //! - Response generation utilities 37 | //! 38 | //! - **Routing** ([`router`]) 39 | //! - URL pattern matching 40 | //! - HTTP method filtering 41 | //! - Route parameter extraction 42 | //! 43 | //! - **Data Extraction** ([`extract`]) 44 | //! - Query string parsing 45 | //! - Form data handling 46 | //! - JSON serialization/deserialization 47 | //! 48 | //! - **Request Filtering** ([`filter`]) 49 | //! - Header-based filtering 50 | //! - Method matching 51 | //! - Custom filter implementation 52 | //! 53 | //! - **Middleware** ([`wrapper`]) 54 | //! - Response transformation 55 | //! - Cross-cutting concerns 56 | //! - Built-in middleware components 57 | //! 58 | //! # Example 59 | //! 60 | //! ```no_run 61 | //! use micro_web::{ 62 | //! router::{get, Router}, 63 | //! handler_fn, 64 | //! Server, 65 | //! }; 66 | //! 67 | //! // Define a simple handler 68 | //! async fn hello_world() -> &'static str { 69 | //! "Hello, World!" 70 | //! } 71 | //! 72 | //! #[tokio::main] 73 | //! async fn main() { 74 | //! // Create a router 75 | //! let router = Router::builder() 76 | //! .route("/", get(handler_fn(hello_world))) 77 | //! .build(); 78 | //! 79 | //! // Start the server 80 | //! Server::builder() 81 | //! .router(router) 82 | //! .bind("127.0.0.1:3000") 83 | //! .build() 84 | //! .unwrap() 85 | //! .start() 86 | //! .await; 87 | //! } 88 | //! ``` 89 | //! 90 | //! # Relationship with micro-http 91 | //! 92 | //! This framework builds upon the low-level HTTP implementation provided by micro-http: 93 | //! 94 | //! - micro-http handles the raw TCP connections and HTTP protocol details 95 | //! - This crate provides the high-level abstractions for building web services 96 | //! - The integration is seamless while maintaining performance 97 | //! 98 | //! See the [micro-http documentation](micro_http) for more details about the underlying implementation. 99 | 100 | // Internal modules 101 | mod body; 102 | mod fn_trait; 103 | mod handler; 104 | mod request; 105 | mod server; 106 | 107 | // Public modules 108 | pub mod date; 109 | pub mod encoding; 110 | pub mod extract; 111 | pub mod responder; 112 | pub mod router; 113 | 114 | // Public re-exports 115 | pub use body::OptionReqBody; 116 | pub use body::ResponseBody; 117 | pub use fn_trait::FnTrait; 118 | pub use handler::FnHandler; 119 | pub use handler::handler_fn; 120 | pub use request::PathParams; 121 | pub use request::RequestContext; 122 | pub use server::Server; 123 | -------------------------------------------------------------------------------- /crates/web/src/request.rs: -------------------------------------------------------------------------------- 1 | //! Request handling module that provides access to HTTP request information and path parameters. 2 | //! 3 | //! This module contains the core types for working with HTTP requests in the web framework: 4 | //! - `RequestContext`: Provides access to request headers and path parameters 5 | //! - `PathParams`: Handles URL path parameters extracted from request paths 6 | 7 | use http::{HeaderMap, Method, Uri, Version}; 8 | use matchit::Params; 9 | use micro_http::protocol::RequestHeader; 10 | 11 | /// Represents the context of an HTTP request, providing access to both the request headers 12 | /// and any path parameters extracted from the URL. 13 | /// 14 | /// The lifetime parameters ensure that the request context does not outlive the server 15 | /// or the request data it references. 16 | pub struct RequestContext<'server: 'req, 'req> { 17 | request_header: &'req RequestHeader, 18 | path_params: &'req PathParams<'server, 'req>, 19 | } 20 | 21 | impl<'server, 'req> RequestContext<'server, 'req> { 22 | /// Creates a new RequestContext with the given request header and path parameters 23 | pub fn new(request_header: &'req RequestHeader, path_params: &'req PathParams<'server, 'req>) -> Self { 24 | Self { request_header, path_params } 25 | } 26 | 27 | /// Returns a reference to the underlying RequestHeader 28 | pub fn request_header(&self) -> &RequestHeader { 29 | self.request_header 30 | } 31 | 32 | /// Returns the HTTP method of the request 33 | pub fn method(&self) -> &Method { 34 | self.request_header.method() 35 | } 36 | 37 | /// Returns the URI of the request 38 | pub fn uri(&self) -> &Uri { 39 | self.request_header.uri() 40 | } 41 | 42 | /// Returns the HTTP version of the request 43 | pub fn version(&self) -> Version { 44 | self.request_header.version() 45 | } 46 | 47 | /// Returns the HTTP headers of the request 48 | pub fn headers(&self) -> &HeaderMap { 49 | self.request_header.headers() 50 | } 51 | 52 | /// Returns a reference to the path parameters extracted from the request URL 53 | pub fn path_params(&self) -> &PathParams { 54 | self.path_params 55 | } 56 | } 57 | 58 | /// Represents path parameters extracted from the URL path of an HTTP request. 59 | /// 60 | /// Path parameters are named segments in the URL path that can be extracted and accessed 61 | /// by name. For example, in the path "/users/{id}", "id" is a path parameter. 62 | #[derive(Debug, Clone)] 63 | pub struct PathParams<'server, 'req> { 64 | kind: PathParamsKind<'server, 'req>, 65 | } 66 | 67 | /// Internal enum to represent either empty parameters or actual parameters 68 | #[derive(Debug, Clone)] 69 | enum PathParamsKind<'server, 'req> { 70 | None, 71 | Params(Params<'server, 'req>), 72 | } 73 | 74 | impl<'server, 'req> PathParams<'server, 'req> { 75 | /// Creates a new PathParams instance from the given Params 76 | /// If the params are empty, returns an empty PathParams instance 77 | #[inline] 78 | fn new(params: Params<'server, 'req>) -> Self { 79 | if params.is_empty() { Self::empty() } else { Self { kind: PathParamsKind::Params(params) } } 80 | } 81 | 82 | /// Creates an empty PathParams instance with no parameters 83 | #[inline] 84 | pub fn empty() -> Self { 85 | Self { kind: PathParamsKind::None } 86 | } 87 | 88 | /// Returns true if there are no path parameters 89 | #[inline] 90 | pub fn is_empty(&self) -> bool { 91 | match &self.kind { 92 | PathParamsKind::None => true, 93 | PathParamsKind::Params(params) => params.is_empty(), 94 | } 95 | } 96 | 97 | /// Returns the number of path parameters 98 | #[inline] 99 | pub fn len(&self) -> usize { 100 | match &self.kind { 101 | PathParamsKind::None => 0, 102 | PathParamsKind::Params(params) => params.len(), 103 | } 104 | } 105 | 106 | /// Gets the value of a path parameter by its name 107 | /// Returns None if the parameter doesn't exist 108 | #[inline] 109 | pub fn get(&self, key: impl AsRef) -> Option<&'req str> { 110 | match &self.kind { 111 | PathParamsKind::Params(params) => params.get(key), 112 | PathParamsKind::None => None, 113 | } 114 | } 115 | } 116 | 117 | // Implementation of From trait to convert from Params to PathParams 118 | impl<'server, 'req> From> for PathParams<'server, 'req> { 119 | fn from(params: Params<'server, 'req>) -> Self { 120 | PathParams::new(params) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/web/src/responder.rs: -------------------------------------------------------------------------------- 1 | //! Response handling module that converts handler results into HTTP responses. 2 | //! 3 | //! This module provides the [`Responder`] trait which defines how different types 4 | //! can be converted into HTTP responses. It includes implementations for common types 5 | //! like Result, Option, String, etc. 6 | //! 7 | //! The [`Responder`] trait is a key part of the response pipeline, allowing handler 8 | //! return values to be automatically converted into proper HTTP responses. 9 | 10 | pub mod sse; 11 | 12 | use crate::RequestContext; 13 | use crate::body::ResponseBody; 14 | use http::{HeaderValue, Response, StatusCode}; 15 | use std::convert::Infallible; 16 | 17 | /// A trait for types that can be converted into HTTP responses. 18 | /// 19 | /// Types implementing this trait can be returned directly from request handlers 20 | /// and will be automatically converted into HTTP responses. 21 | pub trait Responder { 22 | fn response_to(self, req: &RequestContext) -> Response; 23 | } 24 | 25 | /// Implementation for Result allows handlers to return Result types directly. 26 | /// The Ok and Err variants must both implement Responder. 27 | impl Responder for Result { 28 | fn response_to(self, req: &RequestContext) -> Response { 29 | match self { 30 | Ok(t) => t.response_to(req), 31 | Err(e) => e.response_to(req), 32 | } 33 | } 34 | } 35 | 36 | /// Implementation for Option allows handlers to return Option types. 37 | /// None case returns an empty response. 38 | impl Responder for Option { 39 | fn response_to(self, req: &RequestContext) -> Response { 40 | match self { 41 | Some(t) => t.response_to(req), 42 | None => Response::new(ResponseBody::empty()), 43 | } 44 | } 45 | } 46 | 47 | /// Implementation for Response allows passing through pre-built responses. 48 | /// The response body is converted to the internal ResponseBody type. 49 | impl Responder for Response 50 | where 51 | B: Into, 52 | { 53 | fn response_to(self, _req: &RequestContext) -> Response { 54 | self.map(|b| b.into()) 55 | } 56 | } 57 | 58 | /// Implementation for (StatusCode, T) tuple allows setting a status code 59 | /// along with the response content. 60 | impl Responder for (StatusCode, T) { 61 | fn response_to(self, req: &RequestContext) -> Response { 62 | let (status, responder) = self; 63 | let mut response = responder.response_to(req); 64 | *response.status_mut() = status; 65 | response 66 | } 67 | } 68 | 69 | /// Implementation for (T, StatusCode) tuple - same as above but with reversed order. 70 | impl Responder for (T, StatusCode) { 71 | fn response_to(self, req: &RequestContext) -> Response { 72 | let (responder, status) = self; 73 | (status, responder).response_to(req) 74 | } 75 | } 76 | 77 | /// Implementation for Box allows boxing responders. 78 | impl Responder for Box { 79 | fn response_to(self, req: &RequestContext) -> Response { 80 | (*self).response_to(req) 81 | } 82 | } 83 | 84 | /// Implementation for unit type () returns an empty response. 85 | impl Responder for () { 86 | fn response_to(self, _req: &RequestContext) -> Response { 87 | Response::new(ResponseBody::empty()) 88 | } 89 | } 90 | 91 | const TEXT_PLAIN_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("text/plain; charset=utf-8"); 92 | 93 | /// Implementation for static strings returns them as plain text responses. 94 | impl Responder for &'static str { 95 | fn response_to(self, _req: &RequestContext) -> Response { 96 | let mut builder = Response::builder(); 97 | let headers = builder.headers_mut().unwrap(); 98 | headers.reserve(16); 99 | headers.insert(http::header::CONTENT_TYPE, TEXT_PLAIN_CONTENT_TYPE); 100 | 101 | builder.status(StatusCode::OK).body(ResponseBody::from(self)).unwrap() 102 | } 103 | } 104 | 105 | /// Implementation for String returns it as a plain text response. 106 | impl Responder for String { 107 | fn response_to(self, _req: &RequestContext) -> Response { 108 | let mut builder = Response::builder(); 109 | let headers = builder.headers_mut().unwrap(); 110 | headers.reserve(16); 111 | headers.insert(http::header::CONTENT_TYPE, TEXT_PLAIN_CONTENT_TYPE); 112 | builder.status(StatusCode::OK).body(ResponseBody::from(self)).unwrap() 113 | } 114 | } 115 | 116 | impl Responder for Infallible { 117 | fn response_to(self, _req: &RequestContext) -> Response { 118 | unreachable!() 119 | } 120 | } 121 | 122 | pub struct NotFound; 123 | 124 | impl Responder for NotFound { 125 | #[inline] 126 | fn response_to(self, req: &RequestContext) -> Response { 127 | ("404 Not Found.", StatusCode::NOT_FOUND).response_to(req) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /crates/web/src/responder/sse.rs: -------------------------------------------------------------------------------- 1 | use crate::responder::Responder; 2 | use crate::{RequestContext, ResponseBody}; 3 | use bytes::Bytes; 4 | use futures::channel::mpsc::{SendError, channel}; 5 | use futures::{Sink, SinkExt, Stream, StreamExt}; 6 | use http::{HeaderValue, Response, StatusCode}; 7 | use http_body::Frame; 8 | use http_body_util::StreamBody; 9 | use std::time::Duration; 10 | 11 | pub struct SseStream { 12 | stream: S, 13 | } 14 | 15 | pub struct SseEmitter { 16 | sink: S, 17 | } 18 | 19 | impl SseStream 20 | where 21 | S: Stream, 22 | { 23 | fn new(stream: S) -> Self { 24 | SseStream { stream } 25 | } 26 | } 27 | 28 | impl SseEmitter 29 | where 30 | S: Sink, 31 | { 32 | fn new(sink: S) -> Self { 33 | SseEmitter { sink } 34 | } 35 | } 36 | 37 | impl SseEmitter 38 | where 39 | S: Sink + Unpin, 40 | { 41 | pub async fn send(&mut self, event: Event) -> Result<(), SendError> { 42 | self.sink.send(event).await 43 | } 44 | 45 | pub async fn close(&mut self) -> Result<(), SendError> { 46 | self.sink.close().await 47 | } 48 | } 49 | 50 | pub fn build_sse_stream_emitter(buffer: usize) -> (SseStream>, SseEmitter>) { 51 | let (sender, receiver) = channel::(buffer); 52 | (SseStream::new(receiver), SseEmitter::new(sender)) 53 | } 54 | 55 | pub enum Event { 56 | Retry(Duration), 57 | Message(Message), 58 | } 59 | 60 | pub struct Message { 61 | // https://html.spec.whatwg.org/multipage/server-sent-events.html#concept-event-stream-last-event-id 62 | pub id: Option, 63 | pub name: Option, 64 | // the message data 65 | pub data: String, 66 | } 67 | 68 | impl Event { 69 | pub fn message(data: String, id: Option, name: Option) -> Event { 70 | Event::Message(Message { id, name, data }) 71 | } 72 | 73 | pub fn from_data(data: String) -> Event { 74 | Event::Message(Message { id: None, name: None, data }) 75 | } 76 | 77 | pub fn retry(duration: impl Into) -> Event { 78 | Event::Retry(duration.into()) 79 | } 80 | } 81 | 82 | impl Responder for SseStream 83 | where 84 | S: Stream + Send + 'static, 85 | { 86 | fn response_to(self, _req: &RequestContext) -> Response { 87 | let mut builder = Response::builder(); 88 | let headers = builder.headers_mut().unwrap(); 89 | headers.reserve(16); 90 | headers.insert(http::header::CONTENT_TYPE, mime::TEXT_EVENT_STREAM.as_ref().parse().unwrap()); 91 | headers.insert(http::header::CACHE_CONTROL, HeaderValue::from_static("no-cache")); 92 | headers.insert(http::header::CONNECTION, HeaderValue::from_static("keep-alive")); 93 | 94 | let event_stream = self.stream.map(|event| match event { 95 | Event::Message(Message { id, name, data }) => { 96 | let mut string = String::with_capacity(data.len()); 97 | 98 | if let Some(i) = id { 99 | string.push_str(&format!("id: {}\n", i)); 100 | } 101 | 102 | if let Some(n) = name { 103 | string.push_str(&format!("event: {}\n", n)); 104 | } 105 | 106 | let split = data.lines(); 107 | 108 | for s in split { 109 | string.push_str(&format!("data: {}\n", s)); 110 | } 111 | 112 | string.push('\n'); 113 | Ok(Frame::data(Bytes::from(string))) 114 | } 115 | Event::Retry(duration) => Ok(Frame::data(Bytes::from(format!("retry: {}\n\n", duration.as_millis())))), 116 | }); 117 | 118 | let stream_body = StreamBody::new(event_stream); 119 | 120 | builder.status(StatusCode::OK).body(ResponseBody::stream(stream_body)).unwrap() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/web/src/router/filter.rs: -------------------------------------------------------------------------------- 1 | //! Request filtering module that provides composable request filters. 2 | //! 3 | //! This module implements a filter system that allows you to: 4 | //! - Filter requests based on HTTP methods 5 | //! - Filter requests based on headers 6 | //! - Combine multiple filters using AND/OR logic 7 | //! - Create custom filters using closures 8 | //! 9 | //! ## Thread Safety 10 | //! 11 | //! All filters must implement the `Filter` trait, which requires `Send + Sync`. 12 | //! This ensures that filters can be safely shared and used across threads, 13 | //! which is essential for concurrent request handling in a web server environment. 14 | //! 15 | //! # Examples 16 | //! 17 | //! ``` 18 | //! use micro_web::router::filter::{all_filter, any_filter, get_method, header}; 19 | //! 20 | //! // Create a filter that matches GET requests 21 | //! let get_filter = get_method(); 22 | //! 23 | //! // Create a filter that checks for specific header 24 | //! let auth_filter = header("Authorization", "Bearer token"); 25 | //! 26 | //! // Combine filters with AND logic 27 | //! let mut combined = all_filter(); 28 | //! combined.and(get_filter).and(auth_filter); 29 | //! ``` 30 | 31 | use crate::RequestContext; 32 | use http::{HeaderName, HeaderValue, Method}; 33 | 34 | /// Core trait for request filtering. 35 | /// 36 | /// Implementors of this trait can be used to filter HTTP requests 37 | /// based on custom logic. Filters can be composed using [`AllFilter`] 38 | /// and [`AnyFilter`]. 39 | /// 40 | /// 41 | /// The `Filter` trait requires `Send + Sync`, ensuring that filters 42 | /// can be safely used in a multithreaded environment. 43 | pub trait Filter: Send + Sync { 44 | /// Check if the request matches this filter's criteria. 45 | /// 46 | /// Returns `true` if the request should be allowed, `false` otherwise. 47 | fn matches(&self, req: &RequestContext) -> bool; 48 | } 49 | 50 | /// A filter that wraps a closure. 51 | struct FnFilter bool>(F); 52 | 53 | impl bool + Send + Sync> Filter for FnFilter { 54 | fn matches(&self, req: &RequestContext) -> bool { 55 | (self.0)(req) 56 | } 57 | } 58 | 59 | /// Creates a new filter from a closure. 60 | /// 61 | /// This allows creating custom filters using simple closures. 62 | /// 63 | /// # Example 64 | /// ``` 65 | /// use micro_web::router::filter::filter_fn; 66 | /// 67 | /// let custom_filter = filter_fn(|req| { 68 | /// req.uri().path().starts_with("/api") 69 | /// }); 70 | /// ``` 71 | pub fn filter_fn(f: F) -> impl Filter 72 | where 73 | F: Fn(&RequestContext) -> bool + Send + Sync, 74 | { 75 | FnFilter(f) 76 | } 77 | 78 | /// Creates a filter that always returns true. 79 | #[inline(always)] 80 | pub fn true_filter() -> TrueFilter { 81 | TrueFilter 82 | } 83 | 84 | /// Creates a filter that always returns false. 85 | #[inline(always)] 86 | pub fn false_filter() -> FalseFilter { 87 | FalseFilter 88 | } 89 | 90 | /// A filter that always returns true. 91 | pub struct TrueFilter; 92 | impl Filter for TrueFilter { 93 | #[inline(always)] 94 | fn matches(&self, _req: &RequestContext) -> bool { 95 | true 96 | } 97 | } 98 | 99 | /// A filter that always returns false. 100 | pub struct FalseFilter; 101 | impl Filter for FalseFilter { 102 | #[inline(always)] 103 | fn matches(&self, _req: &RequestContext) -> bool { 104 | false 105 | } 106 | } 107 | 108 | /// Creates a new OR-composed filter chain. 109 | pub fn any_filter() -> AnyFilter { 110 | AnyFilter::new() 111 | } 112 | 113 | /// Compose filters with OR logic. 114 | /// 115 | /// If any inner filter succeeds, the whole filter succeeds. 116 | /// An empty filter chain returns true by default. 117 | pub struct AnyFilter { 118 | filters: Vec>, 119 | } 120 | 121 | impl AnyFilter { 122 | fn new() -> Self { 123 | Self { filters: vec![] } 124 | } 125 | 126 | /// Add a new filter to the OR chain. 127 | pub fn or(&mut self, filter: F) -> &mut Self { 128 | self.filters.push(Box::new(filter)); 129 | self 130 | } 131 | } 132 | 133 | impl Filter for AnyFilter { 134 | fn matches(&self, req: &RequestContext) -> bool { 135 | if self.filters.is_empty() { 136 | return true; 137 | } 138 | 139 | for filter in &self.filters { 140 | if filter.matches(req) { 141 | return true; 142 | } 143 | } 144 | 145 | false 146 | } 147 | } 148 | 149 | /// Creates a new AND-composed filter chain. 150 | pub fn all_filter() -> AllFilter { 151 | AllFilter::new() 152 | } 153 | 154 | /// Compose filters with AND logic. 155 | /// 156 | /// All inner filters must succeed for the whole filter to succeed. 157 | /// An empty filter chain returns true by default. 158 | pub struct AllFilter { 159 | filters: Vec>, 160 | } 161 | 162 | impl AllFilter { 163 | fn new() -> Self { 164 | Self { filters: vec![] } 165 | } 166 | 167 | /// Add a new filter to the AND chain. 168 | pub fn and(&mut self, filter: F) -> &mut Self { 169 | self.filters.push(Box::new(filter)); 170 | self 171 | } 172 | } 173 | 174 | impl Filter for AllFilter { 175 | fn matches(&self, req: &RequestContext) -> bool { 176 | if self.filters.is_empty() { 177 | return true; 178 | } 179 | 180 | for filter in &self.filters { 181 | if !filter.matches(req) { 182 | return false; 183 | } 184 | } 185 | 186 | true 187 | } 188 | } 189 | 190 | /// A filter that matches HTTP methods. 191 | pub struct MethodFilter(Method); 192 | 193 | impl Filter for MethodFilter { 194 | fn matches(&self, req: &RequestContext) -> bool { 195 | self.0.eq(req.method()) 196 | } 197 | } 198 | 199 | macro_rules! method_filter { 200 | ($method:ident, $upper_case_method:ident) => { 201 | #[doc = concat!("Creates a filter that matches HTTP ", stringify!($upper_case_method), " requests.")] 202 | #[inline] 203 | pub fn $method() -> MethodFilter { 204 | MethodFilter(Method::$upper_case_method) 205 | } 206 | }; 207 | } 208 | 209 | method_filter!(get_method, GET); 210 | method_filter!(post_method, POST); 211 | method_filter!(put_method, PUT); 212 | method_filter!(delete_method, DELETE); 213 | method_filter!(head_method, HEAD); 214 | method_filter!(options_method, OPTIONS); 215 | method_filter!(connect_method, CONNECT); 216 | method_filter!(patch_method, PATCH); 217 | method_filter!(trace_method, TRACE); 218 | 219 | /// Creates a filter that matches a specific header name and value. 220 | #[inline] 221 | pub fn header(header_name: K, header_value: V) -> HeaderFilter 222 | where 223 | HeaderName: TryFrom, 224 | >::Error: Into, 225 | HeaderValue: TryFrom, 226 | >::Error: Into, 227 | { 228 | // TODO: need to process the unwrap 229 | let name = >::try_from(header_name).map_err(Into::into).unwrap(); 230 | let value = >::try_from(header_value).map_err(Into::into).unwrap(); 231 | HeaderFilter(name, value) 232 | } 233 | 234 | /// A filter that matches HTTP headers. 235 | pub struct HeaderFilter(HeaderName, HeaderValue); 236 | 237 | impl Filter for HeaderFilter { 238 | fn matches(&self, req: &RequestContext) -> bool { 239 | let value_option = req.headers().get(&self.0); 240 | value_option.map(|value| self.1.eq(value)).unwrap_or(false) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /crates/web/src/router/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod filter; 2 | 3 | use crate::PathParams; 4 | use crate::handler::RequestHandler; 5 | 6 | use crate::handler::handler_decorator::HandlerDecorator; 7 | use crate::handler::handler_decorator_factory::{ 8 | HandlerDecoratorFactory, HandlerDecoratorFactoryComposer, HandlerDecoratorFactoryExt, IdentityHandlerDecoratorFactory, 9 | }; 10 | use filter::{AllFilter, Filter}; 11 | use std::collections::HashMap; 12 | use tracing::error; 13 | 14 | type RouterFilter = dyn Filter + Send + Sync + 'static; 15 | type InnerRouter = matchit::Router; 16 | 17 | /// Main router structure that handles HTTP request routing 18 | pub struct Router { 19 | inner_router: InnerRouter>, 20 | } 21 | 22 | /// A router item containing a filter and handler 23 | pub struct RouterItem { 24 | filter: Box, 25 | handler: Box, 26 | } 27 | 28 | /// Result of matching a route, containing matched items and path parameters 29 | pub struct RouteResult<'router, 'req> { 30 | router_items: &'router [RouterItem], 31 | params: PathParams<'router, 'req>, 32 | } 33 | 34 | impl Router { 35 | /// Creates a new router builder with default wrappers 36 | pub fn builder() -> RouterBuilder { 37 | RouterBuilder::new() 38 | } 39 | 40 | /// Matches a path against the router's routes 41 | /// 42 | /// Returns a `RouteResult` containing matched handlers and path parameters 43 | /// 44 | /// # Arguments 45 | /// * `path` - The path to match against 46 | pub fn at<'router, 'req>(&'router self, path: &'req str) -> RouteResult<'router, 'req> { 47 | self.inner_router 48 | .at(path) 49 | .map(|matched| RouteResult { router_items: matched.value.as_slice(), params: matched.params.into() }) 50 | .map_err(|e| error!("match '{}' error: {}", path, e)) 51 | .unwrap_or(RouteResult::empty()) 52 | } 53 | } 54 | 55 | impl RouterItem { 56 | /// Gets the filter for this router item 57 | pub fn filter(&self) -> &RouterFilter { 58 | self.filter.as_ref() 59 | } 60 | 61 | /// Gets the request handler for this router item 62 | pub fn handler(&self) -> &dyn RequestHandler { 63 | self.handler.as_ref() 64 | } 65 | } 66 | 67 | impl<'router, 'req> RouteResult<'router, 'req> { 68 | fn empty() -> Self { 69 | Self { router_items: &[], params: PathParams::empty() } 70 | } 71 | 72 | /// Returns true if no routes were matched 73 | #[inline] 74 | pub fn is_empty(&self) -> bool { 75 | self.router_items.is_empty() 76 | } 77 | 78 | /// Gets the path parameters from the matched route 79 | pub fn params(&self) -> &PathParams<'router, 'req> { 80 | &self.params 81 | } 82 | 83 | /// Gets the matched router items 84 | pub fn router_items(&self) -> &'router [RouterItem] { 85 | self.router_items 86 | } 87 | } 88 | 89 | pub struct RouterBuilder { 90 | data: HashMap>, 91 | decorator_factory: DF, 92 | } 93 | 94 | impl RouterBuilder { 95 | fn new() -> Self { 96 | Self { data: HashMap::new(), decorator_factory: IdentityHandlerDecoratorFactory } 97 | } 98 | } 99 | impl RouterBuilder { 100 | pub fn route(mut self, route: impl Into, item_builder: RouterItemBuilder) -> Self { 101 | let vec = self.data.entry(route.into()).or_default(); 102 | vec.push(item_builder); 103 | self 104 | } 105 | 106 | pub fn with_global_decorator(self, factory: DF2) -> RouterBuilder> 107 | where 108 | DF: HandlerDecoratorFactory, 109 | DF2: HandlerDecoratorFactory, 110 | { 111 | RouterBuilder { data: self.data, decorator_factory: self.decorator_factory.and_then(factory) } 112 | } 113 | 114 | /// Builds the router from the accumulated routes and wrappers 115 | pub fn build(self) -> Router 116 | where 117 | DF: HandlerDecoratorFactory, 118 | { 119 | let mut inner_router = InnerRouter::new(); 120 | 121 | for (path, items) in self.data.into_iter() { 122 | let router_items = items 123 | .into_iter() 124 | .map(|item_builder| item_builder.build()) 125 | .map(|item| { 126 | let decorator = self.decorator_factory.create_decorator(); 127 | let handler = decorator.decorate(item.handler); 128 | RouterItem { handler: Box::new(handler), ..item } 129 | }) 130 | .collect::>(); 131 | 132 | inner_router.insert(path, router_items).unwrap(); 133 | } 134 | 135 | Router { inner_router } 136 | } 137 | } 138 | 139 | macro_rules! method_router_filter { 140 | ($method:ident, $method_name:ident) => { 141 | pub fn $method(handler: H) -> RouterItemBuilder { 142 | let mut filters = filter::all_filter(); 143 | filters.and(filter::$method_name()); 144 | RouterItemBuilder { filters, handler: Box::new(handler) } 145 | } 146 | }; 147 | } 148 | 149 | method_router_filter!(get, get_method); 150 | method_router_filter!(post, post_method); 151 | method_router_filter!(put, put_method); 152 | method_router_filter!(delete, delete_method); 153 | method_router_filter!(head, head_method); 154 | method_router_filter!(options, options_method); 155 | method_router_filter!(connect, connect_method); 156 | method_router_filter!(patch, patch_method); 157 | method_router_filter!(trace, trace_method); 158 | 159 | pub struct RouterItemBuilder { 160 | filters: AllFilter, 161 | handler: Box, 162 | } 163 | 164 | impl RouterItemBuilder { 165 | pub fn with(mut self, filter: F) -> Self { 166 | self.filters.and(filter); 167 | self 168 | } 169 | 170 | fn build(self) -> RouterItem { 171 | // todo: we can remove indirect when filters has only one filter 172 | RouterItem { filter: Box::new(self.filters), handler: self.handler } 173 | } 174 | } 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use super::filter::header; 179 | use super::{Router, get, post}; 180 | use crate::{PathParams, RequestContext, handler_fn}; 181 | use http::{HeaderValue, Method, Request}; 182 | use micro_http::protocol::RequestHeader; 183 | 184 | async fn simple_get_1(_method: &Method) -> String { 185 | "hello world".into() 186 | } 187 | 188 | async fn simple_get_2(_method: &Method) -> String { 189 | "hello world".into() 190 | } 191 | 192 | fn router() -> Router { 193 | Router::builder() 194 | .route("/", get(handler_fn(simple_get_1))) 195 | .route( 196 | "/", 197 | post(handler_fn(simple_get_1)).with(header( 198 | http::header::CONTENT_TYPE, 199 | HeaderValue::from_str(mime::APPLICATION_WWW_FORM_URLENCODED.as_ref()).unwrap(), 200 | )), 201 | ) 202 | .route("/", post(handler_fn(simple_get_1))) 203 | .route("/2", get(handler_fn(simple_get_2))) 204 | .build() 205 | } 206 | 207 | #[test] 208 | fn test_route_get() { 209 | let router = router(); 210 | let route_result = router.at("/"); 211 | 212 | assert_eq!(route_result.params.len(), 0); 213 | 214 | let items = route_result.router_items; 215 | assert_eq!(items.len(), 3); 216 | 217 | let header: RequestHeader = Request::builder().method(Method::GET).body(()).unwrap().into_parts().0.into(); 218 | let params = PathParams::empty(); 219 | let req_ctx = RequestContext::new(&header, ¶ms); 220 | 221 | assert!(items[0].filter.matches(&req_ctx)); 222 | assert!(!items[1].filter.matches(&req_ctx)); 223 | assert!(!items[2].filter.matches(&req_ctx)); 224 | } 225 | 226 | #[test] 227 | fn test_route_post() { 228 | let router = router(); 229 | let route_result = router.at("/"); 230 | 231 | assert_eq!(route_result.params.len(), 0); 232 | 233 | let items = route_result.router_items; 234 | assert_eq!(items.len(), 3); 235 | 236 | let header: RequestHeader = Request::builder().method(Method::POST).body(()).unwrap().into_parts().0.into(); 237 | let params = PathParams::empty(); 238 | let req_ctx = RequestContext::new(&header, ¶ms); 239 | 240 | assert!(!items[0].filter.matches(&req_ctx)); 241 | assert!(!items[1].filter.matches(&req_ctx)); 242 | assert!(items[2].filter.matches(&req_ctx)); 243 | } 244 | 245 | #[test] 246 | fn test_route_post_with_content_type() { 247 | let router = router(); 248 | let route_result = router.at("/"); 249 | 250 | assert_eq!(route_result.params.len(), 0); 251 | 252 | let items = route_result.router_items; 253 | assert_eq!(items.len(), 3); 254 | 255 | let header: RequestHeader = Request::builder() 256 | .method(Method::POST) 257 | .header(http::header::CONTENT_TYPE, "application/x-www-form-urlencoded") 258 | .body(()) 259 | .unwrap() 260 | .into_parts() 261 | .0 262 | .into(); 263 | let params = PathParams::empty(); 264 | let req_ctx = RequestContext::new(&header, ¶ms); 265 | 266 | assert!(!items[0].filter.matches(&req_ctx)); 267 | assert!(items[1].filter.matches(&req_ctx)); 268 | assert!(items[2].filter.matches(&req_ctx)); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /crates/web/src/server.rs: -------------------------------------------------------------------------------- 1 | //! Server module for handling HTTP requests and managing web server lifecycle. 2 | //! 3 | //! This module provides the core server functionality including: 4 | //! - Server builder pattern for configuration 5 | //! - HTTP request routing and handling 6 | //! - Connection management and error handling 7 | //! - Default request handling 8 | //! 9 | //! # Examples 10 | //! 11 | //! ```no_run 12 | //! use micro_web::{Server, router::{Router, get}, handler_fn}; 13 | //! 14 | //! async fn hello_world() -> &'static str { 15 | //! "Hello, World!" 16 | //! } 17 | //! 18 | //! #[tokio::main] 19 | //! async fn main() { 20 | //! let router = Router::builder() 21 | //! .route("/", get(handler_fn(hello_world))) 22 | //! .build(); 23 | //! 24 | //! Server::builder() 25 | //! .router(router) 26 | //! .bind("127.0.0.1:3000") 27 | //! .build() 28 | //! .unwrap() 29 | //! .start() 30 | //! .await; 31 | //! } 32 | //! ``` 33 | 34 | use crate::handler::RequestHandler; 35 | use crate::router::Router; 36 | use crate::{OptionReqBody, RequestContext, ResponseBody, handler_fn}; 37 | use http::{Request, Response, StatusCode}; 38 | use micro_http::connection::HttpConnection; 39 | use micro_http::handler::Handler; 40 | use micro_http::protocol::RequestHeader; 41 | use micro_http::protocol::body::ReqBody; 42 | use std::error::Error; 43 | use std::net::{SocketAddr, ToSocketAddrs}; 44 | use std::sync::Arc; 45 | use thiserror::Error; 46 | use tokio::net::TcpListener; 47 | use tracing::{Level, error, info, warn}; 48 | use tracing_subscriber::FmtSubscriber; 49 | 50 | /// Builder for configuring and constructing a [`Server`] instance. 51 | /// 52 | /// The builder provides a fluent API for setting server options including: 53 | /// - Binding address 54 | /// - Request router 55 | /// - Default request handler 56 | pub struct ServerBuilder { 57 | router: Option, 58 | default_handler: Option>, 59 | address: Option>, 60 | } 61 | 62 | impl ServerBuilder { 63 | fn new() -> Self { 64 | Self { router: None, default_handler: None, address: None } 65 | } 66 | 67 | pub fn bind(mut self, address: A) -> Self { 68 | self.address = Some(address.to_socket_addrs().unwrap().collect::>()); 69 | self 70 | } 71 | 72 | pub fn router(mut self, router: Router) -> Self { 73 | self.router = Some(router); 74 | self 75 | } 76 | 77 | pub fn default_handler(mut self, request_handler: impl RequestHandler + 'static) -> Self { 78 | self.default_handler = Some(Box::new(request_handler)); 79 | self 80 | } 81 | 82 | pub fn build(self) -> Result { 83 | let new_builder = if self.default_handler.is_none() { self.default_handler(handler_fn(default_handler)) } else { self }; 84 | let router = new_builder.router.ok_or(ServerBuildError::MissingRouter)?; 85 | let address = new_builder.address.ok_or(ServerBuildError::MissingAddress)?; 86 | 87 | // unwrap is safe here because we set it in the new_builder 88 | Ok(Server { router, default_handler: new_builder.default_handler.unwrap(), address }) 89 | } 90 | } 91 | 92 | async fn default_handler() -> (StatusCode, &'static str) { 93 | (StatusCode::NOT_FOUND, "404 Not Found") 94 | } 95 | 96 | /// Core server implementation that processes HTTP requests. 97 | /// 98 | /// The server is responsible for: 99 | /// - Listening for incoming connections 100 | /// - Routing requests to appropriate handlers 101 | /// - Managing connection lifecycle 102 | /// - Error handling and logging 103 | /// 104 | pub struct Server { 105 | router: Router, 106 | default_handler: Box, 107 | address: Vec, 108 | } 109 | 110 | /// Errors that can occur during server construction. 111 | #[derive(Error, Debug)] 112 | pub enum ServerBuildError { 113 | /// Router was not configured 114 | #[error("router must be set")] 115 | MissingRouter, 116 | 117 | /// Bind address was not configured 118 | #[error("address must be set")] 119 | MissingAddress, 120 | } 121 | 122 | impl Server { 123 | pub fn builder() -> ServerBuilder { 124 | ServerBuilder::new() 125 | } 126 | 127 | pub async fn start(self) { 128 | let subscriber = FmtSubscriber::builder().with_max_level(Level::WARN).finish(); 129 | tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 130 | 131 | info!("start listening at {:?}", self.address); 132 | let tcp_listener = match TcpListener::bind(self.address.as_slice()).await { 133 | Ok(tcp_listener) => tcp_listener, 134 | Err(e) => { 135 | error!(cause = %e, "bind server error"); 136 | return; 137 | } 138 | }; 139 | 140 | let handler = Arc::new(self); 141 | loop { 142 | let (tcp_stream, _remote_addr) = tokio::select! { 143 | _ = tokio::signal::ctrl_c() => { break; }, 144 | result = tcp_listener.accept() => { 145 | match result { 146 | Ok(stream_and_addr) => stream_and_addr, 147 | Err(e) => { 148 | warn!(cause = %e, "failed to accept"); 149 | continue; 150 | } 151 | } 152 | } 153 | }; 154 | 155 | let handler = handler.clone(); 156 | 157 | tokio::spawn(async move { 158 | let (reader, writer) = tcp_stream.into_split(); 159 | let connection = HttpConnection::new(reader, writer); 160 | match connection.process(handler).await { 161 | Ok(_) => { 162 | info!("finished process, connection shutdown"); 163 | } 164 | Err(e) => { 165 | error!("service has error, cause {}, connection shutdown", e); 166 | } 167 | } 168 | }); 169 | } 170 | } 171 | } 172 | 173 | impl Handler for Server { 174 | type RespBody = ResponseBody; 175 | type Error = Box; 176 | 177 | async fn call(&self, req: Request) -> Result, Self::Error> { 178 | let (parts, body) = req.into_parts(); 179 | let header = RequestHeader::from(parts); 180 | // TODO: insignificant memory allocate 181 | let req_body = OptionReqBody::from(body); 182 | 183 | let path = header.uri().path(); 184 | let route_result = self.router.at(path); 185 | 186 | let mut request_context = RequestContext::new(&header, route_result.params()); 187 | 188 | let handler = route_result 189 | .router_items() 190 | .iter() 191 | .filter(|item| item.filter().matches(&request_context)) 192 | .map(|item| item.handler()) 193 | .take(1) 194 | .next() 195 | .unwrap_or(self.default_handler.as_ref()); 196 | 197 | let response = handler.invoke(&mut request_context, req_body).await; 198 | Ok(response) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /deploy.md: -------------------------------------------------------------------------------- 1 | # Publishing Guide for micro-http Workspace 2 | 3 | This guide details the process of publishing the micro-http workspace packages to crates.io. 4 | 5 | ## Prerequisites 6 | 7 | ### 1. Install cargo-workspaces 8 | 9 | `cargo-workspaces` is a powerful tool for managing Rust workspace versioning. Install it using: 10 | 11 | ```bash 12 | cargo install cargo-workspaces 13 | ``` 14 | 15 | Verify installation: 16 | ```bash 17 | cargo ws --version 18 | ``` 19 | 20 | ### 2. Crates.io Authentication 21 | 22 | Ensure you're authenticated with crates.io: 23 | ```bash 24 | cargo login 25 | ``` 26 | 27 | ## Pre-release Steps 28 | 29 | 1. **Code Quality Checks** 30 | ```bash 31 | # Run all tests 32 | cargo test 33 | 34 | # Run clippy for additional checks 35 | cargo clippy 36 | 37 | # Ensure formatting is correct 38 | cargo fmt --all -- --check 39 | ``` 40 | 41 | 2. **Workspace Status** 42 | ```bash 43 | # Check for changed crates since last release 44 | cargo ws changed 45 | 46 | # Review crates in publishing order 47 | cargo ws plan 48 | 49 | # Verify git status is clean 50 | git status 51 | ``` 52 | 53 | ## Publishing Process 54 | 55 | ### 1. Version Management 56 | 57 | Use cargo-workspaces to manage versions: 58 | ```bash 59 | # List current versions 60 | cargo ws list 61 | 62 | # Bump versions (replace with major/minor/patch) 63 | cargo ws version --no-git-push 64 | ``` 65 | 66 | ### 2. Review Publishing Order 67 | 68 | Check the publishing order of crates: 69 | ```bash 70 | cargo ws plan 71 | ``` 72 | 73 | This will show you the dependency order for publishing. Note this order for the next step. 74 | 75 | ### 3. Dry Run Publishing 76 | 77 | For each crate in the correct order (from cargo ws plan), perform a dry run: 78 | 79 | ```bash 80 | # Replace ${crate} with the crate name 81 | cargo publish --dry-run -p ${crate} 82 | ``` 83 | 84 | ### 4. Actual Publishing 85 | 86 | If all dry runs succeed, publish each crate in order: 87 | 88 | ```bash 89 | # Replace ${crate} with each crate name in order 90 | cargo publish -p ${crate} 91 | ``` 92 | 93 | Example publishing sequence (adjust according to your plan output): 94 | ```bash 95 | cargo publish -p micro-http 96 | cargo publish -p micro-web 97 | # ... other crates in order 98 | ``` 99 | 100 | ### 5. Git Management 101 | 102 | After successful publishing: 103 | ```bash 104 | # Push version changes 105 | git push origin main 106 | 107 | # Push tags 108 | git push origin --tags 109 | ``` 110 | 111 | ## Post-release Verification 112 | 113 | 1. **Package Verification** 114 | - Verify packages are listed on [crates.io](https://crates.io) 115 | - Check documentation generation on [docs.rs](https://docs.rs) 116 | - Ensure all published versions are accessible 117 | 118 | 2. **Version Verification** 119 | ```bash 120 | # Verify current versions 121 | cargo ws list 122 | ``` 123 | 124 | ## Troubleshooting 125 | 126 | ### Common Issues 127 | 128 | 1. **Publishing Failures** 129 | - Verify crates.io authentication 130 | - Check for dependency version conflicts 131 | - Ensure version numbers are unique 132 | - Validate API token permissions 133 | 134 | 2. **Version Conflicts** 135 | ```bash 136 | # Check current versions 137 | cargo ws list 138 | 139 | # Review publishing order 140 | cargo ws plan 141 | ``` 142 | 143 | 3. **Dependency Issues** 144 | - Ensure all dependencies are published 145 | - Check for yanked dependency versions 146 | - Verify compatibility of dependency versions 147 | 148 | ### Getting Help 149 | 150 | - [cargo-workspaces documentation](https://github.com/pksunkara/cargo-workspaces) 151 | - [crates.io documentation](https://doc.rust-lang.org/cargo/reference/publishing.html) 152 | - File issues on the project's GitHub repository 153 | 154 | ## Additional Commands 155 | 156 | Useful cargo-workspaces commands for version management: 157 | ```bash 158 | # List all workspace crates 159 | cargo ws list 160 | 161 | # View changed crates 162 | cargo ws changed 163 | 164 | # View publishing order 165 | cargo ws plan 166 | ``` 167 | 168 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # see: https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | 3 | max_width = 140 4 | use_small_heuristics = "Max" 5 | # use_small_heuristics = "Default" 6 | # hard_tabs = false 7 | # tab_spaces = 4 8 | # newline_style = "Auto" 9 | # indent_style = "Block" 10 | # use_small_heuristics = "Default" 11 | # fn_call_width = 60 12 | # attr_fn_like_width = 70 13 | # struct_lit_width = 18 14 | # struct_variant_width = 35 15 | # array_width = 60 16 | # chain_width = 60 17 | # single_line_if_else_max_width = 50 18 | # wrap_comments = false 19 | # format_code_in_doc_comments = false 20 | # doc_comment_code_block_width = 100 21 | # comment_width = 80 22 | # normalize_comments = false 23 | # normalize_doc_attributes = false 24 | # format_strings = false 25 | # format_macro_matchers = false 26 | # format_macro_bodies = true 27 | # hex_literal_case = "Preserve" 28 | # empty_item_single_line = true 29 | # struct_lit_single_line = true 30 | # fn_single_line = false 31 | # where_single_line = false 32 | # imports_indent = "Block" 33 | # imports_layout = "Mixed" 34 | # imports_granularity = "Preserve" 35 | # group_imports = "Preserve" 36 | # reorder_imports = true 37 | # reorder_modules = true 38 | # reorder_impl_items = false 39 | # type_punctuation_density = "Wide" 40 | # space_before_colon = false 41 | # space_after_colon = true 42 | # spaces_around_ranges = false 43 | # binop_separator = "Front" 44 | # remove_nested_parens = true 45 | # combine_control_expr = true 46 | # short_array_element_width_threshold = 10 47 | # overflow_delimited_expr = false 48 | # struct_field_align_threshold = 0 49 | # enum_discrim_align_threshold = 0 50 | # match_arm_blocks = true 51 | # match_arm_leading_pipes = "Never" 52 | # force_multiline_blocks = false 53 | # fn_args_layout = "Tall" 54 | # brace_style = "SameLineWhere" 55 | # control_brace_style = "AlwaysSameLine" 56 | # trailing_semicolon = true 57 | # trailing_comma = "Vertical" 58 | # match_block_trailing_comma = false 59 | # blank_lines_upper_bound = 1 60 | # blank_lines_lower_bound = 0 61 | edition = "2021" 62 | # version = "One" 63 | # inline_attribute_width = 0 64 | # format_generated_files = true 65 | # merge_derives = true 66 | # use_try_shorthand = false 67 | # use_field_init_shorthand = false 68 | # force_explicit_abi = true 69 | # condense_wildcard_suffixes = false 70 | # color = "Auto" 71 | # required_version = "1.5.1" 72 | # unstable_features = false 73 | # disable_all_formatting = false 74 | # skip_children = false 75 | # hide_parse_errors = false 76 | # error_on_line_overflow = false 77 | # error_on_unformatted = false 78 | # ignore = [] 79 | # emit_mode = "Files" 80 | # make_backup = false 81 | --------------------------------------------------------------------------------