A simple API using protobuf and gRPC

This tutorial provides a basic Go programmer’s introduction to working with gRPC.

Goal of this tutorial

  • Define a service in a .proto file.
  • Generate server and client code using the protocol buffer compiler.
  • Use the Go gRPC API to write a simple client and server for your service.

Recommended Books

1. Perquisites

This requires Go 1.6 or later, ProtocolBuffers 3.0.0 or later

// Ubuntu 16.04
$sudo apt install libprotobuf-dev

Requires that GOPATH is set

$ go help gopath
$ # ensure the PATH contains $GOPATH/bin
$ export PATH=$PATH:$GOPATH/bin
# for fish guy!!!
# set -gx PATH $PATH $GOPATH/bin

Then, go get -u as usual.

go get google.golang.org/grpc
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

2. Guidelines

architecture introduction diagram

Figure1. architecture of our example program

Make sure that your $GOPATH/bin is in your $PATH.

2.1 Define your service in gRPC

pb/service.proto:

syntax = "proto3";
option go_package = "echo";

// Echo Service
//
// Echo Service API consists of a single service which returns a message.
package echo;

import "google/api/annotations.proto";

// Message represents a simple message sent to the Echo service.
message Message {
    // Id represents the message identifier.
    string id = 1;
    // The message to be sent.
    string msg = 2;
}

// Echo service responds to incoming echo requests.
service EchoService {
    // Echo method receives a simple message and returns it.
    // The message posted as the id parameter will also be returned.
    rpc Echo(Message) returns (Message) {
        option (google.api.http) = {
            post: "/v1/example/echo/{id}/{msg}"
        };
    }
}

2.2 Gnerate gRPC stub

Generating client and server code

$ protoc -I/usr/local/include -I. \
  -I$GOPATH/src \
 -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
 --go_out=Mgoogle/api/annotations.proto=github.com/gengo/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:. \
  pb/service.proto

Generate reverse-proxy for your RESTful API

$ protoc -I/usr/local/include -I. \
   -I$GOPATH/src \
   -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
   --grpc-gateway_out=logtostderr=true:. \
   pb/service.proto

2.3 Write your Server code

server/server.go:

package main

import (
    "flag"

    "github.com/golang/glog"
    pb "github.com/go-grpc-tutorial/pb"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "net"
)

// Implements of EchoServiceServer

type echoServer struct{}

func newEchoServer() pb.EchoServiceServer {
    return new(echoServer)
}

func (s *echoServer) Echo(ctx context.Context, msg *pb.Message) (*pb.Message, error) {
    glog.Info(msg)
    return msg, nil
}

func Run() error {
    listen, err := net.Listen("tcp", ":50051")
    if err != nil {
        return err
    }
    server := grpc.NewServer()
    pb.RegisterEchoServiceServer(server, newEchoServer())
    server.Serve(listen)
    return nil
}

func main() {
    flag.Parse()
    defer glog.Flush()

    if err := Run(); err != nil {
        glog.Fatal(err)
    }
}

server/server-rproxy.go: (REST JSON API)

package main

import (
    "flag"
    "net/http"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "github.com/golang/glog"
    pb "github.com/go-grpc-tutorial/pb"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

var (
    echoEndpoint = flag.String("echo_endpoint", "localhost:50051", "endpoint of EchoService")
)

func RunEndPoint(address string, opts ...runtime.ServeMuxOption) error {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux(opts...)
    dialOpts := []grpc.DialOption{grpc.WithInsecure()}
    err := pb.RegisterEchoServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, dialOpts)
    if err != nil {
        return err
    }

    http.ListenAndServe(address, mux)
    return nil
}

func main() {
    flag.Parse()
    defer glog.Flush()

    if err := RunEndPoint(":8080"); err != nil {
        glog.Fatal(err)
    }
}

2.3 Client Side

server/client.go:

package main

import (
    "log"
    "os"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "github.com/go-grpc-tutorial/pb"
)

const (
    address = "localhost:50051"
    defaultName = "PhuongDV"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewEchoServiceClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    r, err := c.Echo(context.Background(), &pb.Message{Id: "1", Msg: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Msg)
}

2.4 Try it out!

To compile and run the server, assuming you are in the folder $GOPATH/src/github.com/go-grpc-tutorial, simply:

 $ go run server/server.go

Likewise, to run the client:

 $ go run client/client.go

 017/03/06 17:36:33 Greeting: PhuongDV

Call REST API

 $ curl -X POST "http://localhost:8080/v1/example/echo/1/PhuongDV"

{"id":"1","msg":"PhuongD"}

3. Source code

Download the entire example from https://github.com/phuongdo/go-grpc-tutorial, then copy it
to $GOPATH/src/github.com/go-grpc-tutorial

4. Further reading

Leave a comment

Blog at WordPress.com.

Up ↑