0

I am using gRPC with Golang.

After successfully running their HelloWorld tutorial which required me to add a second function, compile the protoc file, I decided to make some further changes.

My code is included, which appears not to work because it expects the pb variable to be a HelloWorld object, or something, as shown in the error message in command line below. Could someone please tell me where I have gone wrong, for what should be some very small changes.

I have fixed some typos in my original post's code below.

Command line:

go run server/server.go
# command-line-arguments
server/server.go:24:71: undefined: helloworld.TheGRPCNotificationMessage

Command line protoc file:

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative GRPCNotification/GRPCNotification.proto

GRPCNotification.proto

syntax = "proto3";

import "google/protobuf/empty.proto";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package grpcnotificationpackage;

// The greeting service definition.
service TheGRPCNotificationService {
  // Sends a notification and nothing sent back
  rpc SendNotificationWithGRPC (TheGRPCNotificationMessage) returns (google.protobuf.Empty);
  // Receives a notification and does not reply
  rpc ReceiveNotificationWithGRPC (stream TheGRPCNotificationMessage) returns (google.protobuf.Empty);
}

// The request notification message
message TheGRPCNotificationMessage {
  string message = 1;
  // ... add more here
}

// Since nothing gets returned from server we need this empty
message Empty {
  // Nothing goes here
}

Client.go

// Package main implements a client for Greeter service.
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

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

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

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SendNotificationWithGRPC(ctx, &pb.TheGRPCNotificationMessage{Message: "Horse"})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Sent a message, please check it reached server...")
}

Server.go

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    port = ":50051"
)


// server is used to implement helloworld.GreeterServer
type server struct {
    pb.UnimplementedGreeterServer
}


// SendNotificationWithGRPC implements helloworld.GreeterServer   <--- Problem?
func (s *server) ReceiveNotificationWithGRPC(ctx context.Context, in *pb.TheGRPCNotificationMessage) {
    log.Printf("Received: %v", in.Name)
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

I wish the example tutorial did not glue me to it so roughly!

1 Answers1

2

Your go_package and client.go and server.go package imports are incorrect. This is a "fiddly" part of gRPC but it results because the protocol must support many (varied) languages and their package solutions.

You've also renamed the service and methods so can't use e.g. GreeterService as this no longer exists. Most editors (e.g. VSCode) will help you navigate packages to find naming errors.

pb "google.golang.org/grpc/examples/helloworld/helloworld"
  1. Assuming your code is in a Go Module called ${REPO} (and probably something of the form github.com/YOUR-ORG/YOUR-REPO, i.e. you:
go mod init ${REPO}
  1. In the proto file, for Golang (I'll leave the other languages for you to solve), you'll want:
option go_package = "${REPO}/helloworld";

NOTE Replace ${REPO} with its value i.e. github.com/....

  1. I would encourage you to be consistent with proto package name name grpcnotificationpackage and your generated code package names but, for now, your proto package is grpcnotificationpackage but you're generating code into helloworld.

  2. Create a directory in the repo called helloworld. You're asking protoc to generate Go source (go_package) into a package called ${REPO}/helloworld and Go requires that the files be in a directory called helloworld in your repo.

  3. Then:

protoc \
--go_out=${PWD}/helloworld \
--go_opt=paths=source_relative \
--go-grpc_out=${PWD}/helloworld \
--go-grpc_opt=paths=source_relative \
GRPCNotification.proto

There are various flavors of this command but this will generate the sources into ${PWD}/helloworld in your repo.

  1. Then you must import correctly:
import (
  ...
  ${REPO}/helloworld
)
  1. Then you must correct your references. If you call a service TheGRPCNotificationService and a method SendNotificationWithGRPC and a message TheGRPCNotificationMessage, this is what protoc will generate (you can review the generated Go sources to confirm this). So:
package main

import (
    "log"
    "net"

    pb "${REPO}/helloworld"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

const (
    port = ":50051"
)

// Ensure that `server` implements the interface
var _ pb.TheGRPCNotificationServiceServer = &server{}

// server is used to implement helloworld.GreeterServer
type server struct {
    pb.UnimplementedTheGRPCNotificationServiceServer
}

// This is the correct signature of the method
func (s *server) ReceiveNotificationWithGRPC(
    stream pb.TheGRPCNotificationService_ReceiveNotificationWithGRPCServer,
) error {
    // Implement
    return status.Errorf(codes.Unimplemented, "not implemented")
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterTheGRPCNotificationServiceServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
  1. I don't recommend returns (google.protobuf.Empty). I don't know whether there's a best practice for this but I think it's better to return a message that contains no fields, e.g.:
rpc Foo (FooRequest) returns (FooResponse);

message FooResponse {}
  1. Consider using a tool such as gRPCurl to debug:

In this example, gRPCurl uses the proto not the service to enumerate the services (defined in the proto):

grpcurl \
-plaintext \
--proto *.proto \
localhost:50051 list

Yields:

grpcnotificationpackage.TheGRPCNotificationService

Same-same but for the methods for the given service:

grpcurl \
-plaintext \
--proto *.proto \
localhost:50051 list grpcnotificationpackage.TheGRPCNotificationService

Yields the methods:

grpcnotificationpackage.TheGRPCNotificationService.ReceiveNotificationWithGRPC
grpcnotificationpackage.TheGRPCNotificationService.SendNotificationWithGRPC

Invoke the server streaming method:

grpcurl \
-plaintext \
--proto *.proto \
localhost:50051 grpcnotificationpackage.TheGRPCNotificationService.ReceiveNotificationWithGRPC

Yields (correctly an Unimplemented error)

ERROR:
  Code: Unimplemented
  Message: not implemented
DazWilkin
  • 32,823
  • 5
  • 47
  • 88
  • I appreciate your help. Would you mind providing me with a client source code, as at the moment above there is just code for the server. –  Oct 19 '21 at 19:40
  • I need a client source code so I can send messages to the server. –  Oct 19 '21 at 22:10