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

"Example server" example in the docs doesn't actually run a server. #509

Open
kevincox opened this issue Sep 5, 2024 · 2 comments
Open
Labels
C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. T-docs Topic: documentation

Comments

@kevincox
Copy link

kevincox commented Sep 5, 2024

Bug Report

The docs have a section "Example server" section which claims to "run that service using hyper."

//! # Example server
//!
//! This example shows how to apply middleware from tower-http to a [`Service`] and then run
//! that service using [hyper].
//!
//! ```rust,no_run
//! use tower_http::{
//! add_extension::AddExtensionLayer,
//! compression::CompressionLayer,
//! propagate_header::PropagateHeaderLayer,
//! sensitive_headers::SetSensitiveRequestHeadersLayer,
//! set_header::SetResponseHeaderLayer,
//! trace::TraceLayer,
//! validate_request::ValidateRequestHeaderLayer,
//! };
//! use tower::{ServiceBuilder, service_fn, BoxError};
//! use http::{Request, Response, header::{HeaderName, CONTENT_TYPE, AUTHORIZATION}};
//! use std::{sync::Arc, net::SocketAddr, convert::Infallible, iter::once};
//! use bytes::Bytes;
//! use http_body_util::Full;
//! # struct DatabaseConnectionPool;
//! # impl DatabaseConnectionPool {
//! # fn new() -> DatabaseConnectionPool { DatabaseConnectionPool }
//! # }
//! # fn content_length_from_response<B>(_: &http::Response<B>) -> Option<http::HeaderValue> { None }
//! # async fn update_in_flight_requests_metric(count: usize) {}
//!
//! // Our request handler. This is where we would implement the application logic
//! // for responding to HTTP requests...
//! async fn handler(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, BoxError> {
//! // ...
//! # todo!()
//! }
//!
//! // Shared state across all request handlers --- in this case, a pool of database connections.
//! struct State {
//! pool: DatabaseConnectionPool,
//! }
//!
//! #[tokio::main]
//! async fn main() {
//! // Construct the shared state.
//! let state = State {
//! pool: DatabaseConnectionPool::new(),
//! };
//!
//! // Use tower's `ServiceBuilder` API to build a stack of tower middleware
//! // wrapping our request handler.
//! let service = ServiceBuilder::new()
//! // Mark the `Authorization` request header as sensitive so it doesn't show in logs
//! .layer(SetSensitiveRequestHeadersLayer::new(once(AUTHORIZATION)))
//! // High level logging of requests and responses
//! .layer(TraceLayer::new_for_http())
//! // Share an `Arc<State>` with all requests
//! .layer(AddExtensionLayer::new(Arc::new(state)))
//! // Compress responses
//! .layer(CompressionLayer::new())
//! // Propagate `X-Request-Id`s from requests to responses
//! .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id")))
//! // If the response has a known size set the `Content-Length` header
//! .layer(SetResponseHeaderLayer::overriding(CONTENT_TYPE, content_length_from_response))
//! // Authorize requests using a token
//! .layer(ValidateRequestHeaderLayer::bearer("passwordlol"))
//! // Accept only application/json, application/* and */* in a request's ACCEPT header
//! .layer(ValidateRequestHeaderLayer::accept("application/json"))
//! // Wrap a `Service` in our middleware stack
//! .service_fn(handler);
//! # let mut service = service;
//! # tower::Service::call(&mut service, Request::new(Full::default()));
//! }
//! ```
//!
//! Keep in mind that while this example uses [hyper], tower-http supports any HTTP
//! client/server implementation that uses the [http] and [http-body] crates.

However this doesn't seem to use Hyper at all and doesn't actually run any server. There is just hidden code to send a request to the Service from Rust.

Description

It seems that either the wording around the example should be updated or the example should be fixed to actually run a server.

It would be nice to have a minimal server example, as it seems like right now the way to do this is to use a "full featured" framework like axum or warp just to run a tower Service.

@jplatte
Copy link
Collaborator

jplatte commented Sep 5, 2024

I think this is as minimal as it gets when not using something like axum / warp.

Want to incorporate that into the docs in a PR?

@jplatte jplatte added C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. T-docs Topic: documentation labels Sep 5, 2024
@francoposa
Copy link

francoposa commented Sep 29, 2024

similar minimal version but without serving a file:

main.rs:

use std::convert::Infallible;
use std::net::SocketAddr;

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Request, Response};
use tokio::net::TcpListener;
use tower::ServiceBuilder;

async fn handle(_req: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
    Ok(Response::new(Full::new(Bytes::from("hello, world"))))
}

#[tokio::main]
async fn main() {
    let tower_service = ServiceBuilder::new().service_fn(handle);
    let hyper_service = hyper_util::service::TowerToHyperService::new(tower_service);

    let addr = SocketAddr::from(([0, 0, 0, 0], 5000));
    let listener = TcpListener::bind(addr).await.unwrap();

    loop {
        let (stream, _) = listener.accept().await.unwrap();

        let io = hyper_util::rt::TokioIo::new(stream);
        let service_clone = hyper_service.clone();

        tokio::task::spawn(async move {
            if let Err(err) =
                hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new())
                    .serve_connection(io, service_clone)
                    .await
            {
                eprintln!("server error: {}", err);
            }
        });
    }
}

Cargo.toml:

[package]
name = "hyper-hello-world"
version = "0.1.0"
edition = "2021"

[dependencies]
bytes = { version = "1", default-features = false }
hyper = { version = "1", default-features = false }
http-body-util = { version = "0.1", default-features = false }
hyper-util = { version = "0.1", features = ["http1", "service", "server", "tokio"], default-features = false }
tokio = { version = "1", features = ["rt-multi-thread", "macros"], default-features = false }
tower = { version = "0.5", default-features = false, features = ["util"] }
tower-http = { version = "0.6", default-features = false }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. E-help-wanted Call for participation: Help is requested to fix this issue. T-docs Topic: documentation
Projects
None yet
Development

No branches or pull requests

3 participants