gRPC

gRPC is an open source conceptual framework for RPC originally developed at Google to supersede Stubby (Private). It's designed for cross-platform compatibility whilst offering an emphasis on programming semantics and increased performance over REST with binary transports.

It's commonly used with the Protocol Buffers transport, but transports can be swapped out.

Concepts

  • Servers receive and process requests, returning responses.
  • Clients interact with servers.
  • The Transport Protocol serialises and deserialises messages for transport over the wire.
    • Protocol Buffers is the most commonly used.
    • Thrift
    • Avro
    • Flatbuffers
    • Cap'n Proto
    • Raw bytes
    • JSON support is available, but it's generally more common to use a proxy to transcode messages.
  • Services are sets of methods bound to a gRPC Server.
  • Metadata can be used to perform negotiation rounds between the Client and Server.
  • Messages define a mapping between a Request and Response format. Streams can be indicated by prefixing the response type with the stream keyword in the rpc's returns clause.
    • Unary messages follows the traditional request/response flow.
    • Server streaming allows the client to send a request and the server to send a chunked response.
    • Client streaming enables a Client to send larger messages, e.g. uploaded files, to a Server. Processing on the Server begins after receipt of the final chunk.
    • Bidirectional streaming allows the Client to send multiple requests, and the Server to respond as it can.
  • Messages are strongly typed, with RPCs accepting a request object and returning a response object.
  • Metadata allows the client to send data about a request ahead of a request body, and the server to attach leading or trailing data about a response.

Service definition

For a bidirectional stream:

service Greet {
  rpc Greet(stream GreetRequest) returns (stream GreetResponse);
}

Design lifecycle

  1. Define messages.
  2. Generate source code.
  3. Write client/server implementations.

Message lifecycle

  1. Create a (reusable) communication channel.
  2. Create a (reusable) client.
  3. Server can optionally return metadata.
  4. Send/receive messages.

Authentication

Metadata can be used for authentication. There are multiple options for securing gRPC services:

  • Insecure (HTTP/1) offers the easiest setup.
  • SSL/TLS (on HTTP/2) allows the client to validate the server's certificate with a CA.
  • Google Token-Based requires SSL/TLS and allows using Google service accounts.
  • Custom, community-contributed implementations for OAuth 2 and similar are available for some languages.

Troubleshooting

gRPCurl offers a cURL-like CLI for interacting with gRPC services.

Usage with Go

Dependencies:

  • google.golang.org/grpc
  • github.com/golang/protobuf/proto
  • github.com/golang/protobuf/proto-gen-go

Code generation:

protoc -I ./pb/ /pb/*.proto --go_out=plugins=grpc:./src/

Server:

package main

import (
    "golang.org/x/net/context"
    "greet/pb"
)

const PORT = ":8080"

func main() {
    lis, err := net.Listen("tcp", PORT)
    if err != nil {
        log.Fatalf("opening listener", err)
    }

    creds, err := credentials.NewServerTLSFromFile("cert.pem", "key.pem")
    if err != nil {
        log.Fatalf("obtaining credentials", err)
    }

    opts := []grpc.ServerOption{grpc.Creds(creds)}
    s := grpc.NewServer(opts...)
    pb.RegisterGreetServer(s, new(GreetService))
    s.Serve()
    log.Println("ready on port" + PORT)
}

type GreetService struct{}

func (s *GreetService) Greet(ctx context.Context, req *pb.GreetRequest) (*pb.GreetResponse, error) {
    res = &pb.GreetResponse{"Hi", req.Name}
    return res, nil
}

Client:

package main

import (
    "greet/pb"
)

const PORT = ":8080"

func main() {
    creds, err := credentials.NewClientTLSFromFile("cert.pem", "")
    if err != nil {
        log.Fatalf("obtaining credentials", err)
    }

    opts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
    conn, err := grpc.Dial("localhost" + PORT, opts...)
    if err != nil {
        log.Fatalf("connecting to server", err)
    }
    defer conn.Close()

    client := pb.NewGreetClient(conn)

    res, err := client.Greet(&pb.GreetRequest{name: "Luke"})
    if err != nil {
        log.Fatalf("greeting", err)
    }
}

Backlinks