# How Kratos Supports HTTP and gRPC Transports Simultaneously in a Single Service

> Learn how Kratos enables simultaneous HTTP and gRPC support within a single service. Discover the unified transport interface that aggregates and manages multiple listeners efficiently.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: internals
- Published: 2026-03-02

---

**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`](https://github.com/go-kratos/kratos/blob/main/transport/transport.go). Both the HTTP server ([`transport/http/server.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/server.go)) and the gRPC server ([`transport/grpc/transport.go`](https://github.com/go-kratos/kratos/blob/main/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:

```go
// 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`](https://github.com/go-kratos/kratos/blob/main/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`](https://github.com/go-kratos/kratos/blob/main/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`](https://github.com/go-kratos/kratos/blob/main/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:

```go
// 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`](https://github.com/go-kratos/kratos/blob/main/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:

```go
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:

```go
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`](https://github.com/go-kratos/kratos/blob/main/transport/http/server.go) lines 93-105) and the gRPC interceptor ([`transport/grpc/transport.go`](https://github.com/go-kratos/kratos/blob/main/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`](https://github.com/go-kratos/kratos/blob/main/transport/transport.go), decoupling protocol details from application lifecycle.
- **Variadic aggregation** – `kratos.New` accepts multiple servers via the `kratos.Server(...)` option, storing them in `opts.servers` for unified management.
- **Concurrent execution** – `App.Run()` launches each transport in its own goroutine via `errgroup`, binding their lifecycles together.
- **Discovery integration** – `buildInstance()` in [`app.go`](https://github.com/go-kratos/kratos/blob/main/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.