Back to Encore

Use Connect for incoming gRPC requests

docs/go/how-to/grpc-connect.md

1.56.94.9 KB
Original Source

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:

  1. First, we'll define a simple gRPC service using Protobuf and Connect.
  2. Then, we'll implement the service in Go, using connect-go.
  3. Then, we'll mount the Connect service into Encore with a raw endpoint.
  4. 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:

shell
$ 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.proto --
syntax = "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.yaml --
version: 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:

shell
$ 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.go --
package 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
}
<Callout type="info">

The sample code is straight from the getting started guide; there are no Encore specific changes required here.

</Callout>

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.go --
package 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:

shell
$ encore run

Once it starts up, open a separate terminal and use grpcurl to call the service:

shell
# Install grpcurl if you haven't already
$ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# Call the service with grpcurl
grpcurl \
    -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.