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.Server interface defined in transport/transport.go, decoupling protocol details from application lifecycle.
  • Variadic aggregationkratos.New accepts multiple servers via the kratos.Server(...) option, storing them in opts.servers for unified management.
  • Concurrent executionApp.Run() launches each transport in its own goroutine via errgroup, binding their lifecycles together.
  • Discovery integrationbuildInstance() in app.go aggregates endpoints from all transport.Endpointer implementations into a single service registration.
  • Runtime transparency – Handlers use transport.FromServerContext() to detect KindHTTP or KindGRPC and 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →