Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an example showing how to make a server #110

Merged
merged 1 commit into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {}
}