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
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());
}
});
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.
Top comments (3)
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!
Solved!! According to my architecture there was an error on the url in the index.js.
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