Use Connect for incoming gRPC requests
The Connect protocol is an HTTP/2-based protocol for RPC communication. It's conceptually similar to gRPC, but with better support for using from browsers and JavaScript clients.
This guide shows how to use Encore for setting up a Connect service for external clients to use:
- First, we'll define a simple gRPC service using Protobuf and Connect.
- Then, we'll implement the service in Go, using connect-go.
- Then, we'll mount the Connect service into Encore with a raw endpoint.
- Finally, we'll call the Connect service from cURL using its JSON mapping.
Define a Connect service
We'll largely follow the connect-go getting started guide with some small tweaks.
Start by installing the necessary tools:
$ go install github.com/bufbuild/buf/cmd/buf@latest$ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest$ go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
Next, inside your Encore application (create one if you haven't already)
create a new file at greet/v1/greet.proto
with the following contents:
greet/v1/greet.protosyntax = "proto3";
package greet.v1;
option go_package = "encore.app/gen/greet/v1;greetv1";
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
Next, add a buf.gen.yaml
in the repository root, containing:
buf.gen.yamlversion: v2
plugins:
- local: protoc-gen-go
out: gen
opt: paths=source_relative
- local: protoc-gen-connect-go
out: gen
opt: paths=source_relative
Now it's time to generate the connect-go service code. Run:
$ buf lint$ buf generate
If all went well, you should see a new gen
directory in the repository root containing some generated Go code:
gen
└── greet
└── v1
├── greet.pb.go
└── greetv1connect
└── greet.connect.go
Implement the service
Now that we have the service definition, we can implement the Connect service in Go.
Add the file greet/greet.go
with the following contents:
greet/greet.gopackage greet
import (
"context"
"fmt"
"log"
"connectrpc.com/connect"
greetv1 "encore.app/gen/greet/v1" // generated by protoc-gen-go
)
type GreetServer struct{}
func (s *GreetServer) Greet(
ctx context.Context,
req *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
log.Println("Request headers: ", req.Header())
res := connect.NewResponse(&greetv1.GreetResponse{
Greeting: fmt.Sprintf("Hello, %s!", req.Msg.Name),
})
res.Header().Set("Greet-Version", "v1")
return res, nil
}
Please note
The sample code is straight from the getting started guide; there are no Encore specific changes required here.
Mount the service in Encore
Now we'll create an Encore service struct that initializes the Connect service, and a raw endpoint that forwards incoming requests to the Connect service.
Add the file greet/service.go
with the following contents:
greet/service.gopackage greet
import (
"net/http"
"encore.app/gen/greet/v1/greetv1connect"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
//encore:service
type Service struct {
routes http.Handler
}
//encore:api public raw path=/greet.v1.GreetService/*endpoint
func (s *Service) GreetService(w http.ResponseWriter, req *http.Request) {
s.routes.ServeHTTP(w, req)
}
func initService() (*Service, error) {
greeter := &GreetServer{}
mux := http.NewServeMux()
path, handler := greetv1connect.NewGreetServiceHandler(greeter)
mux.Handle(path, handler)
return &Service{routes: mux}, nil
}
That's it! We're ready to run the service and check that everything works.
Run the service
Run the service with encore run
:
$ encore run
Once it starts up, open a separate terminal and use grpcurl
to call the service:
# Install grpcurl if you haven't already$ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest# Call the service with grpcurlgrpcurl \ -protoset <(buf build -o -) -plaintext \ -d '{"name": "Jane"}' \ localhost:4000 greet.v1.GreetService/Greet{"greeting": "Hello, Jane!"} # Or call the service with curl$ curl -H "Content-Type: application/json" -d '{"name": "Jane"}' http://localhost:4000/greet.v1.GreetService/Greet{"greeting":"Hello, Jane!"} # Expected response
If you see {"greeting":"Hello, Jane!"}
, everything is working!
What's more, Encore automatically traces the incoming requests, and adds request logging and captures request metrics.