Install Protocol Buffers
The latest release of Protocol Buffers can be found on the release page https://github.com/protocolbuffers/protobuf/releases/tag/v3.9.1
Install Libraries
Make sure Git is installed on your machine and in your system’s PATH. Install the package to your $GOPATH with the go tool from shell:
$ go get google.golang.org/grpc
Generate Keys
Create new folder named src, In src folder, create new folder named keys. Select src folder and open it in Terminal window of Visual Studio Code. Use command line below to generate keys:
openssl req -x509 -newkey rsa:2048 -keyout keys/server-key.pem -out keys/server-cert.pem -days 365 -nodes
Enter information for creating keys:
Create Proto Service
In src folder, create new folder named productservice. In productservice folder, create new folder named proto. In proto folder, create new file named product.proto as below:
syntax = "proto3";
package productservice;
service ProductService {
rpc FindAll (FindAllRequest) returns (FindAllResponse);
rpc Search(SearchRequest) returns (SearchResponse);
}
message SearchRequest {
string keyword = 1;
}
message SearchResponse {
repeated Product Products = 1;
}
message FindAllRequest {
}
message FindAllResponse {
repeated Product Products = 1;
}
message Product {
string id = 1;
string name = 2;
int32 quantity = 3;
double price = 4;
bool status = 5;
}
Generate Protocol Buffers
Select productservice folder and open it in Terminal window of Visual Studio Code. Use command line as below to generate product.pb.go file:
protoc -I ./ proto/product.proto --go_out=plugins=grpc:.
Create Handlers
In productservice folder, create new folder named handlers. In this folder, create new file named handler.go as below:
package handlers
import (
"context"
productservice "productservice/proto"
"strings"
)
type ProductServiceServer struct {
}
var products []*productservice.Product
func init() {
products = []*productservice.Product{
&productservice.Product{
Id: "p01",
Name: "tivi 1",
Price: 4.5,
Quantity: 4,
Status: true,
},
&productservice.Product{
Id: "p02",
Name: "tivi 2",
Price: 22,
Quantity: 3,
Status: false,
},
&productservice.Product{
Id: "p03",
Name: "laptop 3",
Price: 27,
Quantity: 3,
Status: true,
},
}
}
func (*ProductServiceServer) FindAll(ctx context.Context, in *productservice.FindAllRequest) (*productservice.FindAllResponse, error) {
return &productservice.FindAllResponse{Products: products}, nil
}
func (*ProductServiceServer) Search(ctx context.Context, in *productservice.SearchRequest) (*productservice.SearchResponse, error) {
var result []*productservice.Product
for _, product := range products {
if strings.Contains(product.Name, in.Keyword) {
result = append(result, product)
}
}
return &productservice.SearchResponse{Products: result}, nil
}
Create Server Interceptors
In productservice folder, create new folder named interceptors. In this folder, create new server interceptors as below:
BasicAuth Interceptor
In interceptors folder, create new go file named basicauinterceptor.go as below:
package interceptors
import (
"context"
"encoding/base64"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func BasicAuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
auth, err := extractHeader(ctx, "authorization")
if err != nil {
return ctx, err
}
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
return ctx, status.Error(codes.Unauthenticated, `missing "Basic " prefix in "Authorization" header`)
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return ctx, status.Error(codes.Unauthenticated, `invalid base64 in header`)
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return ctx, status.Error(codes.Unauthenticated, `invalid basic auth format`)
}
username, password := cs[:s], cs[s+1:]
if username != "acc1" || password != "123" {
return ctx, status.Error(codes.Unauthenticated, "invalid user or password")
}
return handler(ctx, req)
}
func extractHeader(ctx context.Context, header string) (string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", status.Error(codes.Unauthenticated, "no headers in request")
}
authHeaders, ok := md[header]
if !ok {
return "", status.Error(codes.Unauthenticated, "no header in request")
}
if len(authHeaders) != 1 {
return "", status.Error(codes.Unauthenticated, "more than 1 header in request")
}
return authHeaders[0], nil
}
Create Server
In productservice folder, create new folder named server. In this folder, create new file named main.go as below:
package main
import (
"fmt"
"log"
"net"
"productservice/handlers"
"productservice/interceptors"
productservice "productservice/proto"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
creds, err := credentials.NewServerTLSFromFile("../../keys/server-cert.pem", "../../keys/server-key.pem")
if err != nil {
log.Fatalf("Failed to setup tls: %v", err)
}
lis, err := net.Listen("tcp", ":1111")
if err != nil {
fmt.Println(err)
}
defer lis.Close()
s := handlers.ProductServiceServer{}
server := grpc.NewServer(
grpc.Creds(creds),
grpc.UnaryInterceptor(
grpc_middleware.ChainUnaryServer(
interceptors.BasicAuthInterceptor,
),
),
)
productservice.RegisterProductServiceServer(server, &s)
if err := server.Serve(lis); err != nil {
fmt.Println(err)
}
}
Start Server
Select main.go file in server folder and open it in Terminal window of Visual Studio Code. Use command line as below to start server:
go run main.go
Create Authentication Header
In src folder, create new folder named security. In this folder, create new file named basicauth.go as below:
package security
import (
"context"
"encoding/base64"
)
type BasicAuth struct {
Username string
Password string
}
func (basicAuth BasicAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
auth := basicAuth.Username + ":" + basicAuth.Password
enc := base64.StdEncoding.EncodeToString([]byte(auth))
return map[string]string{
"authorization": "Basic " + enc,
}, nil
}
func (BasicAuth) RequireTransportSecurity() bool {
return true
}
Create Client
In src folder, create new folder named client. In this folder, create new file named main.go as below:
package main
import (
"context"
"fmt"
"log"
productservice "productservice/proto"
"security"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
creds, err := credentials.NewClientTLSFromFile("../keys/server-cert.pem", "")
if err != nil {
log.Fatalf("Failed to setup tls: %v", err)
}
conn, err := grpc.Dial("localhost:1111",
grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(security.BasicAuth{
Username: "acc1",
Password: "123",
}),
)
if err != nil {
fmt.Println(err)
}
defer conn.Close()
productServ := productservice.NewProductServiceClient(conn)
response1, err1 := productServ.FindAll(context.Background(), &productservice.FindAllRequest{})
if err1 != nil {
fmt.Println(err1)
} else {
products := response1.Products
fmt.Println("Product List")
for _, product := range products {
fmt.Println("id: ", product.Id)
fmt.Println("name: ", product.Name)
fmt.Println("price: ", product.Price)
fmt.Println("quantity: ", product.Quantity)
fmt.Println("status: ", product.Status)
fmt.Println("========================")
}
}
response2, err2 := productServ.Search(context.Background(), &productservice.SearchRequest{Keyword: "vi"})
if err2 != nil {
fmt.Println(err2)
} else {
products := response2.Products
fmt.Println("Search Products")
for _, product := range products {
fmt.Println("id: ", product.Id)
fmt.Println("name: ", product.Name)
fmt.Println("price: ", product.Price)
fmt.Println("quantity: ", product.Quantity)
fmt.Println("status: ", product.Status)
fmt.Println("========================")
}
}
}
Start Client
Select main.go file in client folder and open it in Terminal window of Visual Studio Code. Use command line as below to start client:
go run main.go
Test with Valid Account
Use valid account for testing. Username: acc1 and password: 123
Product List
id: p01
name: tivi 1
price: 4.5
quantity: 4
status: true
========================
id: p02
name: tivi 2
price: 22
quantity: 3
status: false
========================
id: p03
name: laptop 3
price: 27
quantity: 3
status: true
========================
Search Products
id: p01
name: tivi 1
price: 4.5
quantity: 4
status: true
========================
id: p02
name: tivi 2
price: 22
quantity: 3
status: false
========================
Test with Invalid Account
Use valid account for testing. Username: acc2 and password: 456
rpc error: code = Unauthenticated desc = invalid user or password
rpc error: code = Unauthenticated desc = invalid user or password