-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
435 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "smol-example" | ||
version = "0.1.0" | ||
edition.workspace = true | ||
publish = false | ||
|
||
[dependencies] | ||
viz = { workspace = true, features = ["smol"] } | ||
|
||
# smol | ||
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use std::io; | ||
use std::sync::Arc; | ||
|
||
use async_net::TcpListener; | ||
use macro_rules_attribute::apply; | ||
use viz::{IntoResponse, Request, Response, Result, Router}; | ||
|
||
#[apply(smol_macros::main!)] | ||
async fn main(ex: &Arc<smol_macros::Executor<'_>>) -> 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::serve(ex.clone(), listener, app).await | ||
} | ||
|
||
async fn handler(_: Request) -> Result<Response> { | ||
Ok("<h1>Hello, World!</h1>".into_response()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,188 +1,53 @@ | ||
use std::{ | ||
fmt::Debug, | ||
future::{pending, Future, IntoFuture, Pending}, | ||
io, | ||
pin::Pin, | ||
sync::Arc, | ||
}; | ||
|
||
use hyper_util::{ | ||
rt::{TokioExecutor, TokioIo}, | ||
server::conn::auto::Builder, | ||
}; | ||
use tokio::{pin, select, sync::watch}; | ||
|
||
use crate::{future::FutureExt, Responder, Router, Tree}; | ||
use std::{fmt::Debug, future::Pending}; | ||
|
||
mod listener; | ||
pub use listener::Listener; | ||
|
||
#[cfg(any(feature = "http1", feature = "http2"))] | ||
mod tcp; | ||
#[cfg(not(feature = "smol"))] | ||
mod tokio; | ||
#[cfg(not(feature = "smol"))] | ||
pub use self::tokio::serve; | ||
|
||
#[cfg(feature = "smol")] | ||
mod smol; | ||
#[cfg(feature = "smol")] | ||
pub use self::smol::serve; | ||
|
||
#[cfg(any(feature = "native_tls", feature = "rustls"))] | ||
#[path = "server/tls.rs"] | ||
pub(super) mod internal; | ||
|
||
/// TLS | ||
#[cfg(any(feature = "native_tls", feature = "rustls"))] | ||
pub mod tls { | ||
pub use super::internal::*; | ||
|
||
#[cfg(all(unix, feature = "unix-socket"))] | ||
mod unix; | ||
#[cfg(not(feature = "smol"))] | ||
pub use super::tokio::tls::*; | ||
|
||
/// Starts a server and serves the connections. | ||
pub fn serve<L>(listener: L, router: Router) -> Server<L> | ||
where | ||
L: Listener + Send + 'static, | ||
L::Io: Send + Unpin, | ||
L::Addr: Send + Sync + Debug, | ||
{ | ||
Server::<L>::new(listener, router) | ||
#[cfg(feature = "smol")] | ||
pub use super::smol::tls::*; | ||
} | ||
|
||
/// A listening HTTP server that accepts connections. | ||
#[derive(Debug)] | ||
pub struct Server<L, E = TokioExecutor, F = Pending<()>> { | ||
signal: F, | ||
tree: Tree, | ||
pub struct Server<L, E, F, S = Pending<()>> { | ||
signal: S, | ||
tree: crate::Tree, | ||
executor: E, | ||
listener: L, | ||
builder: Builder<E>, | ||
build: F, | ||
} | ||
|
||
impl<L, E, F> Server<L, E, F> { | ||
/// Starts a [`Server`] with a listener and a [`Tree`]. | ||
pub fn new(listener: L, router: Router) -> Server<L> { | ||
Server { | ||
listener, | ||
signal: pending(), | ||
tree: router.into(), | ||
builder: Builder::new(TokioExecutor::new()), | ||
} | ||
} | ||
|
||
impl<L, E, F, S> Server<L, E, F, S> { | ||
/// Changes the signal for graceful shutdown. | ||
pub fn signal<T>(self, signal: T) -> Server<L, E, T> { | ||
pub fn signal<X>(self, signal: X) -> Server<L, E, F, X> { | ||
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<E> { | ||
&mut self.builder | ||
} | ||
} | ||
|
||
/// Copied from Axum. Thanks. | ||
impl<L, F> IntoFuture for Server<L, TokioExecutor, F> | ||
where | ||
L: Listener + Send + 'static, | ||
L::Io: Send + Unpin, | ||
L::Addr: Send + Sync + Debug, | ||
F: Future + Send + 'static, | ||
{ | ||
type Output = io::Result<()>; | ||
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>; | ||
|
||
fn into_future(self) -> Self::IntoFuture { | ||
let Self { | ||
tree, | ||
signal, | ||
builder, | ||
listener, | ||
} = self; | ||
|
||
let (shutdown_tx, shutdown_rx) = watch::channel(()); | ||
let shutdown_tx = Arc::new(shutdown_tx); | ||
|
||
tokio::spawn(async move { | ||
signal.await; | ||
tracing::trace!("received graceful shutdown signal"); | ||
drop(shutdown_rx); | ||
}); | ||
|
||
let (close_tx, close_rx) = watch::channel(()); | ||
|
||
let tree = Arc::new(tree); | ||
|
||
Box::pin(async move { | ||
loop { | ||
let (stream, remote_addr) = select! { | ||
res = listener.accept() => { | ||
match res { | ||
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}"); | ||
tokio::time::sleep(std::time::Duration::from_secs(1)).await; | ||
} | ||
continue | ||
} | ||
} | ||
} | ||
() = shutdown_tx.closed() => { | ||
tracing::trace!("server is closing"); | ||
break; | ||
} | ||
}; | ||
|
||
tracing::trace!("connection {:?} accepted", remote_addr); | ||
|
||
let io = TokioIo::new(stream); | ||
let remote_addr = Arc::new(remote_addr); | ||
let builder = builder.clone(); | ||
let responder = | ||
Responder::<Arc<L::Addr>>::new(tree.clone(), Some(remote_addr.clone())); | ||
|
||
let shutdown_tx = Arc::clone(&shutdown_tx); | ||
let close_rx = close_rx.clone(); | ||
|
||
tokio::spawn(async move { | ||
let conn = builder.serve_connection_with_upgrades(io, responder); | ||
pin!(conn); | ||
|
||
let shutdown = shutdown_tx.closed().fuse(); | ||
pin!(shutdown); | ||
|
||
loop { | ||
select! { | ||
res = conn.as_mut() => { | ||
if let Err(e) = res { | ||
tracing::error!("connection failed: {e}"); | ||
} | ||
break; | ||
} | ||
() = &mut shutdown => { | ||
tracing::trace!("connection is starting to graceful shutdown"); | ||
conn.as_mut().graceful_shutdown(); | ||
} | ||
} | ||
} | ||
|
||
tracing::trace!("connection {:?} closed", remote_addr); | ||
|
||
drop(close_rx); | ||
}); | ||
} | ||
|
||
drop(close_rx); | ||
drop(listener); | ||
|
||
tracing::trace!( | ||
"waiting for {} task(s) to finish", | ||
close_tx.receiver_count() | ||
); | ||
close_tx.closed().await; | ||
|
||
tracing::trace!("server shutdown complete"); | ||
|
||
Ok(()) | ||
}) | ||
} | ||
} | ||
|
||
fn is_connection_error(e: &io::Error) -> bool { | ||
matches!( | ||
e.kind(), | ||
io::ErrorKind::ConnectionRefused | ||
| io::ErrorKind::ConnectionAborted | ||
| io::ErrorKind::ConnectionReset | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.