diff --git a/Cargo.toml b/Cargo.toml index 1937a147..207c6bed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,3 +113,7 @@ required-features = ["server", "http1", "tokio"] [[example]] name = "server_graceful" required-features = ["tokio", "server-graceful", "server-auto"] + +[[example]] +name = "single_threaded_server" +required-features = ["server", "http1", "http2", "tokio"] diff --git a/examples/single_threaded_server.rs b/examples/single_threaded_server.rs new file mode 100644 index 00000000..954a18a5 --- /dev/null +++ b/examples/single_threaded_server.rs @@ -0,0 +1,64 @@ +//! This example runs a server that responds to any request with "Hello, world!". +//! +//! Unlike its `server.rs` counter part, this server runs on a local single-threaded runtime, +//! and uses a `!Send` executor. This pattern is useful for running hyper servers on asynchronous +//! runtimes like gloomio or monoio. + +use bytes::Bytes; +use http::{Request, Response, header::CONTENT_TYPE}; +use http_body_util::Full; +use hyper::{body::Incoming, service::service_fn}; +use hyper_util::{ + rt::{TokioIo, tokio::TokioLocalExecutor}, + server::conn::auto::Builder, +}; +use std::{convert::Infallible, error::Error}; +use tokio::net::TcpListener; + +/// Function from an incoming request to an outgoing response +/// +/// This function gets turned into a [`hyper::service::Service`] later via +/// [`service_fn`]. Instead of doing this, you could also write a type that +/// implements [`hyper::service::Service`] directly and pass that in place of +/// writing a function like this and calling [`service_fn`]. +async fn handle_request(_request: Request) -> Result>, Infallible> { + let response = Response::builder() + .header(CONTENT_TYPE, "text/plain") + .body(Full::new(Bytes::from("Hello, world!\n"))) + .expect("values provided to the builder should be valid"); + + Ok(response) +} + +#[tokio::main(flavor = "local")] +async fn main() -> Result<(), Box> { + let listen_addr = "127.0.0.1:8000"; + let tcp_listener = TcpListener::bind(listen_addr).await?; + println!("listening on http://{listen_addr}"); + + loop { + let (stream, addr) = match tcp_listener.accept().await { + Ok(x) => x, + Err(e) => { + eprintln!("failed to accept connection: {e}"); + continue; + } + }; + + let serve_connection = async move { + println!("handling a request from {addr}"); + + let result = Builder::new(TokioLocalExecutor::new()) + .serve_connection(TokioIo::new(stream), service_fn(handle_request)) + .await; + + if let Err(e) = result { + eprintln!("error serving {addr}: {e}"); + } + + println!("handled a request from {addr}"); + }; + + tokio::task::spawn_local(serve_connection); + } +}