This is the final part of the gRPC tutorial. If you haven’t checked the rest out, here’s the link:
We have already covered the basics, and code generation, and testing. Now we’ll code our main server and client.
Main Server and Client
Create two separate folders – server and client. Each of these will have their own main.go files.
Also, let’s create our Makefile:
gen:
protoc --proto_path=proto proto/*.proto --go_out=plugins=grpc:pb --grpc-gateway_out=:pb --openapiv2_out=:swagger
clean:
rm pb/*.go
server1:
go run ~/server/main.go -port 50051
server2:
go run ~/server/main.go -port 50052
server1-tls:
go run ~/server/main.go -port 50051 -tls
server2-tls:
go run ~/server/main.go -port 50052 -tls
server:
go run ~/server/main.go -port 8080
server-tls:
go run ~/server/main.go -port 8080 -tls
rest:
go run ~/server/main.go -port 8081 -type rest -endpoint 0.0.0.0:8080
client:
go run ~/client/main.go -address 0.0.0.0:8080
client-tls:
go run ~/client/main.go -address 0.0.0.0:8080 -tls
test:
go test -cover -race ./...
cert:
cd cert; ./gen.sh; cd ..
.PHONY: gen clean server client test cert
Open the server/main.go file:
func main() {
port := flag.Int("port", 0, "server port")
flag.Parse()
log.Printf("start server on port %d", *port)
laptopStore := service.NewInMemoryLaptopStore()
laptopServer := service.NewLaptopServer(laptopStore)
pb.RegisterLaptopServiceServer(grpcServer, laptopServer)
address := fmt.Sprintf("0.0.0.0:%d", *port)
listener, err := net.Listen("tcp", address)
if err != nil {
log.Fatal("cannot start server: ", err)
}
err = grpcServer.Serve(listener)
if err != nil {
log.Fatal("cannot start server: ", err)
}
}
We build an address string with the port that we’ve had before, then listen to this server address for TCP connexions. We need a port for the server, so I use the flag.Int() function to get it from command line arguments.
Finally, grpcServer. Serve() is called to start the server. Just write a fatal log and exit if any error occurs. And that’s the code for a server.
Next, we do the client side:
func main() {
serverAddress := flag.String("address", "", "the server address")
flag.Parse()
log.Printf("dial server %s", *serverAddress)
conn, err := grpc.Dial(*serverAddress, grpc.WithInsecure())
if err != nil {
log.Fatal("cannot dial server: ", err)
}
laptopClient := pb.NewLaptopServiceClient(conn)
laptop := sample.NewLaptop()
req := &pb.CreateLaptopRequest{
Laptop: laptop,
}
// set timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
res, err := laptopClient.CreateLaptop(ctx, req)
if err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.AlreadyExists {
// not a big deal
log.Print("laptop already exists")
} else {
log.Fatal("cannot create laptop: ", err)
}
return
}
log.Printf("created laptop with id: %s", res.Id)
}
We call grpc.Dial() function with the input address, and create a connection.
If an error occurs, we write a fatal log and exit. Else, we create a new laptop client object with the connection.
Then we generate a new laptop, make a new request object, and just call laptopClient.Createlaptop()
function with the request and a context. Here we use context.WithTimeout()
to set the timeout of this request to be 5 seconds.
If the error is not nil, we convert it into a status object. If the status code is ‘AlreadyExists
‘ then we write a normal log, else a fatal log.
If everything is good, we simply write a log saying the laptop is created with this ID. And that’s it for the client.
And we’re at the end of our tutorial. If we run this using go run ~/server/main.go -port 8080, we get:
If we cancel the request using Ctrl + C at the client side, we get a canceled message: