diff --git a/Cargo.toml b/Cargo.toml index 7dd959a2..997d241e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "viz-macros", "viz-router", "viz-tower", + "viz-smol", "viz-test", "examples/hello-world", @@ -34,6 +35,7 @@ members = [ "examples/databases/*", "examples/htmlx", "examples/tower", + "examples/smol", ] [workspace.package] @@ -55,6 +57,8 @@ viz-macros = { version = "0.2.0", path = "viz-macros" } viz-test = { version = "0.2.0", path = "viz-test" } viz-tower = { version = "0.1.0", path = "viz-tower" } +viz-smol = { version = "0.1.0", path = "viz-smol" } + anyhow = "1.0" async-trait = "0.1" bytes = "1.5" @@ -113,6 +117,16 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } tower = "0.4" tower-http = "0.5" +# soml +async-channel = "2.1" +async-executor = "1.8" +async-io = "2.2" +async-net = "2.0" +smol-hyper = "0.1.1" +smol-macros = "0.1" +macro_rules_attribute = "0.2" +futures-lite = { version = "2.1.0", default-features = false, features = ["std"] } + [workspace.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/examples/smol/Cargo.toml b/examples/smol/Cargo.toml new file mode 100644 index 00000000..454d4547 --- /dev/null +++ b/examples/smol/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "smol-example" +version = "0.1.0" +edition.workspace = true +publish = false + +[dependencies] +viz-smol.workspace = true + +async-net.workspace = true +smol-macros.workspace = true +macro_rules_attribute.workspace = true diff --git a/examples/smol/src/main.rs b/examples/smol/src/main.rs new file mode 100644 index 00000000..e98db65a --- /dev/null +++ b/examples/smol/src/main.rs @@ -0,0 +1,23 @@ +use std::io; +use std::sync::Arc; + +use async_net::TcpListener; +use macro_rules_attribute::apply; +use viz_smol::{IntoResponse, Request, Response, Result, Router}; + +#[apply(smol_macros::main!)] +async fn main(ex: &Arc>) -> io::Result<()> { + // Build our application with a route. + let app = Router::new().get("/", handler); + + // Create a `smol`-based TCP listener. + let listener = TcpListener::bind(("127.0.0.1", 3000)).await.unwrap(); + println!("listening on {}", listener.local_addr().unwrap()); + + // Run it + viz_smol::serve(ex.clone(), listener, app).await +} + +async fn handler(_: Request) -> Result { + Ok("

Hello, World!

".into_response()) +} diff --git a/viz-core/Cargo.toml b/viz-core/Cargo.toml index 897ac167..d9069394 100644 --- a/viz-core/Cargo.toml +++ b/viz-core/Cargo.toml @@ -17,28 +17,23 @@ categories = ["asynchronous", "network-programming", "web-programming"] default = [ "state", "limits", - "params", - "json", - "form", "query", + "form", + "json", "multipart", - "websocket", + "params", "cookie", "session", - "fs", ] state = [] limits = [] -params = ["dep:serde"] -json = ["dep:serde", "dep:serde_json"] -form = ["dep:serde", "dep:serde_urlencoded"] query = ["dep:serde", "dep:serde_urlencoded"] +form = ["dep:serde", "dep:serde_urlencoded"] +json = ["dep:serde", "dep:serde_json"] multipart = ["dep:form-data"] -websocket = ["dep:tokio-tungstenite", "tokio/rt"] -sse = ["dep:tokio-stream", "tokio/time"] -fs = ["tokio-util/io", "tokio/fs"] +params = ["dep:serde"] cookie = ["dep:cookie"] cookie-private = ["cookie", "cookie?/private"] @@ -46,6 +41,10 @@ cookie-signed = ["cookie", "cookie?/signed"] session = ["cookie-private", "json", "dep:sessions-core"] +websocket = ["dep:tokio-tungstenite", "tokio/rt"] +sse = ["dep:tokio-stream", "tokio/time"] +fs = ["tokio-util/io", "tokio/fs"] + csrf = ["cookie-private", "dep:base64", "dep:getrandom"] cors = [] diff --git a/viz-core/src/response.rs b/viz-core/src/response.rs index e761cc26..780f7319 100644 --- a/viz-core/src/response.rs +++ b/viz-core/src/response.rs @@ -1,6 +1,6 @@ use http_body_util::Full; -use crate::{header, Body, BoxError, Bytes, Error, Future, Response, Result, StatusCode}; +use crate::{header, Body, BoxError, Bytes, Response, Result, StatusCode}; /// The [`Response`] Extension. pub trait ResponseExt: private::Sealed + Sized { @@ -94,7 +94,10 @@ pub trait ResponseExt: private::Sealed + Sized { /// Downloads transfers the file from path as an attachment. #[cfg(feature = "fs")] - fn download(path: T, name: Option<&str>) -> impl Future> + Send + fn download( + path: T, + name: Option<&str>, + ) -> impl std::future::Future> + Send where T: AsRef + Send; @@ -231,7 +234,9 @@ impl ResponseExt for Response { let mut resp = Self::attachment(&format!("attachment; filename=\"{value}\"")); *resp.body_mut() = Body::from_stream(tokio_util::io::ReaderStream::new( - tokio::fs::File::open(path).await.map_err(Error::from)?, + tokio::fs::File::open(path) + .await + .map_err(crate::Error::from)?, )); Ok(resp) } diff --git a/viz-smol/Cargo.toml b/viz-smol/Cargo.toml new file mode 100644 index 00000000..ebb7408e --- /dev/null +++ b/viz-smol/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "viz-smol" +version = "0.1.0" +documentation = "https://docs.rs/viz-smol" +description = "An adapter for smol runtime" +readme = "README.md" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +rust-version.workspace = true + +[features] +default = [ + "state", + "limits", + "query", + "form", + "json", + "multipart", + "params", + "cookie", + "session", + + "http1", +] + +state = ["viz-core/state"] +limits = ["viz-core/limits"] + +query = ["viz-core/query"] +form = ["viz-core/form"] +json = ["viz-core/json"] +multipart = ["viz-core/multipart"] +params = ["viz-core/params"] + +cookie = ["viz-core/cookie"] +cookie-private = ["viz-core/cookie-private"] +cookie-signed = ["viz-core/cookie-signed"] + +session = ["cookie", "cookie-private", "viz-core/session"] + +csrf = ["cookie", "cookie-private", "viz-core/csrf"] +cors = ["viz-core/cors"] + +http1 = ["hyper/http1"] +http2 = ["hyper/http2"] + +unix-socket = [] + +macros = ["dep:viz-macros"] + +handlers = ["dep:viz-handlers"] + +otel = ["viz-core/otel"] +otel-tracing = ["otel", "viz-core/otel-tracing"] +otel-metrics = ["otel", "viz-core/otel-metrics"] +otel-prometheus = ["handlers", "viz-handlers?/prometheus"] + +[dependencies] +viz-core.workspace = true +viz-router.workspace = true +viz-handlers = { workspace = true, optional = true } +viz-macros = { workspace = true, optional = true } + +hyper.workspace = true +hyper-util.workspace = true +tracing.workspace = true + +async-executor.workspace = true +async-net.workspace = true +smol-hyper.workspace = true +futures-lite.workspace = true + +[dev-dependencies] +smol-macros.workspace = true +macro_rules_attribute.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/viz-smol/src/lib.rs b/viz-smol/src/lib.rs new file mode 100644 index 00000000..70fd9682 --- /dev/null +++ b/viz-smol/src/lib.rs @@ -0,0 +1,80 @@ +//! Viz +//! +//! Fast, robust, flexible, lightweight web framework for Rust. +//! +//! # Features +//! +//! * **Safety** `#![forbid(unsafe_code)]` +//! +//! * Lightweight +//! +//! * Simple + Flexible [`Handler`](#handler) & [`Middleware`](#middleware) +//! +//! * Handy [`Extractors`](#extractors) +//! +//! * Robust [`Routing`](#routing) +//! +//! * Supports Tower [`Service`] +//! +//! # Hello Viz +//! +//! ```no_run +//! use std::io; +//! use std::sync::Arc; +//! +//! use async_net::TcpListener; +//! use macro_rules_attribute::apply; +//! use viz_smol::{Request, Result, Router}; +//! +//! async fn index(_: Request) -> Result<&'static str> { +//! Ok("Hello, Viz!") +//! } +//! +//! #[apply(smol_macros::main!)] +//! async fn main(ex: &Arc>) -> io::Result<()> { +//! // Build our application with a route. +//! let app = Router::new().get("/", index); +//! +//! // Create a `smol`-based TCP listener. +//! let listener = TcpListener::bind(("127.0.0.1", 3000)).await.unwrap(); +//! println!("listening on {}", listener.local_addr().unwrap()); +//! +//! // Run it +//! viz_smol::serve(ex.clone(), listener, app).await +//! } +//! ``` +//! +//! [`Service`]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html + +#![doc(html_logo_url = "https://viz.rs/logo.svg")] +#![doc(html_favicon_url = "https://viz.rs/logo.svg")] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +mod responder; +pub use responder::Responder; + +mod listener; +pub use listener::Listener; + +mod server; +pub use server::serve; + +#[cfg(any(feature = "native_tls", feature = "rustls"))] +pub use server::tls; + +pub use viz_core::*; +pub use viz_router::*; + +#[cfg(feature = "handlers")] +#[cfg_attr(docsrs, doc(cfg(feature = "handlers")))] +#[doc(inline)] +pub use viz_handlers as handlers; + +#[cfg(feature = "macros")] +#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +#[doc(inline)] +pub use viz_macros::handler; diff --git a/viz-smol/src/listener.rs b/viz-smol/src/listener.rs new file mode 100644 index 00000000..900e41a4 --- /dev/null +++ b/viz-smol/src/listener.rs @@ -0,0 +1,20 @@ +/// A trait for a listener: `TcpListener` and `UnixListener`. +pub trait Listener { + /// The stream's type of this listener. + type Io; + /// The socket address type of this listener. + type Addr; + + /// Accepts a new incoming connection from this listener. + fn accept( + &self, + ) -> impl std::future::Future> + Send; + + /// Returns the local address that this listener is bound to. + /// + /// # Errors + /// + /// An error will return if got the socket address of the local half of this connection is + /// failed. + fn local_addr(&self) -> std::io::Result; +} diff --git a/viz-smol/src/responder.rs b/viz-smol/src/responder.rs new file mode 100644 index 00000000..8cffc890 --- /dev/null +++ b/viz-smol/src/responder.rs @@ -0,0 +1,62 @@ +use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc}; + +use crate::{Body, Handler, Incoming, IntoResponse, Method, Request, Response, StatusCode, Tree}; + +/// Handles the HTTP [`Request`] and retures the HTTP [`Response`]. +#[derive(Debug)] +pub struct Responder { + tree: Arc, + remote_addr: Option, +} + +impl Responder +where + A: Clone + Send + Sync + 'static, +{ + /// Creates a Responder for handling the [`Request`]. + #[must_use] + pub fn new(tree: Arc, remote_addr: Option) -> Self { + Self { tree, remote_addr } + } +} + +impl hyper::service::Service> for Responder +where + A: Clone + Send + Sync + 'static, +{ + type Response = Response; + type Error = Infallible; + type Future = Pin> + Send>>; + + fn call(&self, mut req: Request) -> Self::Future { + let method = req.method().clone(); + let path = req.uri().path().to_owned(); + + let Some((handler, route)) = self.tree.find(&method, &path).or_else(|| { + if method == Method::HEAD { + self.tree.find(&Method::GET, &path) + } else { + None + } + }) else { + return Box::pin(async move { Ok(StatusCode::NOT_FOUND.into_response()) }); + }; + + req.extensions_mut().insert(self.remote_addr.clone()); + req.extensions_mut() + .insert(Arc::from(crate::types::RouteInfo { + id: *route.id, + pattern: route.pattern(), + params: route.params().into(), + })); + + let handler = handler.clone(); + + Box::pin(async move { + Ok(handler + .call(req.map(Body::Incoming)) + .await + .unwrap_or_else(IntoResponse::into_response)) + }) + } +} diff --git a/viz-smol/src/server.rs b/viz-smol/src/server.rs new file mode 100644 index 00000000..b2360cff --- /dev/null +++ b/viz-smol/src/server.rs @@ -0,0 +1,89 @@ +use std::{borrow::Borrow, fmt::Debug, io, sync::Arc}; + +use async_executor::Executor; +use futures_lite::io::{AsyncRead, AsyncWrite}; +use hyper::rt::Timer; +use hyper_util::server::conn::auto::Builder; +use smol_hyper::rt::{FuturesIo, SmolExecutor, SmolTimer}; + +use crate::{Listener, Responder, Router, Tree}; + +#[cfg(any(feature = "http1", feature = "http2"))] +mod tcp; + +#[cfg(all(unix, feature = "unix-socket"))] +mod unix; + +/// TLS +#[cfg(any(feature = "native_tls", feature = "rustls"))] +pub mod tls; + +/// Serve a server with smol's networking types. +#[allow(clippy::missing_errors_doc)] +pub async fn serve<'ex, E, L>(executor: E, listener: L, router: Router) -> io::Result<()> +where + E: Borrow> + Clone + Send + 'ex, + L: Listener + Send + 'static, + L::Io: AsyncRead + AsyncWrite + Send + Unpin, + L::Addr: Send + Sync + Debug, +{ + let tree = Arc::::new(router.into()); + + loop { + // Wait for a new connection. + let (stream, remote_addr) = match listener.accept().await { + Ok(conn) => conn, + Err(e) => { + if !is_connection_error(&e) { + // [From `hyper::Server` in 0.14](https://github.com/hyperium/hyper/blob/v0.14.27/src/server/tcp.rs#L186) + tracing::error!("listener accept error: {e}"); + SmolTimer::new() + .sleep(std::time::Duration::from_secs(1)) + .await; + } + continue; + } + }; + + // Wrap it in a `FuturesIo`. + let io = FuturesIo::new(stream); + let remote_addr = Arc::new(remote_addr); + let responder = Responder::>::new(tree.clone(), Some(remote_addr.clone())); + + // Spawn the service on our executor. + let task = executor.borrow().spawn({ + let executor = executor.clone(); + async move { + let mut builder = Builder::new(SmolExecutor::new(AsRefExecutor(executor.borrow()))); + builder.http1().timer(SmolTimer::new()); + builder.http2().timer(SmolTimer::new()); + + if let Err(err) = builder.serve_connection_with_upgrades(io, responder).await { + tracing::error!("unintelligible hyper error: {err}"); + } + } + }); + + // Detach the task and let it run forever. + task.detach(); + } +} + +fn is_connection_error(e: &io::Error) -> bool { + matches!( + e.kind(), + io::ErrorKind::ConnectionRefused + | io::ErrorKind::ConnectionAborted + | io::ErrorKind::ConnectionReset + ) +} + +#[derive(Clone)] +struct AsRefExecutor<'this, 'ex>(&'this Executor<'ex>); + +impl<'ex> AsRef> for AsRefExecutor<'_, 'ex> { + #[inline] + fn as_ref(&self) -> &Executor<'ex> { + self.0 + } +} diff --git a/viz-smol/src/server/tcp.rs b/viz-smol/src/server/tcp.rs new file mode 100644 index 00000000..2eeb4e85 --- /dev/null +++ b/viz-smol/src/server/tcp.rs @@ -0,0 +1,16 @@ +use std::{future::Future, io::Result}; + +use async_net::{SocketAddr, TcpListener, TcpStream}; + +impl crate::Listener for TcpListener { + type Io = TcpStream; + type Addr = SocketAddr; + + fn accept(&self) -> impl Future> + Send { + TcpListener::accept(self) + } + + fn local_addr(&self) -> Result { + TcpListener::local_addr(self) + } +} diff --git a/viz/src/tls/listener.rs b/viz-smol/src/server/tls.rs similarity index 59% rename from viz/src/tls/listener.rs rename to viz-smol/src/server/tls.rs index 081d23a7..1491baa6 100644 --- a/viz/src/tls/listener.rs +++ b/viz-smol/src/server/tls.rs @@ -1,3 +1,5 @@ +//! A TLS listener wrapper. + /// Unified TLS listener type. #[derive(Debug)] pub struct TlsListener { @@ -13,4 +15,14 @@ impl TlsListener { acceptor: a, } } + + /// Gets the listener. + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Gets the acceptor. + pub fn get_acceptor(&self) -> &A { + &self.acceptor + } } diff --git a/viz-smol/src/server/unix.rs b/viz-smol/src/server/unix.rs new file mode 100644 index 00000000..29313d71 --- /dev/null +++ b/viz-smol/src/server/unix.rs @@ -0,0 +1,16 @@ +use std::{future::Future, io::Result}; + +use async_net::unix::{SocketAddr, UnixListener, UnixStream}; + +impl crate::Listener for UnixListener { + type Io = UnixStream; + type Addr = SocketAddr; + + fn accept(&self) -> impl Future> + Send { + UnixListener::accept(self) + } + + fn local_addr(&self) -> Result { + UnixListener::local_addr(self) + } +} diff --git a/viz-test/Cargo.toml b/viz-test/Cargo.toml index 99f1f31a..94887deb 100644 --- a/viz-test/Cargo.toml +++ b/viz-test/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["async", "framework", "http", "service", "web"] categories = ["asynchronous", "network-programming", "web-programming"] [dependencies] -viz.workspace = true +viz = { workspace = true, features = ["fs"] } bytes.workspace = true futures-util.workspace = true diff --git a/viz/Cargo.toml b/viz/Cargo.toml index 768d3ef2..68b86b85 100644 --- a/viz/Cargo.toml +++ b/viz/Cargo.toml @@ -14,43 +14,46 @@ categories = ["asynchronous", "network-programming", "web-programming"] [features] default = [ - "http1", "state", "limits", "query", "form", "json", - "params", "multipart", + "params", "cookie", "session", - "fs", -] -http1 = ["hyper/http1"] -http2 = ["hyper/http2"] + "http1", +] state = ["viz-core/state"] limits = ["viz-core/limits"] + query = ["viz-core/query"] form = ["viz-core/form"] json = ["viz-core/json"] -params = ["viz-core/params"] multipart = ["viz-core/multipart"] -websocket = ["viz-core/websocket"] -sse = ["viz-core/sse"] -fs = ["viz-core/fs"] +params = ["viz-core/params"] cookie = ["viz-core/cookie"] cookie-private = ["viz-core/cookie-private"] cookie-signed = ["viz-core/cookie-signed"] session = ["cookie", "cookie-private", "viz-core/session"] + +websocket = ["viz-core/websocket"] +sse = ["viz-core/sse"] +fs = ["viz-core/fs"] + csrf = ["cookie", "cookie-private", "viz-core/csrf"] cors = ["viz-core/cors"] compression = ["viz-core/compression"] +http1 = ["hyper/http1"] +http2 = ["hyper/http2"] + unix-socket = [] macros = ["dep:viz-macros"] @@ -64,8 +67,8 @@ otel-tracing = ["otel", "viz-core/otel-tracing"] otel-metrics = ["otel", "viz-core/otel-metrics"] otel-prometheus = ["handlers", "viz-handlers?/prometheus"] -rustls = ["dep:rustls-pemfile", "dep:tokio-rustls", "dep:futures-util"] -native-tls = ["dep:tokio-native-tls", "dep:futures-util"] +rustls = ["dep:rustls-pemfile", "dep:futures-util", "dep:tokio-rustls"] +native-tls = ["dep:futures-util", "dep:tokio-native-tls"] [dependencies] viz-core.workspace = true @@ -77,6 +80,7 @@ hyper.workspace = true hyper-util.workspace = true futures-util = { workspace = true, optional = true } +tracing.workspace = true rustls-pemfile = { workspace = true, optional = true } @@ -84,7 +88,6 @@ tokio-native-tls = { workspace = true, optional = true } tokio-rustls = { workspace = true, optional = true } tokio = { workspace = true, features = ["macros"] } tokio-util = { workspace = true, features = ["net"] } -tracing.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] } diff --git a/viz/src/lib.rs b/viz/src/lib.rs index 2444960f..b110a694 100644 --- a/viz/src/lib.rs +++ b/viz/src/lib.rs @@ -525,12 +525,14 @@ mod responder; pub use responder::Responder; +mod listener; +pub use listener::Listener; + mod server; -pub use server::{serve, Listener, Server}; +pub use server::{serve, Server}; -/// TLS #[cfg(any(feature = "native_tls", feature = "rustls"))] -pub mod tls; +pub use server::tls; pub use viz_core::*; pub use viz_router::*; diff --git a/viz/src/listener.rs b/viz/src/listener.rs new file mode 100644 index 00000000..900e41a4 --- /dev/null +++ b/viz/src/listener.rs @@ -0,0 +1,20 @@ +/// A trait for a listener: `TcpListener` and `UnixListener`. +pub trait Listener { + /// The stream's type of this listener. + type Io; + /// The socket address type of this listener. + type Addr; + + /// Accepts a new incoming connection from this listener. + fn accept( + &self, + ) -> impl std::future::Future> + Send; + + /// Returns the local address that this listener is bound to. + /// + /// # Errors + /// + /// An error will return if got the socket address of the local half of this connection is + /// failed. + fn local_addr(&self) -> std::io::Result; +} diff --git a/viz/src/responder.rs b/viz/src/responder.rs index 624933cc..8cffc890 100644 --- a/viz/src/responder.rs +++ b/viz/src/responder.rs @@ -1,9 +1,6 @@ use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc}; -use crate::{ - types::RouteInfo, Body, Handler, Incoming, IntoResponse, Method, Request, Response, StatusCode, - Tree, -}; +use crate::{Body, Handler, Incoming, IntoResponse, Method, Request, Response, StatusCode, Tree}; /// Handles the HTTP [`Request`] and retures the HTTP [`Response`]. #[derive(Debug)] @@ -46,11 +43,12 @@ where }; req.extensions_mut().insert(self.remote_addr.clone()); - req.extensions_mut().insert(Arc::from(RouteInfo { - id: *route.id, - pattern: route.pattern(), - params: route.params().into(), - })); + req.extensions_mut() + .insert(Arc::from(crate::types::RouteInfo { + id: *route.id, + pattern: route.pattern(), + params: route.params().into(), + })); let handler = handler.clone(); diff --git a/viz/src/server.rs b/viz/src/server.rs index 6999db2d..bf61ba60 100644 --- a/viz/src/server.rs +++ b/viz/src/server.rs @@ -6,16 +6,19 @@ use std::{ sync::Arc, }; -use hyper_util::{ - rt::{TokioExecutor, TokioIo}, - server::conn::auto::Builder, +use hyper_util::rt::{TokioExecutor, TokioIo}; +use hyper_util::server::conn::auto::Builder; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + pin, select, + sync::watch, }; -use tokio::{pin, select, sync::watch}; -use crate::{future::FutureExt, Responder, Router, Tree}; +use crate::{future::FutureExt, Listener, Responder, Router}; -mod listener; -pub use listener::Listener; +/// TLS +#[cfg(any(feature = "native_tls", feature = "rustls"))] +pub mod tls; #[cfg(any(feature = "http1", feature = "http2"))] mod tcp; @@ -24,58 +27,63 @@ mod tcp; mod unix; /// Starts a server and serves the connections. -pub fn serve(listener: L, router: Router) -> Server -where - L: Listener + Send + 'static, - L::Io: Send + Unpin, - L::Addr: Send + Sync + Debug, -{ - Server::::new(listener, router) +pub fn serve( + listener: L, + router: Router, +) -> Server Builder, Pending<()>> { + Server:: Builder, Pending<()>>::new( + TokioExecutor::new(), + listener, + router, + |executor: TokioExecutor| Builder::new(executor), + ) } /// A listening HTTP server that accepts connections. #[derive(Debug)] -pub struct Server> { - signal: F, - tree: Tree, +pub struct Server { listener: L, - builder: Builder, + executor: E, + build: F, + signal: S, + tree: crate::Tree, } -impl Server { - /// Starts a [`Server`] with a listener and a [`Tree`]. - pub fn new(listener: L, router: Router) -> Server { +impl Server { + /// Starts a [`Server`] with a listener and a [`Router`]. + pub fn new(executor: E, listener: L, router: Router, build: F) -> Server> + where + F: Fn(E) -> Builder + Send + 'static, + { Server { + build, + executor, listener, signal: pending(), tree: router.into(), - builder: Builder::new(TokioExecutor::new()), } } /// Changes the signal for graceful shutdown. - pub fn signal(self, signal: T) -> Server { + pub fn signal(self, signal: X) -> Server { Server { signal, tree: self.tree, - builder: self.builder, + build: self.build, + executor: self.executor, listener: self.listener, } } - - /// Returns the HTTP1 or HTTP2 connection builder. - pub fn builder(&mut self) -> &mut Builder { - &mut self.builder - } } /// Copied from Axum. Thanks. -impl IntoFuture for Server +impl IntoFuture for Server where L: Listener + Send + 'static, - L::Io: Send + Unpin, + L::Io: AsyncRead + AsyncWrite + Send + Unpin, L::Addr: Send + Sync + Debug, - F: Future + Send + 'static, + F: Fn(TokioExecutor) -> Builder + Send + 'static, + S: Future + Send + 'static, { type Output = io::Result<()>; type IntoFuture = Pin + Send>>; @@ -83,8 +91,9 @@ where fn into_future(self) -> Self::IntoFuture { let Self { tree, + build, signal, - builder, + executor, listener, } = self; @@ -127,7 +136,7 @@ where let io = TokioIo::new(stream); let remote_addr = Arc::new(remote_addr); - let builder = builder.clone(); + let builder = (build)(executor.clone()); let responder = Responder::>::new(tree.clone(), Some(remote_addr.clone())); diff --git a/viz/src/server/tls.rs b/viz/src/server/tls.rs new file mode 100644 index 00000000..2e32725a --- /dev/null +++ b/viz/src/server/tls.rs @@ -0,0 +1,36 @@ +//! A TLS listener wrapper. + +/// `native_tls` +#[cfg(feature = "native-tls")] +pub mod native_tls; + +/// `rustls` +#[cfg(feature = "rustls")] +pub mod rustls; + +/// Unified TLS listener type. +#[derive(Debug)] +pub struct TlsListener { + pub(crate) inner: T, + pub(crate) acceptor: A, +} + +impl TlsListener { + /// Creates a new TLS listener. + pub fn new(t: T, a: A) -> Self { + Self { + inner: t, + acceptor: a, + } + } + + /// Gets the listener. + pub fn get_ref(&self) -> &T { + &self.inner + } + + /// Gets the acceptor. + pub fn get_acceptor(&self) -> &A { + &self.acceptor + } +} diff --git a/viz/src/tls/native_tls.rs b/viz/src/server/tls/native_tls.rs similarity index 95% rename from viz/src/tls/native_tls.rs rename to viz/src/server/tls/native_tls.rs index f727f661..bdd79c57 100644 --- a/viz/src/tls/native_tls.rs +++ b/viz/src/server/tls/native_tls.rs @@ -37,7 +37,7 @@ impl Config { } } -impl crate::Listener for super::TlsListener { +impl crate::Listener for crate::tls::TlsListener { type Io = TlsStream; type Addr = SocketAddr; diff --git a/viz/src/tls/rustls.rs b/viz/src/server/tls/rustls.rs similarity index 98% rename from viz/src/tls/rustls.rs rename to viz/src/server/tls/rustls.rs index 3144bdd1..1a84a25c 100644 --- a/viz/src/tls/rustls.rs +++ b/viz/src/server/tls/rustls.rs @@ -148,7 +148,7 @@ impl Config { } } -impl crate::Listener for super::TlsListener { +impl crate::Listener for crate::tls::TlsListener { type Io = TlsStream; type Addr = SocketAddr; diff --git a/viz/src/tls.rs b/viz/src/tls.rs deleted file mode 100644 index 0a53abcc..00000000 --- a/viz/src/tls.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod listener; - -pub use listener::TlsListener; - -/// `native_tls` -#[cfg(feature = "native-tls")] -pub mod native_tls; - -/// `rustls` -#[cfg(feature = "rustls")] -pub mod rustls;