How Kratos Supports HTTP and gRPC Transports Simultaneously in a Single Service
Kratos enables simultaneous HTTP and gRPC support by abstracting both protocols behind a unified transport.Server interface, allowing a single App instance to aggregate, launch, and lifecycle-manage multiple listeners concurrently.
The go-kratos/kratos framework treats network listeners as interchangeable implementations of the same transport contract. This design lets a microservice expose both REST and RPC endpoints from a single binary, sharing business logic while maintaining distinct protocol semantics.
The Transport Abstraction Layer
At the heart of Kratos’s multi-transport capability is a minimal interface defined in transport/transport.go. Both the HTTP server (transport/http/server.go) and the gRPC server (transport/grpc/transport.go) satisfy this contract, making them interchangeable within the application lifecycle.
The Core Server Interface
The transport.Server interface requires only two methods for lifecycle management:
// transport/transport.go
type Server interface {
Start(context.Context) error
Stop(context.Context) error
}
Both http.Server and grpc.Transport implement Start to begin listening on their respective ports and Stop to handle graceful shutdown. This symmetry allows the framework to treat them as generic building blocks regardless of underlying protocol complexity.
Transport Kind Discrimination
To distinguish between protocols at runtime, the framework defines a Kind enum in transport/transport.go. Implementations return either transport.KindHTTP or transport.KindGRPC, enabling middleware and handlers to branch based on the incoming wire format.
Aggregating Servers in the Application
The App struct in app.go orchestrates multiple transports through a variadic constructor option. When you call kratos.Server(httpSrv, grpcSrv), the framework stores both implementations in the internal opts.servers slice.
Concurrent Lifecycle Management
During App.Run(), located in app.go, the framework iterates over a.opts.servers and launches each server in its own goroutine using an errgroup. This ensures that HTTP and gRPC listeners start simultaneously and fail together if either encounters a critical error:
// Conceptual flow from app.go Run() method (lines 82-118)
for _, srv := range a.opts.servers {
eg.Go(func() error {
<-octx.Done() // Wait for startup signal
return srv.Start(octx)
})
}
Each server also registers a deferred stop-handler, ensuring that when the application receives a termination signal, both transports drain active connections and shut down gracefully.
Unified Endpoint Registration
For service discovery, the buildInstance() method in app.go (lines 176-191) inspects every server that implements the optional transport.Endpointer interface. It collects the HTTP and gRPC URLs into a single registry.ServiceInstance structure, allowing registries like Consul or etcd to advertise both endpoints under one service name.
Dual-Protocol Service Implementation
To expose the same business logic over both transports, register your service implementation with each server using the generated registration helpers:
package main
import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/transport/http"
"github.com/go-kratos/kratos/v2/transport/grpc"
helloworldv1 "github.com/go-kratos/kratos/v2/internal/testdata/helloworld" // generated protobuf
)
func main() {
// 1️⃣ Create an HTTP server (listens on :8000)
httpSrv := http.NewServer(
http.Address(":8000"),
)
// 2️⃣ Create a gRPC server (listens on :9000)
grpcSrv := grpc.NewServer(
grpc.Address(":9000"),
)
// 3️⃣ Register the same service implementation on both transports
helloworldv1.RegisterGreeterServer(grpcSrv, &greeter{})
helloworldv1.RegisterGreeterHTTPServer(httpSrv, &greeter{}) // HTTP‑specific registration helper
// 4️⃣ Build the Kratos app with *both* servers
app := kratos.New(
kratos.Name("helloworld"),
kratos.Version("v1.0.0"),
kratos.Server(httpSrv, grpcSrv), // <-- multiple transports
)
// 5️⃣ Run the app – both transports start concurrently
if err := app.Run(); err != nil {
panic(err)
}
}
In this configuration, the greeter struct handles requests from both port 8000 (HTTP/JSON) and port 9000 (gRPC/protobuf) without protocol-specific branching in the core business logic.
Runtime Transport Inspection
When a request arrives, the respective transport implementation injects a transport.Transporter into the context. Handlers can inspect this to determine the protocol and access metadata like headers or trailers:
func (g *greeter) SayHello(ctx context.Context, req *helloworldv1.HelloRequest) (*helloworldv1.HelloReply, error) {
tr, ok := transport.FromServerContext(ctx)
if ok {
// tr.Kind() returns transport.KindHTTP or transport.KindGRPC
fmt.Printf("Request arrived via %s\n", tr.Kind())
}
// … business logic …
}
The HTTP server’s filter middleware (transport/http/server.go lines 93-105) and the gRPC interceptor (transport/grpc/transport.go) both populate this context value using transport.NewServerContext, ensuring a consistent API for transport detection regardless of the wire protocol.
Summary
- Interface-driven design – Both HTTP and gRPC implement the
transport.Serverinterface defined intransport/transport.go, decoupling protocol details from application lifecycle. - Variadic aggregation –
kratos.Newaccepts multiple servers via thekratos.Server(...)option, storing them inopts.serversfor unified management. - Concurrent execution –
App.Run()launches each transport in its own goroutine viaerrgroup, binding their lifecycles together. - Discovery integration –
buildInstance()inapp.goaggregates endpoints from alltransport.Endpointerimplementations into a single service registration. - Runtime transparency – Handlers use
transport.FromServerContext()to detectKindHTTPorKindGRPCand access protocol-specific metadata when necessary.
Frequently Asked Questions
Can I run more than two transports in a single Kratos service?
Yes. Because kratos.Server() accepts a variadic slice of transport.Server implementations, you can theoretically attach any number of transports—HTTP, gRPC, or custom implementations—to the same App. The Run() method will start and stop them all concurrently.
How does Kratos handle graceful shutdown with multiple transports?
When the application receives an interrupt signal, App.Run() cancels the parent context and invokes the Stop(context.Context) method on every server in opts.servers. Each transport implementation handles its own graceful shutdown logic, ensuring that HTTP connections drain and gRPC streams complete before the process exits.
Do I need separate service implementations for HTTP and gRPC?
No. You register the same struct instance with both RegisterGreeterServer (gRPC) and RegisterGreeterHTTPServer (HTTP). The generated code adapts the wire format to your method signatures. If you need transport-specific behavior, inspect transport.FromServerContext() inside the handler method.
How do I access protocol-specific details like HTTP headers?
The transport.Transporter interface returned by transport.FromServerContext() provides methods like Header() and Trailer() that map to the underlying protocol. For HTTP, these map directly to http.Header; for gRPC, they map to metadata.MD. This abstraction allows middleware to operate generically while handlers can still drill down into protocol-specific features when required.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →