Skip to content

Commit

Permalink
docs(example): add an example for server using auto conn (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
CobaltCause authored Mar 17, 2024
1 parent 99b77a5 commit 16daef6
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ __internal_happy_eyeballs_tests = []
name = "client"
required-features = ["client-legacy", "http1", "tokio"]

[[example]]
name = "server"
required-features = ["server", "http1", "tokio"]
75 changes: 75 additions & 0 deletions examples/server.rs
Original file line number Diff line number Diff line change
@@ -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<Incoming>,
) -> Result<Response<BoxBody<Bytes, Infallible>>, 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<dyn Error + Send + Sync + 'static>> {
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 {}
}

0 comments on commit 16daef6

Please sign in to comment.