I have axum and tonic running on the same server like the following:
let state = AppState {
rate_limiter: Arc::new(RateLimiter::new(10, Duration::from_secs(60))), // 10 requests per minute
};
let greeter_service = grpc::hello_world::MyGreeter::default();
let grpc_service =
Server::builder().add_service(greeter_server::GreeterServer::new(greeter_service));
let app = axum::Router::new()
// ... many .route()
.layer(
ServiceBuilder::new()
.layer(middleware::from_fn(trace_http))
// https://github.com/tokio-rs/axum/discussions/987
.layer(HandleErrorLayer::new(|err: BoxError| async move {
// turns layer errors into HTTP errors
error!("Unhandled error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled error: {}", err),
)
}))
.layer(BufferLayer::new(1024))
.layer(DefaultBodyLimit::max(1_000_000))
// also see https://docs.rs/tower-http/0.6.1/tower_http/request_id/index.html#example
.layer(tower::timeout::TimeoutLayer::new(Duration::from_secs(60))) // 30 second timeout
.layer(middleware::from_fn_with_state(
state.clone(),
ip_rate_limiter,
)),
)
.with_state(state);
info!("Starting on http://{} and grpc://{}", http_addr, grpc_addr);
let axum_listener = tokio::net::TcpListener::bind(http_addr).await.unwrap();
let axum_server = axum::serve(axum_listener, app).with_graceful_shutdown(async {
tokio::signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
info!("Received shutdown signal");
});
let grpc_server = grpc_service.serve_with_shutdown(grpc_addr, async {
tokio::signal::ctrl_c()
.await
.expect("Failed to install Ctrl+C handler");
info!("Received shutdown signal");
});
_ = tokio::join!(axum_server, grpc_server);
Right now they run on different ports. Is there a way to have them multiplexed on the same port for http 1.1/h2c support such that I can route an incoming request to the correct service based on the header (i.e. if application/grpc
send to tonic, otherwise axum)
tonic = "0.12.3"
axum = { version = "0.8.1", features = ["macros"] }
current versions used, can upgrade as needed
Tonic has a Routes
type that you can add services to and then finish with .into_axum_router()
to use it with axum.
This is pretty much what I've done before using this tonic-made router as the base:
let app = Routes::default()
.add_service(greeter_server::GreeterServer::new(greeter_service))
.add_service(...)
.add_service(...)
.into_axum_router()
.with_state(()) // needed to use routes with a different state
.route(...)
.route(...)
.route(...)
.layer(...)
.with_state(state);
This requires tonic's router
Cargo feature, but it is enabled by default.