Using gRPC and gRPC-Web with a Golang
Tags: go, grpc, grpcweb, envoy
Published: 2021-12-13 04:08:12


#Set Up
The final directory structure will look like this.
```
 go
  |_prj
     |_gRPC_prjweb_tmp
          |-api.proto
          |-client
          |  |_client.go
          |-docker-compose.yaml
          |-go.mod
          |-go.sum
          |-html
          |  |-dist
          |  |  |_main.js
          |  |-index.html
          |  |_index.js
          |-Makefile
          |-pb
          |  |-api_grpc_web_pb.js
          |  |-api.pb.go
          |  |-api_pb.js
          |  |-node_modeles
          |  |-package.json
          |  |_package-lock.json
          |-proxy
          |  |-conf
          |  |  |_envoy.yaml
          |  |_Dockerfile
          |_server
             |_server.go
          

```
And network structure will look like this.
```
gRPC_web(4444)<-->envoy(8080)<-->gRPC_server(8000)
```
First, build the environment.
```
go mod init web_tmp
go get google.golang.org/protobuf github.com/sirupsen/logrus golang.org/x/net/context google.golang.org/grpc google.golang.org/grpc/codes google.golang.org/grpc/status
```  
#gRPC
Create a proto file.
*[api.proto]*
```
syntax = "proto3";

service Greeter {
  rpc Hello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```
Create a pb directory, and generate Protocol Buffers code from api.proto file.

```
mkdir pb
protoc --go_out=plugins=grpc:pb api.proto
```

Create a server directory and a client directory.

```
mkdir server
mkdir client
```

Then we'll write the server and client code.
*[sever/server.go]*
```
package main

import (
    "fmt"
    pb "web_tmp/pb"
    "net"
    "github.com/sirupsen/logrus"
    "gINFO[0000] Greeting: Hello lupinolang.org/x/net/context"
    "google.golang.org/grpc"
)

func main() {
    listener, err := net.Listen("tcp", ":8000")
    if err != nil {
            panic(err)
    }
    server := grpc.NewServer()
    greeterService := &GreeterService{}
    pb.RegisterGreeterServer(server, greeterService)
    logrus.Info(fmt.Sprintf("start server: %#v", listener.Addr().String()))
    server.Serve(listener)
}

type GreeterService struct {}

func (s *GreeterService) Hello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
```
*[client/client.go]*
```
package main

import (
    "context"
    pb "web_tmp/pb"
    "github.com/sirupsen/logrus"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial("localhost:8000", grpc.WithInsecure())
    if err != nil {
            panic(err)
    }
    defer conn.Close()

    c := pb.NewGreeterClient(conn)
    name := &pb.HelloRequest{Name: "lupin"} 

    r, err := c.Hello(context.TODO(), name)
    if err != nil {
        logrus.Error(err)
    }
    logrus.Info("Greeting: ", r.GetMessage())
}

```
Create a makefile.
*[Makefile]*
```
.PHONY: server client

server:
        go run ./server/server.go

client: 
        go run ./client/client.go
```

Start the server.
```
make server
```

Run the client code.
```
make client
```
We can see the gRPC server returning "Hello lupin".
```
INFO[0000] Greeting: Hello lupin
```
<br>
#gRPC-web
Next, we'll write the code for gRPC-web communication.
```
protoc --js_out=import_style=commonjs:pb --grpc-web_out=import_style=commonjs,mode=grpcwebtext:pb api.proto
```
The api_grpc_web_pb created here under pb directory will be imported into the js file to be created later. Also, install the modules grpc-web and google-protobuf as they are required for webpack.

```
cd pb
npm install grpc-web
npm install google-protobuf
```

Create an html directory and create a js file.
```
mkdir html
```
*[html/index.js]*
```
import proto from '../pb/api_grpc_web_pb';
var client = new proto.GreeterClient('http://localhost:8080');
var request = new proto.HelloRequest();
request.setName("lupin");
client.hello(request, {}, function(err, response) {
    if (err) {
        console.log(err.code);
        console.log(err.message);
    } else {
        console.log(response.getMessage());
    }
});

```
<br>
##Proxy
Create a proxy directory and a conf directory under it for prepareing envpy proxy. Write the envoy.yaml file under the conf directory you created.

*[proxy/conf/envoy.yaml]*
```
admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 127.0.0.1, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          codec_type: auto
          stat_prefix: ingress_http
          access_log:
            - name: envoy.access_loggers.file
              typed_config:
                "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog
                path: "/dev/stdout"
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: greeter_service
                  max_grpc_timeout: 0s
              cors:
                allow_origin_string_match:
                  - prefix: "*"
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                max_age: "1728000"
                expose_headers: custom-header-1,grpc-status,grpc-message
          http_filters:
          - name: envoy.filters.http.grpc_web
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.router
  clusters:
  - name: greeter_service
    connect_timeout: 0.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
    dns_lookup_family: V4_ONLY
    upstream_connection_options:
      tcp_keepalive:
        keepalive_time: 300
    load_assignment:
      cluster_name: cluster_0
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: 172.17.0.1
                    port_value: 8000
```
Administration interface of envoy is 9901 port, and envoy proxy is 8080 port. Envoy passes the received communication to the server on port 8000. The docker address may vary depending on your environment, so check it.
```
ip a | grep docker
```

Create the Docker file.
*[proxy/Dockerfile]*
```
FROM envoyproxy/envoy:v1.15.0
COPY ./conf/envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
```

Start up the container in a proxy directory. 
```
docker build -t envoy/hello_lupin .
docker run -d --rm -p 8080:8080 -p 9901:9901 -v ~/go/prj/gRPC_prj/web_tmp/proxy/conf:/etc/envoy --name Greeter envoy/hello_lupin
```
Or create docker-compose.yaml in web_tmp directory.
*[docker-compose.yaml]*
```
version: '3'
services:
  envoy:
    build:
      context: ./proxy
    image: hello_lupin
    container_name: Greeter
    ports:
      - 8080:8080
```
Then, 
```
docker-compose up
```
By the way, if you want to stop the container
```
docker stop Greeter
```
Add the webpack command and the command to build the server to the make file and run it.
*[Makefile]*
```
.PHONY: server client make_webclient

server:
        go run ./server/server.go

client: 
        go run ./client/client.go

web_client:
        cd ./html && webpack ./index.js && static -p 4444
```
Then,
```
make web_client
```
Access 4444 port on localhost with a browser and check the Developer Tools console.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lh7k665whk1ek1pwjqov.png)