This is an early preview / proof of concept providing gRPC support for Hummingbird using grpc-swift. It has not been tested with production workloads yet, so it definitely not ready for use.
Hummingbird gRPC supports:
- gRPC using Protocol Buffers over HTTP/2.
- Unary, client streaming, server streaming, and bidirectional streaming calls.
- grpc-swift Server interceptors.
- Async and SwiftNIO EventLoop based routing.
- grpc-swift generated server providers.
- proto2 and proto3.
Note
From version 0.0.5 Hummingbird gRPC supports Swift 6.0+ and requires Hummingbird 2.15.0+ and grpc-swift 1.23.0+.
Hummingbird gRPC does not currently support:
- gRPC using JSON or other serialization formats.
- gRPC Web (HTTP/1.1).
- Hummingbird middleware with gRPC endpoints.
Hummingbird gRPC uses the TLS Application-Later Protocol Negotiation to decide how to handle an incoming HTTP/2 channel. If it is negoiateed as grpc-exp
or as h2
(HTTP/2) with a Content-Type of application/grpc
the channel will be handled directly by grpc-swift, if it is negotiated as h2
(HTTP/2), with any other Content Type, as http/1.1
, or as anything else it will be handled by Hummingbird.
Currently this requires forking grpc-swift to make HTTP2ToRawGRPCServerCodec
public, it was hoped this change could be upstreamed so we could work directly with the official source but unfortunately grpc-swift 2.0.0 has gone in a different direction but it has embedded the their ways of working. We'd need to fork grpc-swift-nio-transport instead of the whole thing, but a fork is a fork.
On the Hummingbird side, our channel handling is
let app = HBApplication()
// Enable support for gRPC. You will need to configure TLS as well.
app.gRPC.addUpgrade(configuration: .init(), tlsConfiguration: .makeServerConfiguration())
// Add service providers that were generated by grpc-swift's protoc plugin
app.gRPC.addServiceProvider(EchoProvider())
// Alternatively, declare your gRPC endpoints directly using SwiftNIO EventLoop-based futures
app.gRPC.onUnary("echo.Echo", "Get", requestType: Echo_EchoRequest.self) { request, context in
let response = Echo_EchoResponse.with {
$0.text = "Swift echo get: " + request.text
}
return context.eventLoop.makeSucceededFuture(response)
}
// Or using async closures
app.gRPC.onUnary("echo.Echo", "Get", requestType: Echo_EchoRequest.self) { request, context async in
Echo_EchoResponse.with {
$0.text = "Swift echo get: " + request.text
}
}
// Start the server like normal
app.start()
app.wait()