DEV Community

ikkyu
ikkyu

Posted on

Using gRPC and gRPC-Web with a Golang

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


Enter fullscreen mode Exit fullscreen mode

And network structure will look like this.

gRPC_web(4444)<-->envoy(8080)<-->gRPC_server(8000)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Create a pb directory, and generate Protocol Buffers code from api.proto file.

mkdir pb
protoc --go_out=plugins=grpc:pb api.proto
Enter fullscreen mode Exit fullscreen mode

Create a server directory and a client directory.

mkdir server
mkdir client
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

[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())
}

Enter fullscreen mode Exit fullscreen mode

Create a makefile.
[Makefile]

.PHONY: server client

server:
        go run ./server/server.go

client: 
        go run ./client/client.go
Enter fullscreen mode Exit fullscreen mode

Start the server.

make server
Enter fullscreen mode Exit fullscreen mode

Run the client code.

make client
Enter fullscreen mode Exit fullscreen mode

We can see the gRPC server returning "Hello lupin".

INFO[0000] Greeting: Hello lupin
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create an html directory and create a js file.

mkdir html
Enter fullscreen mode Exit fullscreen mode

[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());
    }
});

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then,

docker-compose up
Enter fullscreen mode Exit fullscreen mode

By the way, if you want to stop the container

docker stop Greeter
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then,

make web_client
Enter fullscreen mode Exit fullscreen mode

Access 4444 port on localhost with a browser and check the Developer Tools console.

Image description

Discussion (3)

Collapse
umbeb profile image
Umberto Benelli

Hi @ikk_hck ,
it's a very interesting article!
I tried to follow this tutorial but I had a problem at the end of this steps.
When I run the following command:

make web_client

I received this error:
ERROR in ../pb/node_modules/google-protobuf/google-protobuf.js
Module not found: Error: Can't resolve './../../../html/buffer' in '/home/ubuntu/gitprojects/grpc-test/tutorialprj/pb/node_modules/google-protobuf'
@ ../pb/node_modules/google-protobuf/google-protobuf.js 1:0-33
@ ../pb/api_pb.js
@ ../pb/api_grpc_web_pb.js
@ ./index.js

It's due to "webpack ./index.js" command.

Do you know how I could fix this error?
Thank you very much!

Collapse
umbeb profile image
Umberto Benelli

Solved!! According to my architecture there was an error on the url in the index.js.

Collapse
lorenzotinfena profile image
Lorenzo Tinfena

Seems to early for grpc on browser... I made an app, initially the idea was create a site with grpc, but I ended up with an app, only for the grpc native