├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── astra-web ├── .gitignore ├── Cargo.toml ├── examples │ └── hello_world.rs └── src │ ├── extract │ ├── mod.rs │ ├── parts.rs │ ├── path.rs │ └── state.rs │ ├── handler.rs │ ├── layer.rs │ ├── lib.rs │ ├── response.rs │ └── router.rs ├── examples ├── connection_info.rs ├── hello_world.rs ├── routing.rs └── state.rs └── src ├── executor.rs ├── http.rs ├── lib.rs ├── net.rs └── server.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Rust 4 | 5 | jobs: 6 | check: 7 | name: Cargo Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | 20 | test: 21 | name: Test Suite 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: nightly 29 | override: true 30 | - uses: actions-rs/cargo@v1 31 | with: 32 | command: test 33 | args: --all-targets --all-features 34 | 35 | doc: 36 | name: Documentation 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | toolchain: stable 44 | override: true 45 | - uses: actions-rs/cargo@v1 46 | with: 47 | command: test 48 | args: --doc 49 | 50 | fmt: 51 | name: Format 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - uses: actions-rs/toolchain@v1 56 | with: 57 | profile: minimal 58 | toolchain: stable 59 | override: true 60 | - run: rustup component add rustfmt 61 | - uses: actions-rs/cargo@v1 62 | with: 63 | command: fmt 64 | args: --all -- --check 65 | 66 | clippy: 67 | name: Clippy Lints 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v2 71 | - uses: actions-rs/toolchain@v1 72 | with: 73 | profile: minimal 74 | toolchain: nightly 75 | override: true 76 | - run: rustup component add clippy 77 | - uses: actions-rs/cargo@v1 78 | with: 79 | command: clippy 80 | args: --all-targets --all-features -- -Dclippy::all 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "astra" 3 | version = "0.4.0" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["Ibraheem Ahmed "] 7 | description = "A blocking HTTP server built on hyper." 8 | repository = "https://github.com/ibraheemdev/astra" 9 | keywords = ["http", "hyper", "web"] 10 | categories = ["network-programming", "web-programming::http-server"] 11 | 12 | [features] 13 | default = [] 14 | http2 = ["hyper/http2", "hyper-util/http2"] 15 | 16 | [dependencies] 17 | log = "0.4" 18 | hyper = { version = "1", features = ["http1", "server"] } 19 | hyper-util = { version = "0.1", features = ["http1", "server"] } 20 | mio = { version = "1", features = ["os-poll", "net"] } 21 | bytes = "1" 22 | http-body-util = "0.1" 23 | 24 | [dev-dependencies] 25 | matchit = "0.7.0" 26 | 27 | # [workspace] 28 | # members = ["astra-web"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ibraheem Ahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `astra` 2 | 3 | [crates.io](https://crates.io/crates/astra) 4 | [github](https://github.com/ibraheemdev/astra) 5 | [docs.rs](https://docs.rs/astra) 6 | 7 | A blocking HTTP server built on top of [`hyper`](https://github.com/hyperium/hyper). 8 | 9 | ```rust,no_run 10 | use astra::{Body, Response, Server}; 11 | 12 | fn main() { 13 | Server::bind("localhost:3000") 14 | .serve(|_req, _info| Response::new(Body::new("Hello World!"))) 15 | .expect("serve failed"); 16 | } 17 | ``` 18 | 19 | ## How Does It Work? 20 | 21 | Hyper is built on async I/O and depends on it to run correctly. To avoid depending on a large crate like Tokio, Astra runs a small evented I/O loop on a background thread and dispatches connections to it's own worker pool. The difference is that instead of tasks yielding to a userspace runtime like Tokio, they yield to the operating system. This means that request handlers can use standard I/O primitives without worrying about blocking the runtime: 22 | 23 | ```rust,no_run 24 | use astra::{Body, ResponseBuilder, Server}; 25 | use std::time::Duration; 26 | 27 | fn main() { 28 | Server::bind("localhost:3000") 29 | .serve(|_req, _info| { 30 | // Putting the worker thread to sleep will allow 31 | // other workers to run. 32 | std::thread::sleep(Duration::from_secs(1)); 33 | 34 | // Regular blocking I/O is fine too! 35 | let body = std::fs::read_to_string("index.html").unwrap(); 36 | 37 | ResponseBuilder::new() 38 | .header("Content-Type", "text/html") 39 | .body(Body::new(body)) 40 | .unwrap() 41 | }) 42 | .expect("serve failed"); 43 | } 44 | ``` 45 | 46 | ## Features 47 | 48 | Astra supports both HTTP/1 and HTTP/2 with most the configuration options that Hyper exposes. Features that depend on timers however, such as [`http2_keep_alive_while_idle`](https://docs.rs/hyper/latest/hyper/client/conn/http2/struct.Builder.html#method.keep_alive_while_idle), are currently unsupported. 49 | 50 | Astra is currently an HTTP *server* library only. The client API is unimplemented. 51 | 52 | ## Security 53 | 54 | One of the issues with blocking I/O is that it is susceptible to attacks such as [Slowloris](https://www.cloudflare.com/learning/ddos/ddos-attack-tools/slowloris/). Because of this, it is important to run your server behind an async reverse proxy such as Nginx. You were likely going to doing this anyways for TLS support, but it is something to keep in mind. 55 | 56 | ## But Is It Fast? 57 | 58 | Many of the references you'll find about thread-per-request performance are very outdated, often referencing bottlenecks from a time where [C10k](http://www.kegel.com/c10k.html) was peak scale. Since then, thread creation has gotten significantly cheaper, and context switching overhead has been reduced drastically. Modern OS schedulers are much better than they are given credit for, and it is now very feasible to serve upwards of tens of thousands of concurrent connections using blocking I/O. 59 | 60 | In naive "Hello World" style HTTP benchmarks, Astra is likely to lag behind Tokio. This is partly because Astra has to pay the cost of both threading *and* async I/O to be compatible with Hyper. However, as more work is done per request, especially pure blocking I/O, the difference diminishes. As always, you should measure your own use case, but Astra's performance may surprise you. 61 | 62 | That being said, one of Astra's main use cases is running a lightweight server with minimal dependencies, and avoiding the complexity that comes with async, so any potential performance tradeoffs might not be be relevant. 63 | -------------------------------------------------------------------------------- /astra-web/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /astra-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "astra-web" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | http2 = ["astra/http2"] 8 | 9 | [dependencies] 10 | astra = { version = "0.3.0", path = "../" } 11 | bytes = "1.5.0" 12 | http = "0.2.9" 13 | matchit = "0.7.2" 14 | serde = "1.0.192" 15 | -------------------------------------------------------------------------------- /astra-web/examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | use astra::{Request, Response}; 2 | use astra_web::{layer_fn, Next, Router}; 3 | use http::{Method, Version}; 4 | 5 | fn hello_world(_request: Request, _method: Method, _version: Version) -> &'static str { 6 | "Hello, World!" 7 | } 8 | 9 | fn get_foo() -> &'static str { 10 | "Foo." 11 | } 12 | 13 | fn create_foo() -> &'static str { 14 | "Created Foo." 15 | } 16 | 17 | fn main() { 18 | let router = Router::new() 19 | .get("/", hello_world) 20 | .nest( 21 | "/foo", 22 | Router::new() 23 | .get("/", get_foo) 24 | .get("/index", get_foo) 25 | .post("/new", create_foo), 26 | ) 27 | .layer(layer_fn(logger)); 28 | 29 | astra::Server::bind("localhost:3000") 30 | .serve(router.into_service()) 31 | .unwrap() 32 | } 33 | 34 | fn logger(request: Request, next: Next) -> Response { 35 | println!("{} {}", request.method(), request.uri()); 36 | next(request) 37 | } 38 | -------------------------------------------------------------------------------- /astra-web/src/extract/mod.rs: -------------------------------------------------------------------------------- 1 | mod parts; 2 | mod state; 3 | mod path; 4 | 5 | pub use state::{State, StateError}; 6 | 7 | use crate::IntoResponse; 8 | 9 | use astra::{Request, Response}; 10 | 11 | use std::convert::Infallible; 12 | 13 | pub trait FromRequest: Sized + Send + Sync + 'static { 14 | type Error: IntoResponse; 15 | 16 | fn from_request(request: &mut Request) -> Result; 17 | } 18 | 19 | impl FromRequest for () { 20 | type Error = Infallible; 21 | 22 | fn from_request(_: &mut Request) -> Result { 23 | Ok(()) 24 | } 25 | } 26 | 27 | impl FromRequest for Option { 28 | type Error = Infallible; 29 | 30 | fn from_request(request: &mut Request) -> Result { 31 | Ok(T::from_request(request).ok()) 32 | } 33 | } 34 | 35 | impl FromRequest for Result 36 | where 37 | T: FromRequest, 38 | T::Error: Send + Sync, 39 | { 40 | type Error = Infallible; 41 | 42 | fn from_request(request: &mut Request) -> Result { 43 | Ok(T::from_request(request)) 44 | } 45 | } 46 | 47 | pub trait RequestExt: Sized { 48 | fn extract(&mut self) -> Result 49 | where 50 | T: FromRequest; 51 | } 52 | 53 | impl RequestExt for Request { 54 | fn extract(&mut self) -> Result 55 | where 56 | T: FromRequest, 57 | { 58 | T::from_request(self) 59 | } 60 | } 61 | 62 | macro_rules! extract_tuples ({ $($param:ident)* } => { 63 | impl<$($param,)*> FromRequest for ($($param,)*) 64 | where 65 | $($param: FromRequest,)* 66 | { 67 | type Error = Response; 68 | 69 | #[inline] 70 | #[allow(non_snake_case, unused)] 71 | fn from_request(request: &mut Request) -> Result { 72 | $( 73 | let $param = match $param::from_request(request) { 74 | Ok(value) => value, 75 | Err(err) => return Err(err.into_response()) 76 | }; 77 | )* 78 | 79 | Ok(($($param,)*)) 80 | } 81 | } 82 | }); 83 | 84 | extract_tuples! { A } 85 | extract_tuples! { A B } 86 | extract_tuples! { A B C } 87 | extract_tuples! { A B C D } 88 | extract_tuples! { A B C D E } 89 | extract_tuples! { A B C D E F } 90 | extract_tuples! { A B C D E F G } 91 | extract_tuples! { A B C D E F G H } 92 | extract_tuples! { A B C D E F G H I } 93 | extract_tuples! { A B C D E F G H I J } 94 | extract_tuples! { A B C D E F G H I J K } 95 | extract_tuples! { A B C D E F G H I J K L } 96 | extract_tuples! { A B C D E F G H I J K L M } 97 | extract_tuples! { A B C D E F G H I J K L M N } 98 | extract_tuples! { A B C D E F G H I J K L M N O } 99 | extract_tuples! { A B C D E F G H I J K L M N O P } 100 | -------------------------------------------------------------------------------- /astra-web/src/extract/parts.rs: -------------------------------------------------------------------------------- 1 | use crate::FromRequest; 2 | use astra::Request; 3 | 4 | use std::convert::Infallible; 5 | 6 | use http::{HeaderMap, Method, Uri, Version}; 7 | 8 | impl FromRequest for Request { 9 | type Error = Infallible; 10 | 11 | fn from_request(request: &mut Request) -> Result { 12 | Ok(std::mem::take(request)) 13 | } 14 | } 15 | 16 | impl FromRequest for HeaderMap { 17 | type Error = Infallible; 18 | 19 | fn from_request(request: &mut Request) -> Result { 20 | Ok(request.headers().clone()) 21 | } 22 | } 23 | 24 | impl FromRequest for Method { 25 | type Error = Infallible; 26 | 27 | fn from_request(request: &mut Request) -> Result { 28 | Ok(request.method().clone()) 29 | } 30 | } 31 | 32 | impl FromRequest for Uri { 33 | type Error = Infallible; 34 | 35 | fn from_request(request: &mut Request) -> Result { 36 | Ok(request.uri().clone()) 37 | } 38 | } 39 | 40 | impl FromRequest for Version { 41 | type Error = Infallible; 42 | 43 | fn from_request(request: &mut Request) -> Result { 44 | Ok(request.version().clone()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /astra-web/src/extract/path.rs: -------------------------------------------------------------------------------- 1 | use astra::Body; 2 | use http::StatusCode; 3 | 4 | use crate::{FromRequest, IntoResponse}; 5 | 6 | #[derive(Debug)] 7 | pub struct Path(pub T); 8 | 9 | impl FromRequest for Path 10 | where 11 | T: Send + Sync + 'static, 12 | { 13 | type Error = PathError; 14 | 15 | fn from_request(_request: &mut astra::Request) -> Result { 16 | todo!() 17 | } 18 | } 19 | 20 | pub struct PathError {} 21 | 22 | impl IntoResponse for PathError { 23 | fn into_response(self) -> astra::Response { 24 | astra::ResponseBuilder::new() 25 | .status(StatusCode::BAD_REQUEST) 26 | .body(Body::empty()) 27 | .unwrap() 28 | } 29 | } 30 | 31 | impl std::ops::Deref for Path { 32 | type Target = T; 33 | 34 | fn deref(&self) -> &Self::Target { 35 | &self.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /astra-web/src/extract/state.rs: -------------------------------------------------------------------------------- 1 | use crate::{FromRequest, IntoResponse}; 2 | use astra::{Body, Response, ResponseBuilder}; 3 | use http::StatusCode; 4 | 5 | #[derive(Debug, Default, Clone, Copy)] 6 | pub struct State(T); 7 | 8 | impl FromRequest for State 9 | where 10 | T: Clone + Send + Sync + 'static, 11 | { 12 | type Error = StateError; 13 | 14 | fn from_request(request: &mut astra::Request) -> Result { 15 | request.extensions().get().cloned().ok_or(StateError) 16 | } 17 | } 18 | 19 | pub struct StateError; 20 | 21 | impl IntoResponse for StateError { 22 | fn into_response(self) -> Response { 23 | ResponseBuilder::new() 24 | .status(StatusCode::INTERNAL_SERVER_ERROR) 25 | .body(Body::empty()) 26 | .unwrap() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /astra-web/src/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{FromRequest, IntoResponse, Layer}; 2 | use astra::{Request, Response}; 3 | 4 | use std::marker::PhantomData; 5 | 6 | pub trait Handler: Send + Sync + 'static { 7 | type Response: IntoResponse; 8 | 9 | fn call(&self, request: Request) -> Self::Response; 10 | 11 | fn layer(self, layer: L) -> Layered 12 | where 13 | L: Layer, 14 | Self: Sized, 15 | { 16 | Layered { 17 | layer, 18 | handler: self, 19 | _r: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | pub struct Layered { 25 | layer: L, 26 | handler: H, 27 | _r: PhantomData, 28 | } 29 | 30 | impl Handler for Layered 31 | where 32 | H: Handler, 33 | T: FromRequest, 34 | W: Layer, 35 | { 36 | type Response = Response; 37 | 38 | fn call(&self, request: Request) -> Self::Response { 39 | let next = |req| self.handler.call(req).into_response(); 40 | self.layer.call(request, next) 41 | } 42 | } 43 | 44 | pub(crate) type Erased = Box>; 45 | 46 | impl Handler for Erased { 47 | type Response = Response; 48 | 49 | fn call(&self, request: Request) -> Self::Response { 50 | (**self).call(request) 51 | } 52 | } 53 | 54 | pub(crate) fn erase(handler: H) -> Erased 55 | where 56 | H: Handler, 57 | R: FromRequest, 58 | H::Response: IntoResponse, 59 | { 60 | Box::new(Erase { 61 | inner: handler, 62 | _r: PhantomData, 63 | }) 64 | } 65 | 66 | struct Erase { 67 | inner: T, 68 | _r: PhantomData, 69 | } 70 | 71 | impl Handler<()> for Erase 72 | where 73 | T: Handler, 74 | R: FromRequest, 75 | T::Response: IntoResponse, 76 | { 77 | type Response = Response; 78 | 79 | fn call(&self, request: Request) -> Self::Response { 80 | self.inner.call(request).into_response() 81 | } 82 | } 83 | 84 | macro_rules! variadic_handler ({ $($param:ident)* } => { 85 | impl Handler<($($param,)*)> for Func 86 | where 87 | Func: Fn($($param),*) -> R + Send + Sync + 'static, 88 | $($param: FromRequest,)* 89 | R: IntoResponse 90 | { 91 | type Response = Response; 92 | 93 | #[inline] 94 | #[allow(non_snake_case, unused)] 95 | fn call(&self, mut request: Request) -> Self::Response { 96 | $( 97 | let $param = match $param::from_request(&mut request) { 98 | Ok(value) => value, 99 | Err(err) => return err.into_response() 100 | }; 101 | )* 102 | 103 | (self)($($param),*).into_response() 104 | } 105 | } 106 | }); 107 | 108 | variadic_handler! {} 109 | variadic_handler! { A } 110 | variadic_handler! { A B } 111 | variadic_handler! { A B C } 112 | variadic_handler! { A B C D } 113 | variadic_handler! { A B C D E } 114 | variadic_handler! { A B C D E F } 115 | variadic_handler! { A B C D E F G } 116 | variadic_handler! { A B C D E F G H } 117 | variadic_handler! { A B C D E F G H I } 118 | variadic_handler! { A B C D E F G H I J } 119 | variadic_handler! { A B C D E F G H I J K } 120 | variadic_handler! { A B C D E F G H I J K L } 121 | variadic_handler! { A B C D E F G H I J K L M } 122 | variadic_handler! { A B C D E F G H I J K L M N } 123 | variadic_handler! { A B C D E F G H I J K L M N O } 124 | variadic_handler! { A B C D E F G H I J K L M N O P } 125 | -------------------------------------------------------------------------------- /astra-web/src/layer.rs: -------------------------------------------------------------------------------- 1 | use astra::{Request, Response}; 2 | 3 | pub trait Layer: Send + Clone + Sync + 'static { 4 | fn call(&self, request: Request, next: impl Fn(Request) -> Response) -> Response; 5 | 6 | fn layer(self, layer: L) -> And 7 | where 8 | L: Layer, 9 | Self: Sized, 10 | { 11 | And { 12 | inner: self, 13 | outer: layer, 14 | } 15 | } 16 | } 17 | 18 | impl Layer for () { 19 | fn call(&self, request: Request, next: impl Fn(Request) -> Response) -> Response { 20 | next(request) 21 | } 22 | } 23 | 24 | pub struct And { 25 | inner: I, 26 | outer: O, 27 | } 28 | 29 | impl Clone for And 30 | where 31 | I: Clone, 32 | O: Clone, 33 | { 34 | fn clone(&self) -> Self { 35 | And { 36 | inner: self.inner.clone(), 37 | outer: self.outer.clone(), 38 | } 39 | } 40 | } 41 | 42 | impl Layer for And 43 | where 44 | I: Layer, 45 | O: Layer, 46 | { 47 | fn call(&self, request: Request, next: impl Fn(Request) -> Response) -> Response { 48 | let next = |request| self.inner.call(request, &next); 49 | self.outer.call(request, next) 50 | } 51 | } 52 | 53 | pub type Next<'a> = &'a dyn Fn(Request) -> Response; 54 | 55 | pub fn layer_fn(f: F) -> impl Layer 56 | where 57 | for<'n> F: Fn(Request, Next<'n>) -> Response + Send + Clone + Sync + 'static, 58 | { 59 | #[derive(Clone)] 60 | struct Impl(F); 61 | 62 | impl Layer for Impl 63 | where 64 | for<'n> F: Fn(Request, Next<'n>) -> Response + Send + Clone + Sync + 'static, 65 | { 66 | fn call(&self, request: Request, next: impl Fn(Request) -> Response) -> Response { 67 | (self.0)(request, &next) 68 | } 69 | } 70 | 71 | Impl(f) 72 | } 73 | -------------------------------------------------------------------------------- /astra-web/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod handler; 2 | 3 | mod extract; 4 | mod layer; 5 | mod response; 6 | mod router; 7 | 8 | pub use extract::FromRequest; 9 | pub use layer::{layer_fn, Layer, Next}; 10 | pub use response::IntoResponse; 11 | pub use router::Router; 12 | -------------------------------------------------------------------------------- /astra-web/src/response.rs: -------------------------------------------------------------------------------- 1 | use astra::{Body, Response, ResponseBuilder}; 2 | 3 | use std::{borrow::Cow, convert::Infallible}; 4 | 5 | use bytes::Bytes; 6 | use http::{header, StatusCode}; 7 | 8 | pub trait IntoResponse { 9 | fn into_response(self) -> Response; 10 | } 11 | 12 | impl IntoResponse for () { 13 | fn into_response(self) -> Response { 14 | Response::default() 15 | } 16 | } 17 | 18 | impl IntoResponse for Infallible { 19 | fn into_response(self) -> Response { 20 | unreachable!() 21 | } 22 | } 23 | 24 | impl IntoResponse for Response { 25 | fn into_response(self) -> Response { 26 | self 27 | } 28 | } 29 | 30 | impl IntoResponse for (StatusCode, T) 31 | where 32 | T: IntoResponse, 33 | { 34 | fn into_response(self) -> Response { 35 | let mut response = self.1.into_response(); 36 | *response.status_mut() = self.0; 37 | response 38 | } 39 | } 40 | 41 | impl IntoResponse for StatusCode { 42 | fn into_response(self) -> Response { 43 | ResponseBuilder::new() 44 | .status(self) 45 | .body(Body::empty()) 46 | .unwrap() 47 | } 48 | } 49 | 50 | impl IntoResponse for Result 51 | where 52 | T: IntoResponse, 53 | E: IntoResponse, 54 | { 55 | fn into_response(self) -> Response { 56 | match self { 57 | Ok(response) => response.into_response(), 58 | Err(error) => error.into_response(), 59 | } 60 | } 61 | } 62 | 63 | impl IntoResponse for Option 64 | where 65 | T: IntoResponse, 66 | { 67 | fn into_response(self) -> Response { 68 | match self { 69 | Some(response) => response.into_response(), 70 | None => StatusCode::NOT_FOUND.into_response(), 71 | } 72 | } 73 | } 74 | 75 | macro_rules! with_content_type { 76 | ($($ty:ty $(|$into:ident)? => $content_type:literal),* $(,)?) => { $( 77 | impl IntoResponse for $ty { 78 | fn into_response(self) -> Response { 79 | ResponseBuilder::new() 80 | .header(header::CONTENT_TYPE, $content_type) 81 | .body(Body::new(self $(.$into())?)) 82 | .unwrap() 83 | } 84 | })* 85 | } 86 | } 87 | 88 | with_content_type! { 89 | Bytes => "application/octet-stream", 90 | Vec => "application/octet-stream", 91 | &'static [u8] => "application/octet-stream", 92 | Cow<'static, [u8]> | into_owned => "application/octet-stream", 93 | String => "text/plain", 94 | &'static str => "text/plain", 95 | Cow<'static, str> | into_owned => "text/plain", 96 | } 97 | -------------------------------------------------------------------------------- /astra-web/src/router.rs: -------------------------------------------------------------------------------- 1 | use crate::handler::{self, Handler}; 2 | use crate::{FromRequest, IntoResponse, Layer}; 3 | use astra::{Body, Request, Response, ResponseBuilder}; 4 | 5 | use std::collections::HashMap; 6 | use std::sync::Arc; 7 | 8 | use http::{Method, StatusCode}; 9 | 10 | pub struct Router { 11 | pub routes: Vec<(Method, Vec<(String, handler::Erased)>)>, 12 | pub layer: L, 13 | } 14 | 15 | struct Params(HashMap); 16 | 17 | impl Router<()> { 18 | pub fn new() -> Router<()> { 19 | Router { 20 | routes: Vec::new(), 21 | layer: (), 22 | } 23 | } 24 | } 25 | 26 | impl Router 27 | where 28 | L: Layer, 29 | { 30 | pub fn layer(self, layer: O) -> Router 31 | where 32 | O: Layer, 33 | { 34 | Router { 35 | layer: self.layer.layer(layer), 36 | routes: self.routes, 37 | } 38 | } 39 | 40 | pub fn route(self, path: &str, method: Method, handler: H) -> Router 41 | where 42 | H: Handler, 43 | H::Response: IntoResponse, 44 | R: FromRequest, 45 | { 46 | self.route_erased(path, method, handler::erase(handler)) 47 | } 48 | 49 | pub fn get(self, path: &str, handler: H) -> Router 50 | where 51 | H: Handler, 52 | H::Response: IntoResponse, 53 | R: FromRequest, 54 | { 55 | self.route(path, Method::GET, handler) 56 | } 57 | 58 | route!(put => PUT); 59 | route!(post => POST); 60 | route!(head => HEAD); 61 | route!(patch => PATCH); 62 | route!(delete => DELETE); 63 | route!(options => OPTIONS); 64 | 65 | pub fn route_erased( 66 | mut self, 67 | path: &str, 68 | method: Method, 69 | handler: handler::Erased, 70 | ) -> Router { 71 | if let Some(routes) = self.routes_mut(&method) { 72 | routes.push((path.to_owned(), handler)); 73 | return self; 74 | } 75 | 76 | self.routes.push((method, vec![(path.to_owned(), handler)])); 77 | self 78 | } 79 | 80 | pub fn nest(mut self, prefix: &str, router: Router) -> Router 81 | where 82 | O: Layer, 83 | { 84 | let mut prefix = prefix.to_owned(); 85 | if !prefix.ends_with("/") { 86 | prefix.push('/'); 87 | } 88 | 89 | for (method, routes) in router.routes { 90 | for (path, handler) in routes { 91 | let path = format!("{}{}", prefix, path); 92 | let handler = handler.layer(router.layer.clone()); 93 | self = self.route_erased(&path, method.clone(), handler::erase(handler)); 94 | } 95 | } 96 | 97 | self 98 | } 99 | 100 | fn routes_mut(&mut self, method: &Method) -> Option<&mut Vec<(String, handler::Erased)>> { 101 | self.routes 102 | .iter_mut() 103 | .find(|(m, _)| m == method) 104 | .map(|(_, router)| router) 105 | } 106 | 107 | pub fn into_service(self) -> impl astra::Service { 108 | let router = SharedRouter::new(self); 109 | move |req, _| router.serve(req) 110 | } 111 | } 112 | 113 | #[derive(Clone)] 114 | pub struct SharedRouter { 115 | pub routes: Arc)>>, 116 | pub layer: L, 117 | } 118 | 119 | impl SharedRouter { 120 | pub fn new(router: Router) -> SharedRouter { 121 | let routes = router.routes.into_iter().map(|(method, routes)| { 122 | let mut router = matchit::Router::new(); 123 | for (path, handler) in routes { 124 | router.insert(path, handler).unwrap(); 125 | } 126 | (method, router) 127 | }); 128 | 129 | SharedRouter { 130 | routes: Arc::new(routes.collect()), 131 | layer: router.layer, 132 | } 133 | } 134 | } 135 | 136 | impl SharedRouter 137 | where 138 | L: Layer, 139 | { 140 | pub fn serve(&self, mut request: Request) -> Response { 141 | let path = request.uri().path(); 142 | 143 | match self.routes(request.method()) { 144 | Some(router) => match router.at(path) { 145 | Ok(matched) => { 146 | let handler = matched.value; 147 | 148 | let params = matched 149 | .params 150 | .iter() 151 | .map(|(k, v)| (k.to_owned(), v.to_owned())) 152 | .collect::>(); 153 | 154 | request.extensions_mut().insert(Params(params)); 155 | 156 | return self.layer.call(request, |req| handler.call(req)); 157 | } 158 | Err(_) => ResponseBuilder::new() 159 | .status(StatusCode::NOT_FOUND) 160 | .body(Body::empty()) 161 | .unwrap(), 162 | }, 163 | None => ResponseBuilder::new() 164 | .status(StatusCode::METHOD_NOT_ALLOWED) 165 | .body(Body::empty()) 166 | .unwrap(), 167 | } 168 | } 169 | 170 | fn routes(&self, method: &Method) -> Option<&matchit::Router> { 171 | self.routes 172 | .iter() 173 | .find(|(m, _)| m == method) 174 | .map(|(_, router)| router) 175 | } 176 | } 177 | 178 | macro_rules! route { 179 | ($name:ident => $method:ident) => { 180 | pub fn $name(self, path: &str, handler: H) -> Router 181 | where 182 | H: Handler, 183 | H::Response: IntoResponse, 184 | R: FromRequest, 185 | { 186 | self.route(path, Method::$method, handler) 187 | } 188 | }; 189 | } 190 | 191 | pub(crate) use route; 192 | -------------------------------------------------------------------------------- /examples/connection_info.rs: -------------------------------------------------------------------------------- 1 | use astra::{Body, ConnectionInfo, Request, Response, ResponseBuilder, Server}; 2 | 3 | fn main() { 4 | Server::bind("localhost:3000") 5 | .serve(handle) 6 | .expect("serve failed"); 7 | } 8 | 9 | fn handle(_req: Request, info: ConnectionInfo) -> Response { 10 | // Get the ip address of the client 11 | let peer_addr = match info.peer_addr() { 12 | Some(addr) => addr, 13 | None => { 14 | log::error!("Could not get the clients ip address"); 15 | return ResponseBuilder::new() 16 | .status(500) 17 | .body(Body::empty()) 18 | .unwrap(); 19 | } 20 | }; 21 | 22 | let peer_ip = peer_addr.ip(); 23 | Response::new(Body::new(format!("Hello {}!", peer_ip))) 24 | } 25 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | use astra::{Body, Response, Server}; 2 | 3 | fn main() { 4 | Server::bind("127.0.0.1:3000") 5 | .serve(|_req, _info| Response::new(Body::new("Hello World!"))) 6 | .expect("serve failed"); 7 | } 8 | -------------------------------------------------------------------------------- /examples/routing.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use astra::{Body, Request, Response, ResponseBuilder, Server}; 5 | use matchit::Match; 6 | 7 | type Router = matchit::Router Response>; 8 | type Params = HashMap; 9 | 10 | // GET '/' 11 | fn home(_: Request) -> Response { 12 | Response::new(Body::new("Welcome!")) 13 | } 14 | 15 | // GET '/user/:id' 16 | fn get_user(req: Request) -> Response { 17 | // Retreive route parameters from the the request extensions 18 | let params = req.extensions().get::().unwrap(); 19 | 20 | // Get the 'id' from '/user/:id' 21 | let id = params.get("id").unwrap(); 22 | 23 | Response::new(Body::new(format!("User #{id}"))) 24 | } 25 | 26 | fn main() { 27 | // Setup the routes 28 | let router = Arc::new({ 29 | let mut router = Router::new(); 30 | router.insert("/", home).unwrap(); 31 | router.insert("/user/:id", get_user).unwrap(); 32 | router 33 | }); 34 | 35 | Server::bind("localhost:3000") 36 | // Pass the router to `route`, along with the request 37 | .serve(move |req, _info| route(router.clone(), req)) 38 | .expect("serve failed"); 39 | } 40 | 41 | fn route(router: Arc, mut req: Request) -> Response { 42 | // Try to find the handler for the requested path 43 | match router.at(req.uri().path()) { 44 | // If a handler is found, insert the route parameters into the request 45 | // extensions, and call it 46 | Ok(Match { value, params }) => { 47 | let params = params 48 | .iter() 49 | .map(|(k, v)| (k.to_owned(), v.to_owned())) 50 | .collect::(); 51 | req.extensions_mut().insert(params); 52 | (value)(req) 53 | } 54 | // Otherwise return a 404 55 | Err(_) => ResponseBuilder::new() 56 | .status(404) 57 | .body(Body::empty()) 58 | .unwrap(), 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/state.rs: -------------------------------------------------------------------------------- 1 | use astra::{Body, Request, Response, Server}; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | use std::sync::Arc; 4 | 5 | fn main() { 6 | let counter = Arc::new(AtomicUsize::new(0)); 7 | 8 | Server::bind("localhost:3000") 9 | // Pass a handle to the counter along with the request 10 | .serve(move |req, _info| handle(counter.clone(), req)) 11 | .expect("serve failed"); 12 | } 13 | 14 | fn handle(counter: Arc, _req: Request) -> Response { 15 | // Add 1 to the counter, and fetch the current value 16 | let n = counter.fetch_add(1, Ordering::Relaxed); 17 | log::debug!("Request #{n}"); 18 | 19 | Response::new(Body::new("Hello world!")) 20 | } 21 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::future::Future; 3 | use std::num::NonZero; 4 | use std::pin::Pin; 5 | use std::sync::atomic::{AtomicBool, Ordering}; 6 | use std::sync::{Arc, Condvar, Mutex}; 7 | use std::task::{Context, Poll, Wake}; 8 | use std::thread::{self, Thread}; 9 | use std::time::Duration; 10 | 11 | pub struct Parker { 12 | thread: Thread, 13 | parked: AtomicBool, 14 | } 15 | 16 | impl Parker { 17 | pub fn new() -> Arc { 18 | Arc::new(Parker { 19 | thread: thread::current(), 20 | // start off as parked to ensure wakeups are seen in between polling and parking 21 | parked: AtomicBool::new(true), 22 | }) 23 | } 24 | } 25 | 26 | impl Wake for Parker { 27 | fn wake(self: Arc) { 28 | if self.parked.swap(false, Ordering::Release) { 29 | self.thread.unpark(); 30 | } 31 | } 32 | } 33 | 34 | impl Parker { 35 | pub fn block_on(self: &Arc, mut fut: F) -> F::Output 36 | where 37 | F: Future, 38 | { 39 | // reset the parked state 40 | self.parked.store(true, Ordering::Relaxed); 41 | 42 | let waker = self.clone().into(); 43 | let mut cx = Context::from_waker(&waker); 44 | 45 | let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; 46 | loop { 47 | match fut.as_mut().poll(&mut cx) { 48 | Poll::Ready(res) => break res, 49 | Poll::Pending => { 50 | // wait for a real wakeup 51 | while self.parked.swap(true, Ordering::Acquire) { 52 | thread::park(); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | #[derive(Clone)] 61 | pub struct Executor { 62 | inner: Arc, 63 | } 64 | 65 | struct Inner { 66 | keep_alive: Duration, 67 | shared: Mutex, 68 | max_workers: usize, 69 | condvar: Condvar, 70 | } 71 | 72 | struct Shared { 73 | queue: VecDeque + Send>>, 74 | workers: usize, 75 | idle: usize, 76 | notified: usize, 77 | } 78 | 79 | impl Executor { 80 | pub fn new(max_workers: Option, keep_alive: Option) -> Self { 81 | Self { 82 | inner: Arc::new(Inner { 83 | shared: Mutex::new(Shared { 84 | queue: VecDeque::new(), 85 | workers: 0, 86 | idle: 0, 87 | notified: 0, 88 | }), 89 | condvar: Condvar::new(), 90 | keep_alive: keep_alive.unwrap_or_else(|| Duration::from_secs(6)), 91 | max_workers: max_workers.unwrap_or_else(|| { 92 | std::thread::available_parallelism() 93 | .map(NonZero::get) 94 | .unwrap_or(1) 95 | * 15 96 | }), 97 | }), 98 | } 99 | } 100 | } 101 | 102 | impl hyper::rt::Executor for Executor 103 | where 104 | F: Future + Send + 'static, 105 | { 106 | fn execute(&self, fut: F) { 107 | let mut shared = self.inner.shared.lock().unwrap(); 108 | shared.queue.push_back(Box::new(fut)); 109 | 110 | if shared.idle == 0 { 111 | if shared.workers != self.inner.max_workers { 112 | shared.workers += 1; 113 | let inner = self.inner.clone(); 114 | std::thread::Builder::new() 115 | .name("astra-worker".to_owned()) 116 | .spawn(move || inner.run()) 117 | .unwrap(); 118 | } 119 | } else { 120 | shared.idle -= 1; 121 | shared.notified += 1; 122 | self.inner.condvar.notify_one(); 123 | } 124 | } 125 | } 126 | 127 | impl Inner { 128 | fn run(&self) { 129 | let parker = Parker::new(); 130 | 131 | let mut shared = self.shared.lock().unwrap(); 132 | 133 | 'alive: loop { 134 | while let Some(task) = shared.queue.pop_front() { 135 | drop(shared); 136 | parker.block_on(Pin::from(task)); 137 | shared = self.shared.lock().unwrap(); 138 | } 139 | 140 | shared.idle += 1; 141 | 142 | loop { 143 | let (guard, timeout) = self.condvar.wait_timeout(shared, self.keep_alive).unwrap(); 144 | shared = guard; 145 | 146 | if shared.notified != 0 { 147 | shared.notified -= 1; 148 | continue 'alive; 149 | } 150 | 151 | if timeout.timed_out() { 152 | break 'alive; 153 | } 154 | } 155 | } 156 | 157 | shared.workers -= 1; 158 | shared.idle -= 1; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use crate::executor; 2 | 3 | use core::fmt; 4 | use std::future::Future; 5 | use std::pin::Pin; 6 | use std::task::{Context, Poll}; 7 | use std::{cmp, io, mem}; 8 | 9 | use bytes::BytesMut; 10 | use http_body_util::{BodyExt, Full}; 11 | use hyper::body::Frame; 12 | 13 | pub use hyper::body::Bytes; 14 | 15 | /// An HTTP request. 16 | /// 17 | /// See [`http::Request`](hyper::Request) and [`Body`] for details. 18 | pub type Request = hyper::Request; 19 | 20 | /// An HTTP response. 21 | /// 22 | /// You can create a response with the [`new`](hyper::Response::new) method: 23 | /// 24 | /// ``` 25 | /// # use astra::{Response, Body}; 26 | /// let response = Response::new(Body::new("Hello world!")); 27 | /// ``` 28 | /// 29 | /// Or with a [`ResponseBuilder`]: 30 | /// 31 | /// ``` 32 | /// # use astra::{ResponseBuilder, Body}; 33 | /// let response = ResponseBuilder::new() 34 | /// .status(404) 35 | /// .header("X-Custom-Foo", "Bar") 36 | /// .body(Body::new("Page not found.")) 37 | /// .unwrap(); 38 | /// ``` 39 | /// 40 | /// See [`http::Response`](hyper::Response) and [`Body`] for details. 41 | pub type Response = hyper::Response; 42 | 43 | /// A builder for an HTTP response. 44 | /// 45 | /// ``` 46 | /// use astra::{ResponseBuilder, Body}; 47 | /// 48 | /// let response = ResponseBuilder::new() 49 | /// .status(404) 50 | /// .header("X-Custom-Foo", "Bar") 51 | /// .body(Body::new("Page not found.")) 52 | /// .unwrap(); 53 | /// ``` 54 | /// 55 | /// See [`http::Response`](hyper::Response) and [`Body`] for details. 56 | pub type ResponseBuilder = hyper::http::response::Builder; 57 | 58 | /// The streaming body of an HTTP request or response. 59 | /// 60 | /// Data is streamed by iterating over the body, which 61 | /// yields chunks as [`Bytes`](hyper::body::Bytes). 62 | /// 63 | /// ```rust 64 | /// use astra::{Request, Response, Body}; 65 | /// 66 | /// fn handle(mut req: Request) -> Response { 67 | /// for chunk in req.body_mut() { 68 | /// println!("body chunk {:?}", chunk); 69 | /// } 70 | /// 71 | /// Response::new(Body::new("Hello World!")) 72 | /// } 73 | /// ``` 74 | pub struct Body(pub(crate) BoxBody); 75 | 76 | type BoxBody = http_body_util::combinators::UnsyncBoxBody; 77 | 78 | impl Body { 79 | /// Create a body from a string or bytes. 80 | /// 81 | /// ```rust 82 | /// # use astra::Body; 83 | /// let string = Body::new("Hello world!"); 84 | /// let bytes = Body::new(vec![0, 1, 0, 1, 0]); 85 | /// ``` 86 | pub fn new(data: impl Into) -> Body { 87 | Body(BoxBody::new( 88 | Full::new(data.into()).map_err(|err| match err {}), 89 | )) 90 | } 91 | 92 | /// Create an empty body. 93 | pub fn empty() -> Body { 94 | Body(BoxBody::default()) 95 | } 96 | 97 | /// Create a body from an implementor of [`io::Read`]. 98 | /// 99 | /// ```rust 100 | /// use astra::{Request, Response, ResponseBuilder, Body}; 101 | /// use std::fs::File; 102 | /// 103 | /// fn handle(_request: Request) -> Response { 104 | /// let file = File::open("index.html").unwrap(); 105 | /// 106 | /// ResponseBuilder::new() 107 | /// .header("Content-Type", "text/html") 108 | /// .body(Body::wrap_reader(file)) 109 | /// .unwrap() 110 | /// } 111 | /// ``` 112 | pub fn wrap_reader(reader: R) -> Body 113 | where 114 | R: io::Read + Send + 'static, 115 | { 116 | Body(BoxBody::new(ReaderBody::new(reader))) 117 | } 118 | 119 | /// Create a [`BodyReader`] that implements [`std::io::Read`]. 120 | pub fn reader(&mut self) -> BodyReader<'_> { 121 | BodyReader { 122 | body: self, 123 | prev_bytes: Bytes::new(), 124 | } 125 | } 126 | } 127 | 128 | impl From for Body 129 | where 130 | Bytes: From, 131 | { 132 | fn from(data: T) -> Body { 133 | Body::new(data) 134 | } 135 | } 136 | 137 | impl Iterator for Body { 138 | type Item = io::Result; 139 | 140 | fn next(&mut self) -> Option { 141 | struct FrameFuture<'body>(Pin<&'body mut BoxBody>); 142 | 143 | impl Future for FrameFuture<'_> { 144 | type Output = Option, io::Error>>; 145 | 146 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 147 | hyper::body::Body::poll_frame(self.0.as_mut(), cx) 148 | } 149 | } 150 | 151 | loop { 152 | let result = executor::Parker::new().block_on(FrameFuture(Pin::new(&mut self.0)))?; 153 | 154 | return match result { 155 | Ok(frame) => match frame.into_data() { 156 | Ok(bytes) => Some(Ok(bytes)), 157 | Err(_) => continue, 158 | }, 159 | Err(err) => Some(Err(err)), 160 | }; 161 | } 162 | } 163 | 164 | fn size_hint(&self) -> (usize, Option) { 165 | let size_hint = hyper::body::Body::size_hint(&self.0); 166 | ( 167 | size_hint.lower() as _, 168 | size_hint.upper().map(|size| size as _), 169 | ) 170 | } 171 | } 172 | 173 | impl fmt::Debug for Body { 174 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 175 | self.0.fmt(f) 176 | } 177 | } 178 | 179 | impl Default for Body { 180 | fn default() -> Self { 181 | Self::empty() 182 | } 183 | } 184 | 185 | impl hyper::body::Body for Body { 186 | type Data = Bytes; 187 | type Error = io::Error; 188 | 189 | fn poll_frame( 190 | mut self: Pin<&mut Self>, 191 | cx: &mut Context<'_>, 192 | ) -> Poll, Self::Error>>> { 193 | Pin::new(&mut self.0).poll_frame(cx) 194 | } 195 | } 196 | 197 | /// Implements [`std::io::Read`] for [`Body`]. 198 | pub struct BodyReader<'body> { 199 | body: &'body mut Body, 200 | prev_bytes: Bytes, 201 | } 202 | 203 | impl std::io::Read for BodyReader<'_> { 204 | fn read(&mut self, mut buf: &mut [u8]) -> io::Result { 205 | let mut written = 0; 206 | loop { 207 | if buf.is_empty() { 208 | return Ok(written); 209 | } 210 | 211 | if !self.prev_bytes.is_empty() { 212 | let chunk_size = cmp::min(buf.len(), self.prev_bytes.len()); 213 | let prev_bytes_start = self.prev_bytes.split_to(chunk_size); 214 | buf[..chunk_size].copy_from_slice(&prev_bytes_start[..]); 215 | buf = &mut buf[chunk_size..]; 216 | written += chunk_size; 217 | continue; 218 | } 219 | 220 | if written != 0 { 221 | // Pulling from the iterator can block and we have something to return 222 | // already, so return it. 223 | return Ok(written); 224 | } 225 | 226 | debug_assert!(self.prev_bytes.is_empty()); 227 | debug_assert!(written == 0); 228 | 229 | self.prev_bytes = if let Some(next) = self.body.next() { 230 | next? 231 | } else { 232 | return Ok(written); 233 | } 234 | } 235 | } 236 | } 237 | 238 | /// Implements `hyper::Body` for an implementor of `io::Read`. 239 | struct ReaderBody { 240 | reader: Option, 241 | buf: BytesMut, 242 | } 243 | 244 | impl ReaderBody { 245 | /// Create a new `ReaderBody` from an `io::Read`. 246 | fn new(reader: R) -> Self { 247 | Self { 248 | reader: Some(reader), 249 | buf: BytesMut::zeroed(CHUNK), 250 | } 251 | } 252 | } 253 | 254 | /// The size of the read buffer. 255 | const CHUNK: usize = 4096; 256 | 257 | impl Unpin for ReaderBody {} 258 | 259 | impl hyper::body::Body for ReaderBody 260 | where 261 | R: io::Read, 262 | { 263 | type Data = Bytes; 264 | type Error = io::Error; 265 | 266 | fn poll_frame( 267 | mut self: Pin<&mut Self>, 268 | _: &mut Context<'_>, 269 | ) -> Poll, Self::Error>>> { 270 | let ReaderBody { reader, buf } = &mut *self; 271 | 272 | let reader = match reader { 273 | Some(reader) => reader, 274 | None => return Poll::Ready(None), 275 | }; 276 | 277 | if buf.capacity() == 0 { 278 | buf.extend_from_slice(&[0; CHUNK]); 279 | } 280 | 281 | match reader.read(buf) { 282 | Err(err) => Poll::Ready(Some(Err(err))), 283 | Ok(0) => { 284 | self.reader.take(); 285 | Poll::Ready(None) 286 | } 287 | Ok(n) => { 288 | let remaining = buf.split_off(n); 289 | let chunk = mem::replace(buf, remaining); 290 | Poll::Ready(Some(Ok(Frame::data(Bytes::from(chunk))))) 291 | } 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::return_self_not_must_use, clippy::needless_doctest_main)] 2 | #![doc = include_str!("../README.md")] 3 | 4 | mod executor; 5 | mod http; 6 | mod net; 7 | mod server; 8 | 9 | pub use http::{Body, BodyReader, Request, Response, ResponseBuilder}; 10 | pub use server::{ConnectionInfo, Server, Service}; 11 | -------------------------------------------------------------------------------- /src/net.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::io::{self, Read, Write}; 3 | use std::mem::MaybeUninit; 4 | use std::net::{self as sys, Shutdown}; 5 | use std::pin::Pin; 6 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 7 | use std::sync::{Arc, Mutex}; 8 | use std::task::{Context, Poll, Waker}; 9 | 10 | use hyper::rt::ReadBufCursor; 11 | use mio::{Events, Token}; 12 | 13 | #[derive(Clone)] 14 | pub struct Reactor { 15 | shared: Arc, 16 | } 17 | 18 | struct Shared { 19 | registry: mio::Registry, 20 | token: AtomicUsize, 21 | sources: Mutex>>, 22 | } 23 | 24 | impl Reactor { 25 | pub fn new() -> io::Result { 26 | let poll = mio::Poll::new()?; 27 | let shared = Arc::new(Shared { 28 | token: AtomicUsize::new(0), 29 | registry: poll.registry().try_clone()?, 30 | sources: Mutex::new(HashMap::with_capacity(64)), 31 | }); 32 | 33 | std::thread::Builder::new() 34 | .name("astra-reactor".to_owned()) 35 | .spawn({ 36 | let shared = shared.clone(); 37 | move || shared.run(poll) 38 | })?; 39 | 40 | Ok(Reactor { shared }) 41 | } 42 | 43 | pub fn register(&self, sys: sys::TcpStream) -> io::Result { 44 | sys.set_nonblocking(true)?; 45 | let mut sys = mio::net::TcpStream::from_std(sys); 46 | let token = Token(self.shared.token.fetch_add(1, Ordering::Relaxed)); 47 | 48 | self.shared.registry.register( 49 | &mut sys, 50 | token, 51 | mio::Interest::READABLE | mio::Interest::WRITABLE, 52 | )?; 53 | 54 | let source = Arc::new(Source { 55 | token, 56 | interest: Default::default(), 57 | triggered: Default::default(), 58 | }); 59 | 60 | { 61 | let mut sources = self.shared.sources.lock().unwrap(); 62 | sources.insert(token, source.clone()); 63 | } 64 | 65 | Ok(TcpStream { 66 | sys, 67 | source, 68 | reactor: self.clone(), 69 | }) 70 | } 71 | 72 | fn poll_ready( 73 | &self, 74 | source: &Source, 75 | direction: usize, 76 | cx: &Context<'_>, 77 | ) -> Poll> { 78 | if source.triggered[direction].load(Ordering::Acquire) { 79 | return Poll::Ready(Ok(())); 80 | } 81 | 82 | { 83 | let mut interest = source.interest.lock().unwrap(); 84 | 85 | match &mut interest[direction] { 86 | Some(existing) if existing.will_wake(cx.waker()) => {} 87 | _ => { 88 | interest[direction] = Some(cx.waker().clone()); 89 | } 90 | } 91 | } 92 | 93 | // check if anything changed while we were registering 94 | // our waker 95 | if source.triggered[direction].load(Ordering::Acquire) { 96 | return Poll::Ready(Ok(())); 97 | } 98 | 99 | Poll::Pending 100 | } 101 | 102 | fn clear_trigger(&self, source: &Source, direction: usize) { 103 | source.triggered[direction].store(false, Ordering::Release); 104 | } 105 | } 106 | 107 | impl Shared { 108 | fn run(&self, mut poll: mio::Poll) -> io::Result<()> { 109 | let mut events = Events::with_capacity(64); 110 | let mut wakers = Vec::new(); 111 | 112 | loop { 113 | if let Err(err) = self.poll(&mut poll, &mut events, &mut wakers) { 114 | log::warn!("Failed to poll reactor: {}", err); 115 | } 116 | 117 | events.clear(); 118 | } 119 | } 120 | 121 | fn poll( 122 | &self, 123 | poll: &mut mio::Poll, 124 | events: &mut Events, 125 | wakers: &mut Vec, 126 | ) -> io::Result<()> { 127 | if let Err(err) = poll.poll(events, None) { 128 | if err.kind() != io::ErrorKind::Interrupted { 129 | return Err(err); 130 | } 131 | 132 | return Ok(()); 133 | } 134 | 135 | for event in events.iter() { 136 | let source = { 137 | let sources = self.sources.lock().unwrap(); 138 | match sources.get(&event.token()) { 139 | Some(source) => source.clone(), 140 | None => continue, 141 | } 142 | }; 143 | 144 | let mut interest = source.interest.lock().unwrap(); 145 | 146 | if event.is_readable() { 147 | if let Some(waker) = interest[direction::READ].take() { 148 | wakers.push(waker); 149 | } 150 | 151 | source.triggered[direction::READ].store(true, Ordering::Release); 152 | } 153 | 154 | if event.is_writable() { 155 | if let Some(waker) = interest[direction::WRITE].take() { 156 | wakers.push(waker); 157 | } 158 | 159 | source.triggered[direction::WRITE].store(true, Ordering::Release); 160 | } 161 | } 162 | 163 | for waker in wakers.drain(..) { 164 | waker.wake(); 165 | } 166 | 167 | Ok(()) 168 | } 169 | } 170 | 171 | mod direction { 172 | pub const READ: usize = 0; 173 | pub const WRITE: usize = 1; 174 | } 175 | 176 | struct Source { 177 | interest: Mutex<[Option; 2]>, 178 | triggered: [AtomicBool; 2], 179 | token: Token, 180 | } 181 | 182 | pub struct TcpStream { 183 | pub sys: mio::net::TcpStream, 184 | reactor: Reactor, 185 | source: Arc, 186 | } 187 | 188 | impl TcpStream { 189 | pub fn poll_io( 190 | &self, 191 | direction: usize, 192 | mut f: impl FnMut() -> io::Result, 193 | cx: &Context<'_>, 194 | ) -> Poll> { 195 | loop { 196 | if self 197 | .reactor 198 | .poll_ready(&self.source, direction, cx)? 199 | .is_pending() 200 | { 201 | return Poll::Pending; 202 | } 203 | 204 | match f() { 205 | Err(err) if err.kind() == io::ErrorKind::WouldBlock => { 206 | self.reactor.clear_trigger(&self.source, direction); 207 | } 208 | val => return Poll::Ready(val), 209 | } 210 | } 211 | } 212 | } 213 | 214 | impl hyper::rt::Read for TcpStream { 215 | fn poll_read( 216 | self: Pin<&mut Self>, 217 | cx: &mut Context<'_>, 218 | mut buf: ReadBufCursor<'_>, 219 | ) -> Poll> { 220 | let initialized = unsafe { 221 | let buf = buf.as_mut(); 222 | 223 | // Zero the buffer. 224 | std::ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()); 225 | 226 | // Safety: The buffer was initialized above. 227 | &mut *(buf as *mut [MaybeUninit] as *mut [u8]) 228 | }; 229 | 230 | self.poll_io(direction::READ, || (&self.sys).read(initialized), cx) 231 | .map_ok(|n| { 232 | // Safety: The entire buffer was initialized above. 233 | unsafe { buf.advance(n) }; 234 | }) 235 | } 236 | } 237 | 238 | impl hyper::rt::Write for TcpStream { 239 | fn poll_write( 240 | self: Pin<&mut Self>, 241 | cx: &mut Context<'_>, 242 | buf: &[u8], 243 | ) -> Poll> { 244 | self.poll_io(direction::WRITE, || (&self.sys).write(buf), cx) 245 | } 246 | 247 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 248 | self.poll_io(direction::WRITE, || (&self.sys).flush(), cx) 249 | } 250 | 251 | fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 252 | Poll::Ready(self.sys.shutdown(Shutdown::Write)) 253 | } 254 | } 255 | 256 | impl Drop for TcpStream { 257 | fn drop(&mut self) { 258 | let mut sources = self.reactor.shared.sources.lock().unwrap(); 259 | let _ = sources.remove(&self.source.token); 260 | let _ = self.reactor.shared.registry.deregister(&mut self.sys); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::net::Reactor; 2 | use crate::{executor, Body, Request, Response}; 3 | 4 | use std::convert::Infallible; 5 | use std::future::Future; 6 | use std::io; 7 | use std::net::{SocketAddr, TcpListener, ToSocketAddrs}; 8 | use std::pin::Pin; 9 | use std::sync::Arc; 10 | use std::task::{Context, Poll}; 11 | use std::time::Duration; 12 | 13 | use hyper::rt::Executor; 14 | use hyper_util::server::conn::auto::Builder; 15 | 16 | /// An HTTP server. 17 | /// 18 | /// ```no_run 19 | /// use astra::{Body, Request, Response, Server}; 20 | /// 21 | /// Server::bind("localhost:3000") 22 | /// .serve(|mut req: Request, _info| { 23 | /// println!("incoming {:?}", req.uri()); 24 | /// Response::new(Body::new("Hello World!")) 25 | /// }) 26 | /// .expect("failed to start server"); 27 | /// ``` 28 | /// 29 | /// See the [crate-level documentation](crate#how-does-it-work) for details. 30 | #[derive(Default)] 31 | pub struct Server { 32 | listener: Option, 33 | http1_keep_alive: Option, 34 | http1_half_close: Option, 35 | http1_max_buf_size: Option, 36 | http1_pipeline_flush: Option, 37 | http1_writev: Option, 38 | http1_title_case_headers: Option, 39 | http1_preserve_header_case: Option, 40 | http1_only: bool, 41 | 42 | #[cfg(feature = "http2")] 43 | http2_only: bool, 44 | #[cfg(feature = "http2")] 45 | http2_initial_stream_window_size: Option, 46 | #[cfg(feature = "http2")] 47 | http2_enable_connect_protocol: bool, 48 | #[cfg(feature = "http2")] 49 | http2_initial_connection_window_size: Option, 50 | #[cfg(feature = "http2")] 51 | http2_adaptive_window: Option, 52 | #[cfg(feature = "http2")] 53 | http2_max_frame_size: Option, 54 | #[cfg(feature = "http2")] 55 | http2_max_concurrent_streams: Option, 56 | #[cfg(feature = "http2")] 57 | http2_max_send_buf_size: Option, 58 | #[cfg(feature = "http2")] 59 | http2_max_header_list_size: Option, 60 | 61 | worker_keep_alive: Option, 62 | max_workers: Option, 63 | } 64 | 65 | /// HTTP connection information. 66 | #[derive(Clone, Debug)] 67 | pub struct ConnectionInfo { 68 | peer_addr: Option, 69 | } 70 | 71 | impl ConnectionInfo { 72 | /// Returns the socket address of the remote peer of this connection. 73 | pub fn peer_addr(&self) -> Option { 74 | self.peer_addr 75 | } 76 | } 77 | 78 | /// A service capable of responding to an HTTP request. 79 | /// 80 | /// This trait is automatically implemented for functions 81 | /// from a [`Request`] to a [`Response`], but implementing 82 | /// it manually allows for stateful services: 83 | /// 84 | /// ```no_run 85 | /// use astra::{Request, Response, Server, Service, Body, ConnectionInfo}; 86 | /// use std::sync::Mutex; 87 | /// 88 | /// struct MyService { 89 | /// count: Mutex, 90 | /// } 91 | /// 92 | /// impl Service for MyService { 93 | /// fn call(&self, request: Request, _info: ConnectionInfo) -> Response { 94 | /// let mut count = self.count.lock().unwrap(); 95 | /// *count += 1; 96 | /// println!("request #{}", *count); 97 | /// Response::new(Body::new("Hello world")) 98 | /// } 99 | /// } 100 | /// 101 | /// 102 | /// Server::bind("localhost:3000") 103 | /// .serve(MyService { count: Mutex::new(0) }) 104 | /// .expect("failed to start server"); 105 | /// ``` 106 | /// 107 | /// If your service is already cheaply cloneable, you can instead use `serve_clone` and avoid an extra `Arc` wrapper: 108 | /// 109 | /// ```no_run 110 | /// use astra::{Request, Response, Server, Service, Body, ConnectionInfo}; 111 | /// use std::sync::{Arc, Mutex}; 112 | /// 113 | /// #[derive(Clone)] 114 | /// struct MyService { 115 | /// count: Arc>, 116 | /// } 117 | /// 118 | /// impl Service for MyService { 119 | /// fn call(&self, request: Request, _info: ConnectionInfo) -> Response { 120 | /// let mut count = self.count.lock().unwrap(); 121 | /// *count += 1; 122 | /// println!("request #{}", *count); 123 | /// Response::new(Body::new("Hello world")) 124 | /// } 125 | /// } 126 | /// 127 | /// Server::bind("localhost:3000") 128 | /// .serve_clone(MyService { count: Arc::new(Mutex::new(0)) }) 129 | /// .expect("failed to start server"); 130 | /// ``` 131 | pub trait Service: Send + 'static { 132 | fn call(&self, request: Request, info: ConnectionInfo) -> Response; 133 | } 134 | 135 | impl Service for F 136 | where 137 | F: Fn(Request, ConnectionInfo) -> Response + Send + 'static, 138 | { 139 | fn call(&self, request: Request, info: ConnectionInfo) -> Response { 140 | (self)(request, info) 141 | } 142 | } 143 | 144 | impl Service for Arc 145 | where 146 | S: Service + Sync, 147 | { 148 | fn call(&self, request: Request, info: ConnectionInfo) -> Response { 149 | (**self).call(request, info) 150 | } 151 | } 152 | 153 | impl Server { 154 | /// Binds a server to the provided address. 155 | /// 156 | /// ```no_run 157 | /// use astra::Server; 158 | /// use std::net::SocketAddr; 159 | /// 160 | /// let server = Server::bind("localhost:3000"); 161 | /// let server = Server::bind(SocketAddr::from(([127, 0, 0, 1], 3000))); 162 | /// ``` 163 | /// 164 | /// # Panics 165 | /// 166 | /// This method will panic if binding to the address fails. 167 | pub fn bind(addr: impl ToSocketAddrs) -> Server { 168 | Self::try_bind(addr).expect("failed to bind listener") 169 | } 170 | 171 | /// Binds a server to the provided address, returning an error on failure. 172 | /// 173 | /// ```no_run 174 | /// use astra::Server; 175 | /// use std::net::SocketAddr; 176 | /// 177 | /// let server = Server::try_bind("localhost:3000") 178 | /// .expect("failed to bind listener"); 179 | /// let server = Server::try_bind(SocketAddr::from(([127, 0, 0, 1], 3000))) 180 | /// .expect("failed to bind listener"); 181 | /// ``` 182 | pub fn try_bind(addr: impl ToSocketAddrs) -> io::Result { 183 | let listener = std::net::TcpListener::bind(addr)?; 184 | 185 | Ok(Server { 186 | listener: Some(listener), 187 | ..Default::default() 188 | }) 189 | } 190 | 191 | /// Serve incoming connections with the provided service. 192 | /// 193 | /// ```no_run 194 | /// use astra::{Body, Request, Response, Server}; 195 | /// 196 | /// Server::bind("localhost:3000") 197 | /// .serve(|mut req: Request, _| { 198 | /// println!("incoming {:?}", req.uri()); 199 | /// Response::new(Body::new("Hello World!")) 200 | /// }) 201 | /// .expect("failed to start server"); 202 | /// ``` 203 | pub fn serve(self, service: S) -> io::Result<()> 204 | where 205 | S: Service + Sync, 206 | { 207 | self.serve_clone(Arc::new(service)) 208 | } 209 | 210 | /// Like [`Self::serve`] but does not wrap `service` in an `Arc` and expects it to 211 | /// implement `Clone` and `Sync` internally. 212 | pub fn serve_clone(self, service: S) -> io::Result<()> 213 | where 214 | S: Service + Clone, 215 | { 216 | let executor = executor::Executor::new(self.max_workers, self.worker_keep_alive); 217 | let http = self.configure(Builder::new(executor.clone())); 218 | 219 | let reactor = Reactor::new().expect("failed to create reactor"); 220 | 221 | for conn in self.listener.unwrap().incoming() { 222 | let conn = conn.and_then(|stream| reactor.register(stream))?; 223 | 224 | let service = service.clone(); 225 | let builder = http.clone(); 226 | let info = ConnectionInfo { 227 | peer_addr: conn.sys.peer_addr().ok(), 228 | }; 229 | 230 | executor.execute(async move { 231 | if let Err(err) = builder 232 | .clone() 233 | .serve_connection(conn, service::HyperService(service, info)) 234 | .await 235 | { 236 | log::error!("error serving connection: {}", err); 237 | } 238 | }); 239 | } 240 | 241 | Ok(()) 242 | } 243 | 244 | /// Sets the maximum number of threads in the worker pool. 245 | /// 246 | /// By default, the limit is 15 threads per CPU core. 247 | pub fn max_workers(mut self, val: usize) -> Self { 248 | self.max_workers = Some(val); 249 | self 250 | } 251 | 252 | /// Sets how long to keep alive an idle thread in the worker pool. 253 | /// 254 | /// By default, the timeout is set to 6 seconds. 255 | pub fn worker_keep_alive(mut self, val: Duration) -> Self { 256 | self.worker_keep_alive = Some(val); 257 | self 258 | } 259 | 260 | /// Sets whether to use keep-alive for HTTP/1 connections. 261 | /// 262 | /// Default is `true`. 263 | pub fn http1_keep_alive(mut self, val: bool) -> Self { 264 | self.http1_keep_alive = Some(val); 265 | self 266 | } 267 | 268 | /// Set whether HTTP/1 connections should support half-closures. 269 | /// 270 | /// Clients can chose to shutdown their write-side while waiting 271 | /// for the server to respond. Setting this to `true` will 272 | /// prevent closing the connection immediately if `read` 273 | /// detects an EOF in the middle of a request. 274 | /// 275 | /// Default is `false`. 276 | pub fn http1_half_close(mut self, val: bool) -> Self { 277 | self.http1_half_close = Some(val); 278 | self 279 | } 280 | 281 | /// Set the maximum buffer size. 282 | /// 283 | /// Default is ~ 400kb. 284 | pub fn http1_max_buf_size(mut self, val: usize) -> Self { 285 | self.http1_max_buf_size = Some(val); 286 | self 287 | } 288 | 289 | /// Sets whether to bunch up HTTP/1 writes until the read buffer is empty. 290 | /// 291 | /// This isn't really desirable in most cases, only really being useful in 292 | /// silly pipeline benchmarks. 293 | pub fn http1_pipeline_flush(mut self, val: bool) -> Self { 294 | self.http1_pipeline_flush = Some(val); 295 | self 296 | } 297 | 298 | /// Set whether HTTP/1 connections should try to use vectored writes, 299 | /// or always flatten into a single buffer. 300 | /// 301 | /// Note that setting this to false may mean more copies of body data, 302 | /// but may also improve performance when an IO transport doesn't 303 | /// support vectored writes well, such as most TLS implementations. 304 | /// 305 | /// Setting this to true will force hyper to use queued strategy 306 | /// which may eliminate unnecessary cloning on some TLS backends 307 | /// 308 | /// Default is `auto`. In this mode hyper will try to guess which 309 | /// mode to use 310 | pub fn http1_writev(mut self, enabled: bool) -> Self { 311 | self.http1_writev = Some(enabled); 312 | self 313 | } 314 | 315 | /// Set whether HTTP/1 connections will write header names as title case at 316 | /// the socket level. 317 | /// 318 | /// Note that this setting does not affect HTTP/2. 319 | /// 320 | /// Default is false. 321 | pub fn http1_title_case_headers(mut self, val: bool) -> Self { 322 | self.http1_title_case_headers = Some(val); 323 | self 324 | } 325 | 326 | /// Set whether to support preserving original header cases. 327 | /// 328 | /// Currently, this will record the original cases received, and store them 329 | /// in a private extension on the `Request`. It will also look for and use 330 | /// such an extension in any provided `Response`. 331 | /// 332 | /// Since the relevant extension is still private, there is no way to 333 | /// interact with the original cases. The only effect this can have now is 334 | /// to forward the cases in a proxy-like fashion. 335 | /// 336 | /// Note that this setting does not affect HTTP/2. 337 | /// 338 | /// Default is false. 339 | pub fn http1_preserve_header_case(mut self, val: bool) -> Self { 340 | self.http1_preserve_header_case = Some(val); 341 | self 342 | } 343 | 344 | /// Only accepts HTTP/1. 345 | pub fn http1_only(mut self) -> Self { 346 | self.http1_only = true; 347 | self 348 | } 349 | 350 | /// Sets whether HTTP/2 is required. 351 | /// 352 | /// Default is `false`. 353 | #[cfg(feature = "http2")] 354 | pub fn http2_only(mut self) -> Self { 355 | self.http2_only = true; 356 | self 357 | } 358 | 359 | /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 360 | /// stream-level flow control. 361 | /// 362 | /// Passing `None` will do nothing. 363 | /// 364 | /// If not set, hyper will use a default. 365 | /// 366 | /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE 367 | #[cfg(feature = "http2")] 368 | pub fn http2_initial_stream_window_size(mut self, sz: impl Into>) -> Self { 369 | self.http2_initial_stream_window_size = sz.into(); 370 | self 371 | } 372 | 373 | /// Enables the [extended CONNECT protocol]. 374 | /// 375 | /// [extended CONNECT protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4 376 | #[cfg(feature = "http2")] 377 | pub fn http2_enable_connect_protocol(mut self) -> Self { 378 | self.http2_enable_connect_protocol = true; 379 | self 380 | } 381 | 382 | /// Sets the max connection-level flow control for HTTP2 383 | /// 384 | /// Passing `None` will do nothing. 385 | /// 386 | /// If not set, hyper will use a default. 387 | #[cfg(feature = "http2")] 388 | pub fn http2_initial_connection_window_size(mut self, sz: impl Into>) -> Self { 389 | self.http2_initial_connection_window_size = sz.into(); 390 | self 391 | } 392 | 393 | /// Sets whether to use an adaptive flow control. 394 | /// 395 | /// Enabling this will override the limits set in 396 | /// `http2_initial_stream_window_size` and 397 | /// `http2_initial_connection_window_size`. 398 | #[cfg(feature = "http2")] 399 | pub fn http2_adaptive_window(mut self, enabled: bool) -> Self { 400 | self.http2_adaptive_window = Some(enabled); 401 | self 402 | } 403 | 404 | /// Sets the maximum frame size to use for HTTP2. 405 | /// 406 | /// Passing `None` will do nothing. 407 | /// 408 | /// If not set, hyper will use a default. 409 | #[cfg(feature = "http2")] 410 | pub fn http2_max_frame_size(mut self, sz: impl Into>) -> Self { 411 | self.http2_max_frame_size = sz.into(); 412 | self 413 | } 414 | 415 | /// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2 416 | /// connections. 417 | /// 418 | /// Default is no limit (`std::u32::MAX`). Passing `None` will do nothing. 419 | /// 420 | /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS 421 | #[cfg(feature = "http2")] 422 | pub fn http2_max_concurrent_streams(mut self, max: impl Into>) -> Self { 423 | self.http2_max_concurrent_streams = max.into(); 424 | self 425 | } 426 | 427 | /// Set the maximum write buffer size for each HTTP/2 stream. 428 | /// 429 | /// Default is currently ~400KB, but may change. 430 | /// 431 | /// # Panics 432 | /// 433 | /// The value must be no larger than `u32::MAX`. 434 | #[cfg(feature = "http2")] 435 | pub fn http2_max_send_buf_size(mut self, max: usize) -> Self { 436 | self.http2_max_send_buf_size = Some(max); 437 | self 438 | } 439 | 440 | /// Get the local address of the bound socket 441 | pub fn local_addr(&self) -> io::Result { 442 | self.listener 443 | .as_ref() 444 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Server::bind not called yet"))? 445 | .local_addr() 446 | } 447 | 448 | // Propagate all settings on this server to the `Builder`. 449 | fn configure(&self, mut http: Builder) -> Builder { 450 | macro_rules! configure { 451 | ($self:ident.$option:ident => $other:ident.$builder:ident.$other_option:ident) => {{ 452 | if let Some(val) = $self.$option { 453 | $other.$builder().$other_option(val); 454 | } 455 | }}; 456 | ($self:ident.$option:ident => $other:ident.$builder:ident.$other_option:ident()) => {{ 457 | if $self.$option { 458 | $other.$builder().$other_option(); 459 | } 460 | }}; 461 | } 462 | 463 | if self.http1_only { 464 | http = http.http1_only(); 465 | } 466 | 467 | #[cfg(feature = "http2")] 468 | if self.http2_only { 469 | http = http.http2_only(); 470 | } 471 | 472 | configure!(self.http1_keep_alive => http.http1.keep_alive); 473 | configure!(self.http1_half_close => http.http1.half_close); 474 | configure!(self.http1_max_buf_size => http.http1.max_buf_size); 475 | configure!(self.http1_pipeline_flush => http.http1.pipeline_flush); 476 | configure!(self.http1_writev => http.http1.writev); 477 | configure!(self.http1_title_case_headers => http.http1.title_case_headers); 478 | configure!(self.http1_preserve_header_case => http.http1.preserve_header_case); 479 | 480 | #[cfg(feature = "http2")] 481 | { 482 | configure!(self.http2_initial_stream_window_size => http.http2.initial_stream_window_size); 483 | configure!(self.http2_enable_connect_protocol => http.http2.enable_connect_protocol()); 484 | configure!(self.http2_initial_connection_window_size => http.http2.initial_connection_window_size); 485 | configure!(self.http2_adaptive_window => http.http2.adaptive_window); 486 | configure!(self.http2_max_frame_size => http.http2.max_frame_size); 487 | configure!(self.http2_max_concurrent_streams => http.http2.max_concurrent_streams); 488 | configure!(self.http2_max_send_buf_size => http.http2.max_send_buf_size); 489 | configure!(self.http2_max_header_list_size => http.http2.max_header_list_size); 490 | } 491 | 492 | http 493 | } 494 | } 495 | 496 | mod service { 497 | use super::*; 498 | 499 | use http_body_util::combinators::UnsyncBoxBody; 500 | use http_body_util::BodyExt; 501 | 502 | type HyperRequest = hyper::Request; 503 | 504 | /// Implements `hyper::Service` for an implementation of `astra::Service`. 505 | pub struct HyperService(pub S, pub ConnectionInfo); 506 | 507 | impl hyper::service::Service for HyperService 508 | where 509 | S: Service + Clone, 510 | { 511 | type Response = Response; 512 | type Error = Infallible; 513 | type Future = Lazy; 514 | 515 | fn call(&self, request: HyperRequest) -> Self::Future { 516 | Lazy { 517 | request: Some(request), 518 | service: self.0.clone(), 519 | info: self.1.clone(), 520 | } 521 | } 522 | } 523 | 524 | /// A `Future` that calls `astra::Service::call`. 525 | pub struct Lazy { 526 | service: S, 527 | request: Option, 528 | info: ConnectionInfo, 529 | } 530 | 531 | impl Unpin for Lazy {} 532 | 533 | impl Future for Lazy 534 | where 535 | S: Service, 536 | { 537 | type Output = Result; 538 | 539 | fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { 540 | let (parts, body) = self.request.take().unwrap().into_parts(); 541 | let body = Body(UnsyncBoxBody::new(body.map_err(io::Error::other))); 542 | let req = Request::from_parts(parts, body); 543 | 544 | let res = self.service.call(req, self.info.clone()); 545 | Poll::Ready(Ok(res)) 546 | } 547 | } 548 | } 549 | --------------------------------------------------------------------------------