How to Run Axum and Tonic on the Same Port with Routing

Running Axum and Tonic on the same server can enhance your application by allowing efficient communication through a single port. This article will guide you through configuring Axum and Tonic to run on the same port, enabling header-based routing to determine the correct service based on request type (HTTP or gRPC). Why Use Axum and Tonic Together? Combining Axum (a web framework) and Tonic (a gRPC framework) in one server is advantageous for microservices architecture. You can efficiently handle both HTTP 1.1 requests and gRPC requests without needing to manage multiple ports. The key is to leverage hyper as the underlying server, which supports multiplexing different protocols. Initial Setup Before diving into the code, ensure both axum and tonic dependencies are included in your Cargo.toml file: [dependencies] tonic = "0.12.3" axum = { version = "0.8.1", features = ["macros"] } hyper = "0.14" tokio = { version = "1.0", features = ["full"] } Multiplexing Axum and Tonic To create a single server that can respond to both HTTP and gRPC requests, we need to: Create an instance of hyper::Server. Route requests based on the content type of incoming headers. Start the server and handle shutdown conditions gracefully. Step 1: Create Your Axum Router Define your Axum router as you normally would. For example: let app = axum::Router::new() .route("/api", axum::routing::get(your_http_handler)); Step 2: Create Your Tonic Service Similarly, define your Tonic gRPC service: let greeter_service = grpc::hello_world::MyGreeter::default(); let grpc_service = Server::builder().add_service(greeter_server::GreeterServer::new(greeter_service)); Step 3: Implement a Multiplexing Logic Based on Headers With both services defined, implement a function to determine how to route the requests: async fn serve_multiplex(req: Request, axum_app: &Router, grpc_service: &YourGrpcService) -> Result { if req.headers().get("content-type") == Some(&HeaderValue::from_static("application/grpc")) { // Forward request to Tonic gRPC service // Convert request to gRPC message and serve with grpc_service } else { // Forward request to Axum axum_app.call(req).await } } Step 4: Start the Server You will now start the server using the hyper implementation: use hyper::{Body, Request, Response, Server}; #[tokio::main] async fn main() -> Result { let addr = ([127, 0, 0, 1], 3000).into(); let make_svc = make_service_fn(|_conn| async { Ok::(service_fn(\|req| serve_multiplex(req, &app, &grpc_service)\)) }); let server = Server::bind(&addr).serve(make_svc); println!("Listening on http://{:?}", addr); server.await?; Ok(()) } Step 5: Graceful Shutdown Ensure you handle graceful shutdown by listening for termination signals. You can set up your server to listen for Ctrl+C signals to shut down each service gracefully. Frequently Asked Questions Can I run Axum and Tonic on different ports? Yes, typical setup involves running them on different ports, but for efficiency, integrating them under one process is preferred. Is there a performance gain by multiplexing services? Yes, reducing the number of open connections can improve resource utilization and response times due to fewer context switches. What happens if I receive a request that doesn't match either service? A well-designed server should always have a fallback mechanism to return an appropriate HTTP response (e.g., 404 for not found). Conclusion By following the above steps, you can efficiently multiplex Axum and Tonic on the same port. This combines the power of both HTTP and gRPC services within a single application context, optimizing resource usage and user experience. Now, you'll be able to handle requests seamlessly based on headers, sending them to the appropriate service with minimal performance penalties.

May 11, 2025 - 03:26
 0
How to Run Axum and Tonic on the Same Port with Routing

Running Axum and Tonic on the same server can enhance your application by allowing efficient communication through a single port. This article will guide you through configuring Axum and Tonic to run on the same port, enabling header-based routing to determine the correct service based on request type (HTTP or gRPC).

Why Use Axum and Tonic Together?

Combining Axum (a web framework) and Tonic (a gRPC framework) in one server is advantageous for microservices architecture. You can efficiently handle both HTTP 1.1 requests and gRPC requests without needing to manage multiple ports. The key is to leverage hyper as the underlying server, which supports multiplexing different protocols.

Initial Setup

Before diving into the code, ensure both axum and tonic dependencies are included in your Cargo.toml file:

[dependencies]
tonic = "0.12.3"
axum = { version = "0.8.1", features = ["macros"] }
hyper = "0.14"
tokio = { version = "1.0", features = ["full"] }

Multiplexing Axum and Tonic

To create a single server that can respond to both HTTP and gRPC requests, we need to:

  1. Create an instance of hyper::Server.
  2. Route requests based on the content type of incoming headers.
  3. Start the server and handle shutdown conditions gracefully.

Step 1: Create Your Axum Router

Define your Axum router as you normally would. For example:

let app = axum::Router::new()  
    .route("/api", axum::routing::get(your_http_handler));

Step 2: Create Your Tonic Service

Similarly, define your Tonic gRPC service:

let greeter_service = grpc::hello_world::MyGreeter::default();
let grpc_service =  
    Server::builder().add_service(greeter_server::GreeterServer::new(greeter_service));

Step 3: Implement a Multiplexing Logic Based on Headers

With both services defined, implement a function to determine how to route the requests:

async fn serve_multiplex(req: Request,  
                          axum_app: &Router,  
                          grpc_service: &YourGrpcService) -> Result, Infallible> {
    if req.headers().get("content-type") == Some(&HeaderValue::from_static("application/grpc")) {
        // Forward request to Tonic gRPC service
        // Convert request to gRPC message and serve with grpc_service
    } else {
        // Forward request to Axum
        axum_app.call(req).await
    }
}

Step 4: Start the Server

You will now start the server using the hyper implementation:

use hyper::{Body, Request, Response, Server};

#[tokio::main]
async fn main() -> Result<(), Box> {
    let addr = ([127, 0, 0, 1], 3000).into();
    let make_svc =  
        make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(\|req| serve_multiplex(req, &app, &grpc_service)\)) });

    let server = Server::bind(&addr).serve(make_svc);
    println!("Listening on http://{:?}", addr);
    server.await?;
    Ok(())
}

Step 5: Graceful Shutdown

Ensure you handle graceful shutdown by listening for termination signals. You can set up your server to listen for Ctrl+C signals to shut down each service gracefully.

Frequently Asked Questions

Can I run Axum and Tonic on different ports?

Yes, typical setup involves running them on different ports, but for efficiency, integrating them under one process is preferred.

Is there a performance gain by multiplexing services?

Yes, reducing the number of open connections can improve resource utilization and response times due to fewer context switches.

What happens if I receive a request that doesn't match either service?

A well-designed server should always have a fallback mechanism to return an appropriate HTTP response (e.g., 404 for not found).

Conclusion

By following the above steps, you can efficiently multiplex Axum and Tonic on the same port. This combines the power of both HTTP and gRPC services within a single application context, optimizing resource usage and user experience. Now, you'll be able to handle requests seamlessly based on headers, sending them to the appropriate service with minimal performance penalties.