Skip to content

Commit

Permalink
Introduce ToListener and Listener
Browse files Browse the repository at this point in the history
  • Loading branch information
jbr committed Jun 24, 2020
1 parent 2c26085 commit 69702cf
Show file tree
Hide file tree
Showing 12 changed files with 931 additions and 228 deletions.
29 changes: 29 additions & 0 deletions examples/catflap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#[cfg(unix)]
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
use std::{env, net::TcpListener, os::unix::io::FromRawFd};
tide::log::start();
let mut app = tide::new();
app.at("/").get(|_| async { Ok(CHANGE_THIS_TEXT) });

const CHANGE_THIS_TEXT: &str = "hello world!";

const DOCS: &str = "
To run this example:
$ cargo install catflap cargo-watch
$ catflap -- cargo watch -x \"run --example catflap\"
and then edit this file";

if let Some(fd) = env::var("LISTEN_FD").ok().and_then(|fd| fd.parse().ok()) {
app.listen(unsafe { TcpListener::from_raw_fd(fd) }).await?;
} else {
println!("{} ({})", DOCS, file!());
}
Ok(())
}

#[cfg(not(unix))]
fn main() {
panic!("this example only runs on cfg(unix) systems");
}
16 changes: 16 additions & 0 deletions examples/multi_listener.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
tide::log::start();
let mut app = tide::new();

app.at("/").get(|request: tide::Request<_>| async move {
Ok(format!(
"Hi! You reached this app through: {}",
request.local_addr().unwrap_or("an unknown port")
))
});

app.listen(vec!["localhost:8000", "localhost:8081"]).await?;

Ok(())
}
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
//! [`Request::ext`].
//!
//! If the endpoint needs to share values with middleware, response scoped state can be set via
//! [`Response::set_ext`] and is available through [`Response::ext`].
//! [`Response::insert_ext`] and is available through [`Response::ext`].
//!
//! Application scoped state is used when a complete application needs access to a particular
//! value. Examples of this include: database connections, websocket connections, or
Expand Down Expand Up @@ -207,6 +207,7 @@ pub mod router;
mod server;

pub mod convert;
pub mod listener;
pub mod log;
pub mod prelude;
pub mod security;
Expand Down
44 changes: 44 additions & 0 deletions src/listener/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Types that represent HTTP transports and binding

mod multi_listener;
mod parsed_listener;
mod tcp_listener;
mod to_listener;
#[cfg(unix)]
mod unix_listener;

use crate::utils::BoxFuture;
use crate::Server;
use async_std::io;

pub use multi_listener::MultiListener;
pub use to_listener::ToListener;

pub(crate) use parsed_listener::ParsedListener;
pub(crate) use tcp_listener::TcpListener;
#[cfg(unix)]
pub(crate) use unix_listener::UnixListener;

/// The Listener trait represents an implementation of http transport
/// for a tide application. In order to provide a Listener to tide,
/// you will also need to implement at least one [`ToListener`](crate::listener::ToListener) that
/// outputs your Listener type.
pub trait Listener<State: 'static>:
std::fmt::Debug + std::fmt::Display + Send + Sync + 'static
{
/// This is the primary entrypoint for the Listener trait. listen
/// is called exactly once, and is expected to spawn tasks for
/// each incoming connection.
fn listen<'a>(&'a self, app: Server<State>) -> BoxFuture<'a, io::Result<()>>;

/// Connect provides an opportunity to resolve any addresses, and
/// mutate self. This is called before
/// [listen](crate::listener::Listener::listen).
fn connect<'a>(&'a mut self) -> BoxFuture<'a, io::Result<()>>;
}

pub(crate) fn is_transient_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}
144 changes: 144 additions & 0 deletions src/listener/multi_listener.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use crate::listener::{Listener, ToListener};
use crate::utils::BoxFuture;
use crate::Server;

use std::fmt::{self, Debug, Display, Formatter};

use async_std::io;
use async_std::prelude::*;

/// MultiListener allows tide to listen on any number of transports
/// simultaneously (such as tcp ports, unix sockets, or tls).
///
/// # Example:
/// ```rust
/// fn main() -> Result<(), std::io::Error> {
/// async_std::task::block_on(async {
/// tide::log::start();
/// let mut app = tide::new();
/// app.at("/").get(|_| async { Ok("Hello, world!") });
///
/// let mut multi = tide::listener::MultiListener::new();
/// multi.add("127.0.0.1:8000")?;
/// multi.add(async_std::net::TcpListener::bind("127.0.0.1:8001").await?)?;
/// # if cfg!(unix) {
/// multi.add("unix://unix.socket")?;
/// # }
///
/// # if false {
/// app.listen(multi).await?;
/// # }
/// Ok(())
/// })
///}
///```

pub struct MultiListener<State>(Vec<Box<dyn Listener<State>>>);

impl<State: Send + Sync + 'static> MultiListener<State> {
/// creates a new MultiListener
pub fn new() -> Self {
Self(vec![])
}

/// Adds any [`ToListener`](crate::listener::ToListener) to this
/// MultiListener. An error result represents a failure to convert
/// the [`ToListener`](crate::listener::ToListener) into a
/// [`Listener`](crate::listener::Listener).
///
/// ```rust
/// # fn main() -> std::io::Result<()> {
/// let mut multi = tide::listener::MultiListener::new();
/// multi.add("127.0.0.1:8000")?;
/// multi.add(("localhost", 8001))?;
/// multi.add(std::net::TcpListener::bind(("localhost", 8002))?)?;
/// # std::mem::drop(tide::new().listen(multi)); // for the State generic
/// # Ok(()) }
/// ```
pub fn add<TL: ToListener<State>>(&mut self, listener: TL) -> io::Result<()> {
self.0.push(Box::new(listener.to_listener()?));
Ok(())
}

/// `MultiListener::with` allows for chained construction of a MultiListener:
/// ```rust,no_run
/// # use tide::listener::MultiListener;
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async move {
/// # let app = tide::new();
/// app.listen(
/// MultiListener::new()
/// .with("127.0.0.1:8080")
/// .with(async_std::net::TcpListener::bind("127.0.0.1:8081").await?),
/// ).await?;
/// # Ok(()) }) }
pub fn with<TL: ToListener<State>>(mut self, listener: TL) -> Self {
self.add(listener).expect("Unable to add listener");
self
}

/// from_iter allows for the construction of a new MultiListener
/// from collections of [`ToListener`](ToListener)s.
/// ```rust
/// # use tide::listener::MultiListener;
/// # fn main() -> std::io::Result<()> {
/// let mut multi = MultiListener::from_iter(vec!["127.0.0.1:8000", "tcp://localhost:8001"])?;
/// if cfg!(unix) {
/// multi.add("unix:///var/run/tide/socket")?;
/// }
/// # std::mem::drop(tide::new().listen(multi)); // for the State generic
/// # Ok(()) }
/// ```
pub fn from_iter<TL: ToListener<State>>(vec: impl IntoIterator<Item = TL>) -> io::Result<Self> {
let mut multi = Self::new();
for listener in vec {
multi.add(listener)?;
}
Ok(multi)
}
}

impl<State: Send + Sync + 'static> Listener<State> for MultiListener<State> {
fn connect<'a>(&'a mut self) -> BoxFuture<'a, io::Result<()>> {
Box::pin(async move {
for listener in self.0.iter_mut() {
listener.connect().await?;
}
Ok(())
})
}

fn listen<'a>(&'a self, app: Server<State>) -> BoxFuture<'a, io::Result<()>> {
let mut fut: Option<BoxFuture<'a, io::Result<()>>> = None;

for listener in self.0.iter() {
let app = app.clone();
let listened = listener.listen(app);
if let Some(f) = fut {
fut = Some(Box::pin(f.race(listened)));
} else {
fut = Some(Box::pin(listened));
}
}

fut.expect("at least one listener must be provided to a MultiListener")
}
}

impl<State> Debug for MultiListener<State> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}

impl<State> Display for MultiListener<State> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let string = self
.0
.iter()
.map(|l| l.to_string())
.collect::<Vec<_>>()
.join(", ");

writeln!(f, "{}", string)
}
}
41 changes: 41 additions & 0 deletions src/listener/parsed_listener.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#[cfg(unix)]
use super::UnixListener;
use super::{Listener, TcpListener};
use crate::{utils::BoxFuture, Server};
use async_std::io;
use std::fmt::{self, Display, Formatter};

#[derive(Debug)]
pub enum ParsedListener {
#[cfg(unix)]
Unix(UnixListener),
Tcp(TcpListener),
}

impl Display for ParsedListener {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
#[cfg(unix)]
Self::Unix(u) => write!(f, "{}", u),
Self::Tcp(t) => write!(f, "{}", t),
}
}
}

impl<State: Send + Sync + 'static> Listener<State> for ParsedListener {
fn connect<'a>(&'a mut self) -> BoxFuture<'a, io::Result<()>> {
match self {
#[cfg(unix)]
Self::Unix(u) => Listener::<State>::connect(u),
Self::Tcp(t) => Listener::<State>::connect(t),
}
}

fn listen<'a>(&'a self, app: Server<State>) -> BoxFuture<'a, io::Result<()>> {
match self {
#[cfg(unix)]
Self::Unix(u) => u.listen(app),
Self::Tcp(t) => t.listen(app),
}
}
}
Loading

0 comments on commit 69702cf

Please sign in to comment.