diff --git a/Cargo.toml b/Cargo.toml index 24c6583..a421a7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,3 +76,6 @@ __internal_happy_eyeballs_tests = [] name = "client" required-features = ["client-legacy", "http1", "tokio"] +[[example]] +name = "server" +required-features = ["server", "http1", "tokio"] diff --git a/examples/server.rs b/examples/server.rs new file mode 100644 index 0000000..48806d8 --- /dev/null +++ b/examples/server.rs @@ -0,0 +1,75 @@ +//! This example runs a server that responds to any request with "Hello, world!" + +use std::{convert::Infallible, error::Error}; + +use bytes::Bytes; +use http::{header::CONTENT_TYPE, Request, Response}; +use http_body_util::{combinators::BoxBody, BodyExt, Full}; +use hyper::{body::Incoming, service::service_fn}; +use hyper_util::{ + rt::{TokioExecutor, TokioIo}, + server::conn::auto::Builder, +}; +use tokio::{net::TcpListener, task::JoinSet}; + +/// 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`]. +/// +/// This function could use [`Full`] as the body type directly since that's +/// the only type that can be returned in this case, but this uses [`BoxBody`] +/// anyway for demonstration purposes, since this is what's usually used when +/// writing a more complex webserver library. +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")).boxed()) + .expect("values provided to the builder should be valid"); + + Ok(response) +} + +#[tokio::main(flavor = "current_thread")] +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}"); + + let mut join_set = JoinSet::new(); + 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(TokioExecutor::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}"); + }; + + join_set.spawn(serve_connection); + } + + // If you add a method for breaking the above loop (i.e. graceful shutdown), + // then you may also want to wait for all existing connections to finish + // being served before terminating the program, which can be done like this: + // + // while let Some(_) = join_set.join_next().await {} +}