Core Concepts
HTTP + gRPC Support
New to acton-service?
Start with the homepage to understand what acton-service is, then explore Core Concepts for foundational explanations. See the Glossary for technical term definitions.
HTTP + gRPC Support
Acton Service supports running HTTP REST APIs and gRPC services simultaneously, either on a single port with automatic protocol detection or on separate ports for isolated traffic management. This dual-protocol capability enables you to serve different client types from the same service deployment.
Why Run Both Protocols?
Combining HTTP and gRPC in a single service provides several architectural benefits:
Internal Service Communication
- Use gRPC for efficient, type-safe service-to-service communication
- Leverage HTTP/2 streaming and reduced serialization overhead
- Automatic schema validation with Protocol Buffers
External Client Access
- Expose HTTP REST APIs for browser clients and third-party integrations
- Support clients without gRPC tooling or Protocol Buffer support
- Maintain backwards compatibility with existing REST consumers
Operational Simplicity
- Single deployment unit serving both protocols
- Optional single-port mode with automatic protocol detection
- Unified observability, logging, and metrics across both protocols
- Simplified service mesh integration
Single-Port Mode (Default)
By default, Acton Service runs both HTTP and gRPC on the same port using automatic protocol detection. The framework inspects incoming connections to determine whether they're HTTP/1.1, HTTP/2 REST, or HTTP/2 gRPC requests.
How Protocol Detection Works
- gRPC requests: Identified by the
content-type: application/grpcheader - HTTP/2 requests: Standard HTTP/2 traffic routed to REST handlers
- HTTP/1.1 requests: Automatically upgraded or handled as REST
This detection happens transparently with no performance penalty, allowing clients to connect using their preferred protocol without coordination.
Benefits of Single-Port Mode
- Simplified networking: One port to configure in firewalls and load balancers
- Reduced resource usage: Single listener, single connection pool
- Easier service discovery: Clients only need to know one endpoint
- Cloud-friendly: Fewer port allocations in containerized environments
Separate-Port Mode
For scenarios requiring protocol isolation, you can configure separate ports for HTTP and gRPC traffic:
[grpc]
enabled = true
use_separate_port = true
port = 9090 # gRPC traffic only
[service]
port = 8080 # HTTP traffic only
When to Use Separate Ports
Network Policy Requirements
- Different firewall rules for internal vs external traffic
- Separate ingress controllers for HTTP and gRPC
- Protocol-specific rate limiting or throttling
Performance Tuning
- Dedicated connection pools for each protocol
- Independent timeout configurations
- Separate load balancing strategies
Monitoring and Observability
- Protocol-specific metrics collection
- Isolated traffic analysis
- Granular network-level debugging
Configuration Options
Configure gRPC behavior in your config.toml:
| Option | Type | Default | Description |
|---|---|---|---|
grpc.enabled | boolean | true | Enable or disable gRPC support |
grpc.use_separate_port | boolean | false | Use dedicated port for gRPC traffic |
grpc.port | integer | 9090 | Port for gRPC when use_separate_port = true |
service.port | integer | 8080 | Primary HTTP port (also serves gRPC in single-port mode) |
Single-Port Configuration (Default)
[grpc]
enabled = true
use_separate_port = false # Automatic protocol detection on service.port
Separate-Port Configuration
[service]
name = "my-service"
port = 8080 # HTTP REST traffic
[grpc]
enabled = true
use_separate_port = true
port = 9090 # gRPC traffic only
Complete Example
Here's a complete service implementing both HTTP REST and gRPC:
use acton_service::prelude::*;
use acton_service::grpc::server::GrpcServicesBuilder;
use axum::{routing::get, Json};
use tonic::{Request, Response, Status};
// HTTP handler
async fn list_users() -> Json<Vec<String>> {
Json(vec!["alice".to_string(), "bob".to_string()])
}
// Define HTTP routes with versioning
let http_routes = VersionedApiBuilder::new()
.add_version(ApiVersion::V1, |router| {
router.route("/users", get(list_users))
})
.build_routes();
// Define gRPC service
#[derive(Default)]
struct MyGrpcService;
#[tonic::async_trait]
impl my_service::MyService for MyGrpcService {
async fn my_method(&self, req: Request<MyRequest>)
-> Result<Response<MyResponse>, Status> {
let user_id = req.into_inner().user_id;
Ok(Response::new(MyResponse {
message: format!("Hello from gRPC, user {}", user_id),
}))
}
}
// Collect gRPC services into tonic routes
let grpc_routes = GrpcServicesBuilder::new()
.add_service(my_service::MyServiceServer::new(MyGrpcService))
.build(None);
// Serve both protocols on the same port (automatic protocol detection)
ServiceBuilder::new()
.with_routes(http_routes)
.with_grpc_services(grpc_routes)
.build()
.serve()
.await?;
GrpcServicesBuilder collects every gRPC service you register and returns a tonic::service::Routes value, which .with_grpc_services() accepts. Call .add_service() once per service — a single .with_grpc_services() call carries them all.
The build() argument is an optional AppState. Pass None when you don't enable the health service; pass Some(state) when you do, so health checks can inspect your configured dependencies.
Client Examples
HTTP REST client (any HTTP library, curl, browser):
curl http://localhost:8080/v1/users
gRPC client (using Tonic or any gRPC client):
let mut client = my_service::MyServiceClient::connect("http://localhost:8080").await?;
let response = client.my_method(MyRequest { user_id: 123 }).await?;
Both clients connect to the same port, with the framework automatically routing traffic to the appropriate handler.
Advanced Features
Reflection Support
Enable gRPC reflection for dynamic client discovery. GrpcServicesBuilder builds the reflection service for you — call .with_reflection() and register the file descriptor set generated by tonic-build:
use acton_service::grpc::server::GrpcServicesBuilder;
let grpc_routes = GrpcServicesBuilder::new()
.with_reflection()
.add_file_descriptor_set(my_service::FILE_DESCRIPTOR_SET)
.add_service(my_service::MyServiceServer::new(MyGrpcService))
.build(None);
ServiceBuilder::new()
.with_grpc_services(grpc_routes)
.build()
.serve()
.await?;
Health Checks
Both protocols share the same health check endpoint:
- HTTP:
GET /health - gRPC: Standard
grpc.health.v1.Healthservice
Next Steps
- See Examples for complete dual-protocol service implementations
- Learn about Configuration options for fine-tuning both protocols
- Explore gRPC Guide for protocol buffers, build setup, and implementation details
- Review Observability for protocol-specific metrics and tracing