gRPC学习小札

gRPC

前言

之前这玩意一直没用到过,最近项目中用到了。好好研究下。

为什么使用gRPC

gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。

传输协议

gRPC: 可以使用TCP协议,也可以使用HTTP协议
HTTP:基于HTTP协议

传输效率

gRPC:使用自定义的TCP协议,或者使用HTTP协议,都可以减少报文的体积,提高传输的效率
HTTP:如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理

性能消耗

gRPC:gRPC消息使用一种有效的二进制消息格式protobuf进行序列化。Protobuf在服务器和客户机上的序列化非常快。Protobuf序列化后的消息体积很小,
能够有效负载,在移动应用程序等有限带宽场景中显得很重要
HTTP:大部分是通过json来实现的,字节大小和序列化耗时都比Protobuf要更消耗性能

gRPC主要用于公司内部的服务调用,性能消耗低,传输效率高。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。

gRPC入门

创建hello.proto文件,定义HelloService接口

syntax = "proto3";

package main;

message String {
    string value = 1;
}

service HelloService {
    rpc Hello (String) returns (String);
}

使用protoc-gen-go内置的gRPC插件生成gRPC代码:

protoc --go_out=plugins=grpc:. hello.proto

然后会生成hello.pb.go文件,服务端和客户端生成的代码都包含在内

运行服务端代码

package main

import (
	"context"
	"daily-test/gRPC/gRPC_base"
	"log"
	"net"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(
	ctx context.Context,args *gRPC_base.String,) (*gRPC_base.String,error) {
	reply := &gRPC_base.String{Value: "hello:" + args.GetValue()}
	return reply,nil
}

func main() {

	grpcServer := grpc.NewServer()
	gRPC_base.RegisterHelloServiceServer(grpcServer,new(HelloServiceImpl))

	lis,err := net.Listen("tcp",":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

首先是通过grpc.NewServer()构造一个gRPC服务对象,然后通过gRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过grpcServer.Serve(lis)在一个监听端口上提供gRPC服务。

定义客户端代码

package main

import (
	"context"
	"daily-test/gRPC/gRPC_base"
	"fmt"
	"log"

	"google.golang.org/grpc"
)

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

	client := gRPC_base.NewHelloServiceClient(conn)
	reply,err := client.Hello(context.Background(),&gRPC_base.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.GetValue())
}

其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是
一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。

gRPC流

RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间。因此传统的RPC方法调用对于上传和下载较大数据量场景
并不适合。同时传统RPC模式也不适用于对时间不确定的订阅和发布模式。为此,gRPC框架针对服务器端和客户端分别提供了流特性。

在HelloService增加一个支持双向流的Channel方法

syntax = "proto3";

package main;

message String {
    string value = 1;
}

service HelloService {
    rpc Hello (String) returns (String);

    rpc Channel (stream String) returns (stream String);
}

关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流。

使用protoc-gen-go内置的gRPC插件生成gRPC代码:

protoc --go_out=plugins=grpc:. hello.proto

重新生成代码可以看到接口中新增加的Channel方法的定义:

type HelloServiceServer interface {
    Hello(context.Context,*String) (*String,error)
    Channel(HelloService_ChannelServer) error
}
type HelloServiceClient interface {
    Hello(ctx context.Context,in *String,opts ...grpc.CallOption) (
        *String,error,)
    Channel(ctx context.Context,opts ...grpc.CallOption) (
        HelloService_ChannelClient,)
}

实现流服务

func (p *HelloServiceImpl) Channel(stream gRPC_stream.HelloService_ChannelServer) error {
	for {
		args,err := stream.Recv()
		if err != nil {
			// io.EOF表示客户端流关闭
			if err == io.EOF {
				return nil
			}
			return err
		}

		reply := &gRPC_stream.String{Value: "hello:" + args.GetValue()}

		err = stream.Send(reply)
		if err != nil {
			return err
		}
	}
}

实现服务端代码:

package main

import (
	"context"
	"daily-test/gRPC/gRPC_stream"
	"io"
	"log"
	"net"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(
	ctx context.Context,args *gRPC_stream.String,) (*gRPC_stream.String,error) {
	reply := &gRPC_stream.String{Value: "hello:" + args.GetValue()}
	return reply,nil
}

func (p *HelloServiceImpl) Channel(stream gRPC_stream.HelloService_ChannelServer) error {
	for {
		args,err := stream.Recv()
		if err != nil {
			// io.EOF表示客户端流关闭
			if err == io.EOF {
				return nil
			}
			return err
		}

		reply := &gRPC_stream.String{Value: "hello:" + args.GetValue()}

		err = stream.Send(reply)
		if err != nil {
			return err
		}
	}
}

func main() {

	grpcServer := grpc.NewServer()
	gRPC_stream.RegisterHelloServiceServer(grpcServer,":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

客户端代码

package main

import (
	"context"
	"daily-test/gRPC/gRPC_stream"
	"fmt"
	"io"
	"log"
	"time"

	"google.golang.org/grpc"
)

func main() {
	conn,grpc.WithInsecure())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := gRPC_stream.NewHelloServiceClient(conn)

	stream,err := client.Channel(context.Background())
	if err != nil {
		log.Fatal(err)
	}

	// 发送
	go func() {
		for {
			if err := stream.Send(&gRPC_stream.String{Value: "hi"}); err != nil {
				log.Fatal(err)
			}
			time.Sleep(time.Second)
		}
	}()

	// 接收
	for {
		reply,err := stream.Recv()
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Fatal(err)
		}
		fmt.Println(reply.GetValue())
	}

}

证书认证

gRPC建立在HTTP/2协议之上,对TLS提供了很好的支持。我们使用公钥,私钥,实现一个基本的认证

首先看下代码目录结构

gRPC_cert
├── cert
│   ├── cert.conf
│   ├── server.crt
│   └── server.key
├── client
│   └── main.go
├── hello.pb.go
├── hello.proto
└── server
    └── main.go

创建cert.conf

[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
 
[ dn ]
C = UK
ST = London
L = London
O = liz Ltd.
OU = Information Technologies
emailAddress = email@email.com
CN = localhost
 
[ req_ext ]
subjectAltName = @alt_names
 
[ alt_names ]
DNS.1 = localhost

生成private key

$ openssl genrsa -out cert/server.key 2048
 
$ openssl req -nodes -new -x509 -sha256 -days 1825 -config cert/cert.conf -extensions 'req_ext' -key cert/server.key -out cert/server.crt

启动gRPC服务端的时候传入证书的参数选项

package main

import (
	"context"
	"daily-test/gRPC/gRPC_cert"
	"log"
	"net"

	"google.golang.org/grpc/credentials"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(
	ctx context.Context,args *gRPC_cert.String,) (*gRPC_cert.String,error) {
	reply := &gRPC_cert.String{Value: "hello:" + args.GetValue()}
	return reply,nil
}

func main() {

	creds,err := credentials.NewServerTLSFromFile("./gRPC/gRPC_cert/cert/server.crt","./gRPC/gRPC_cert/cert/server.key")
	if err != nil {
		log.Fatal(err)
	}

	grpcServer := grpc.NewServer(grpc.Creds(creds))
	gRPC_cert.RegisterHelloServiceServer(grpcServer,":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

credentials.NewServerTLSFromFile函数是从文件为服务器构造证书对象,然后通过grpc.Creds(creds)函数将证书包装为选项后作为参数传入grpc.NewServer函数。

客户端基于服务端的证书和服务端名字进行对服务端的认证校验

package main

import (
	"context"
	"daily-test/gRPC/gRPC_cert"
	"fmt"
	"log"

	"google.golang.org/grpc/credentials"

	"google.golang.org/grpc"
)

func main() {
	// 带入证书的信息
	creds,err := credentials.NewClientTLSFromFile(
		"./gRPC/gRPC_cert/cert/server.crt","localhost",)
	if err != nil {
		log.Fatal(err)
	}

	conn,grpc.WithTransportCredentials(creds),)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := gRPC_cert.NewHelloServiceClient(conn)
	reply,&gRPC_cert.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.GetValue())
}

demo地址https://github.com/boilingfrog/daily-test/tree/master/gRPC/gRPC_cert

这种需要讲服务端的证书进行下发,这样客户在交互的时候每次都需要带过来,但是这样是不安全的。在传输的过程中证书存在被
监听和替换的可能性。

可以引入根证书,通过对服务端和客户端来进行签名,来保证安全。

使用根证书

为了避免证书的传递过程中被篡改,可以通过一个安全可靠的根证书分别对服务器和客户端的证书进行签名。这样客户端或服务器在收到对方的证书后可以通过根
证书进行验证证书的有效性。

首先看下我的项目目录

gRPC_cert_ca
├── cert
│   ├── ca.key
│   ├── ca.pem
│   ├── cert.conf
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   ├── server
│   │   ├── server.csr
│   │   ├── server.key
│   │   └── server.pem
│   ├── server.crt
│   └── server.key
├── client
│   └── main.go
├── hello.pb.go
├── hello.proto
└── server
    └── main.go

生成根证书

公钥

openssl genrsa -out ca.key 2048

秘钥

openssl req -new -x509 -days 7200 -key ca.key -out ca.pem

生成秘钥的时候需要填写信息

Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg,city) []:
Organization Name (eg,company) []:
Organizational Unit Name (eg,section) []:
Common Name (eg,fully qualified host name) []:localhost
Email Address []:

Common Name这个我们需要注意下,这个是主机名,测试的我就放了localhost

server端

生成 Key

openssl ecparam -genkey -name secp384r1 -out server.key

生成CSR

openssl req -new -key server.key -out server.csr

需要填写信息

Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg,fully qualified host name) []:localhost
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

使用ca进行证书的签发

openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in server.csr -out server.pem

注意下自己的ca公钥秘钥的路径问题

client

生成key

openssl ecparam -genkey -name secp384r1 -out client.key

生成csr

openssl req -new -key client.key -out client.csr

使用ca进行证书的签发

openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in client.csr -out client.pem

到此完成证书的生成

server端的代码

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"daily-test/gRPC/gRPC_cert_ca"
	"io/ioutil"
	"log"
	"net"

	"google.golang.org/grpc/credentials"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(
	ctx context.Context,args *gRPC_cert_ca.String,) (*gRPC_cert_ca.String,error) {
	reply := &gRPC_cert_ca.String{Value: "hello:" + args.GetValue()}
	return reply,nil
}

func main() {

	cert,err := tls.LoadX509KeyPair("./gRPC/gRPC_cert_ca/cert/server/server.pem","./gRPC/gRPC_cert_ca/cert/server/server.key")
	if err != nil {
		log.Fatalf("tls.LoadX509KeyPair err: %v",err)
	}

	certPool := x509.NewCertPool()
	ca,err := ioutil.ReadFile("./gRPC/gRPC_cert_ca/cert/ca.pem")
	if err != nil {
		log.Fatalf("ioutil.ReadFile err: %v",err)
	}

	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatalf("certPool.AppendCertsFromPEM err")
	}

	c := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{cert},ClientAuth:   tls.RequireAndVerifyClientCert,ClientCAs:    certPool,})

	grpcServer := grpc.NewServer(grpc.Creds(c))
	gRPC_cert_ca.RegisterHelloServiceServer(grpcServer,":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

服务器端使用credentials.NewTLS函数生成证书,通过ClientCAs选择CA根证书,并通过ClientAuth选项启用对客户端进行验证。

客户端代码

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"daily-test/gRPC/gRPC_cert_ca"
	"fmt"
	"io/ioutil"
	"log"

	"google.golang.org/grpc/credentials"

	"google.golang.org/grpc"
)

func main() {
	cert,err := tls.LoadX509KeyPair("./gRPC/gRPC_cert_ca/cert/client/client.pem","./gRPC/gRPC_cert_ca/cert/client/client.key")
	if err != nil {
		log.Fatalf("tls.LoadX509KeyPair err: %v",ServerName:   "localhost",RootCAs:      certPool,})

	conn,err := grpc.Dial(":1234",grpc.WithTransportCredentials(c),)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := gRPC_cert_ca.NewHelloServiceClient(conn)
	reply,&gRPC_cert_ca.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.GetValue())

}

credentials.NewTLS函数调用中,客户端通过引入一个CA根证书和服务器的名字来实现对服务器进行验证。客户端在链接服务器时会首先请求服务器的证书,
然后使用CA根证书对收到的服务器端证书进行验证。

ca认证的demohttps://github.com/boilingfrog/daily-test/tree/master/gRPC/gRPC_cert_ca

gRPC实现token认证

对于每个gRPC请求我们还会用到token的认证

要实现对每个gRPC方法进行认证,需要实现grpc.PerRPCCredentials接口:

type PerRPCCredentials interface {
    // GetRequestMetadata gets the current request metadata,refreshing
    // tokens if required. This should be called by the transport layer on
    // each request,and the data should be populated in headers or other
    // context. If a status code is returned,it will be used as the status
    // for the RPC. uri is the URI of the entry point for the request.
    // When supported by the underlying implementation,ctx can be used for
    // timeout and cancellation.
    // TODO(zhaoq): Define the set of the qualified keys instead of leaving
    // it as an arbitrary string.
    GetRequestMetadata(ctx context.Context,uri ...string) (
        map[string]string,)
    // RequireTransportSecurity indicates whether the credentials requires
    // transport security.
    RequireTransportSecurity() bool
}

GetRequestMetadata方法中返回认证需要的必要信息。RequireTransportSecurity方法表示是否要求底层使用安全链接。在真实的环境中建议必须要求
底层启用安全的链接,否则认证信息有泄露和被篡改的风险。

看下代码结构

gRPC_token
├── client
│   └── main.go
├── hello.pb.go
├── hello.proto
└── server
    └── main.go

服务端代码

package main

import (
	"context"
	"daily-test/gRPC/gRPC_token"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"

	"google.golang.org/grpc"
)

type Authentication struct {
	User     string
	Password string
}

type grpcServer struct {
	auth *Authentication
}

func (p *grpcServer) Hello(
	ctx context.Context,args *gRPC_token.String,) (*gRPC_token.String,error) {
	// 初始化信息,测试用的
	p.Init()
	// 检验
	if err := p.auth.Auth(ctx); err != nil {
		return nil,err
	}

	reply := &gRPC_token.String{Value: "hello:" + args.GetValue()}
	return reply,nil
}

// 测试使用
func (p *grpcServer) Init() {
	p.auth = &Authentication{
		User:     "liz",Password: "123456",}
}

// 认证
func (a *Authentication) Auth(ctx context.Context) error {
	md,ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return fmt.Errorf("missing credentials")
	}

	var appid string
	var appkey string

	if val,ok := md["user"]; ok {
		appid = val[0]
	}
	if val,ok := md["password"]; ok {
		appkey = val[0]
	}

	if appid != a.User || appkey != a.Password {
		return grpc.Errorf(codes.Unauthenticated,"invalid token")
	}

	return nil
}

func main() {

	grpcServer1 := grpc.NewServer()
	gRPC_token.RegisterHelloServiceServer(grpcServer1,new(grpcServer))

	lis,":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer1.Serve(lis)
}

主要通过Authentication.Auth()实现了对token信息的校验,通过metadata.FromIncomingContext(ctx)获取请求中的认证信息,然后进行匹配校验。

客户端代码

package main

import (
	"context"
	"daily-test/gRPC/gRPC_token"
	"fmt"
	"log"

	"google.golang.org/grpc"
)

type Authentication struct {
	User     string
	Password string
}

// 返回认证需要的必要信息
func (a *Authentication) GetRequestMetadata(context.Context,...string) (
	map[string]string,) {
	return map[string]string{"user": a.User,"password": a.Password},nil
}

// 表示是否要求底层使用安全链接,测试的代码就是使用了false
func (a *Authentication) RequireTransportSecurity() bool {
	return false
}

func main() {
	// 初始化账户,密码
	auth := Authentication{
		User:     "liz",}

	conn,grpc.WithInsecure(),grpc.WithPerRPCCredentials(&auth),)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := gRPC_token.NewHelloServiceClient(conn)
	reply,&gRPC_token.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.GetValue())
}

通过grpc.WithPerRPCCredentials函数将Authentication对象转为grpc.Dial参数。测试的代码,使用grpc.WithInsecure()忽略证书认证。

代码的demohttps://github.com/boilingfrog/daily-test/tree/master/gRPC/gRPC_token

和Web服务共存

gRPC构建在HTTP/2协议之上,因此我们可以将gRPC服务和普通的Web服务架设在同一个端口之上。

代码在之前的ca认证的demo基础之上,加入web服务

我的目录结构

gRPC_web
├── cert
│   ├── ca.key
│   ├── ca.pem
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   └── server
│       ├── server.csr
│       ├── server.key
│       └── server.pem
├── client
│   └── main.go
├── hello.pb.go
├── hello.proto
└── server
    └── main.go

服务端代码

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"daily-test/gRPC/gRPC_web"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"

	"google.golang.org/grpc/credentials"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(
	ctx context.Context,args *gRPC_web.String,) (*gRPC_web.String,error) {
	reply := &gRPC_web.String{Value: "hello:" + args.GetValue()}
	return reply,err := tls.LoadX509KeyPair("./gRPC/gRPC_web/cert/server/server.pem","./gRPC/gRPC_web/cert/server/server.key")
	if err != nil {
		log.Fatalf("tls.LoadX509KeyPair err: %v",err := ioutil.ReadFile("./gRPC/gRPC_web/cert/ca.pem")
	if err != nil {
		log.Fatalf("ioutil.ReadFile err: %v",})

	grpcServer := grpc.NewServer(grpc.Creds(c))

	gRPC_web.RegisterHelloServiceServer(grpcServer,new(HelloServiceImpl))

	mux := http.NewServeMux()
	mux.HandleFunc("/",func(w http.ResponseWriter,req *http.Request) {
		w.Write([]byte("hello"))
	})

	http.ListenAndServeTLS(":1234","./gRPC/gRPC_web/cert/server/server.pem","./gRPC/gRPC_web/cert/server/server.key",http.HandlerFunc(func(w http.ResponseWriter,r *http.Request) {
			if r.ProtoMajor != 2 {
				mux.ServeHTTP(w,r)
				return
			}
			if strings.Contains(
				r.Header.Get("Content-Type"),"application/grpc",) {
				grpcServer.ServeHTTP(w,r)
				return
			}

			mux.ServeHTTP(w,r)
			return
		}),)
}

因为gRPC服务已经实现了ServeHTTP方法,可以直接作为Web路由处理对象。如果将gRPC和Web服务放在一起,会导致gRPC和Web路径的冲突,在处理时我们需要区分两类服务。

客户端实现

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"daily-test/gRPC/gRPC_web"
	"fmt"
	"io/ioutil"
	"log"

	"google.golang.org/grpc/credentials"

	"google.golang.org/grpc"
)

func main() {
	cert,err := tls.LoadX509KeyPair("./gRPC/gRPC_web/cert/client/client.pem","./gRPC/gRPC_web/cert/client/client.key")
	if err != nil {
		log.Fatalf("tls.LoadX509KeyPair err: %v",)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	client := gRPC_web.NewHelloServiceClient(conn)
	reply,&gRPC_web.String{Value: "hello"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.GetValue())

}

当然除了grpc可以调用之外,我们同样可以请求web服务进行调用

demo地址https://github.com/boilingfrog/daily-test/tree/master/gRPC/gRPC_web

验证器

Protobuf中可以加入对字段的校验

一个简单的认证

看下我的代码结构

gRPC_validator
├── client
│   └── main.go
├── hello.pb.go
├── hello.proto
├── hello.validator.pb.go
└── server
    └── main.go
syntax = "proto3";

package gRPC_validator;

import "github.com/mwitkow/go-proto-validators/validator.proto";


service HelloService {
    rpc Hello (RequestInfo) returns (String);
}

message RequestInfo {
    string name = 1 [
        (validator.field) = {regex: "^[a-zA-Z0-9_-]{4,16}$"}
    ];
    int64 age = 2 [
        (validator.field) = {int_gt: 0,int_lt: 100}
    ];
}

message String {
    string value = 1;
}

然后生成go代码,不过我们需要下载一个新的插件

$ go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators

使用新的插件生成代码

$ protoc --proto_path=. --proto_path=${GOPATH}/src --govalidators_out=. --go_out=plugins=grpc:. hello.proto

注意

我们要弄明白protoc中proto_path参数的含义

  • proto_path: 指定了在哪个目录中搜索import中导入的和要编译为.go的proto文件,可以定义多个

所以添加proto_path就可以了,指定两个地址,一个是import的地址,一个是要编译为.go的proto文件的地址

$ protoc --proto_path=. --proto_path=${GOPATH}/src --govalidators_out=. --go_out=plugins=grpc:. hello.proto

需要注意下,要不会出现import找不到的错误

然后我们会发现生成一个独立的名为hello.validator.pb.go的文件:

// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: hello.proto

package gRPC_validator

import (
	fmt "fmt"
	proto "github.com/golang/protobuf/proto"
	_ "github.com/mwitkow/go-proto-validators"
	github_com_mwitkow_go_proto_validators "github.com/mwitkow/go-proto-validators"
	math "math"
	regexp "regexp"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

var _regex_RequestInfo_Name = regexp.MustCompile(`^[a-zA-Z0-9_-]{4,16}$`)

func (this *RequestInfo) Validate() error {
	if !_regex_RequestInfo_Name.MatchString(this.Name) {
		return github_com_mwitkow_go_proto_validators.FieldError("Name",fmt.Errorf(`value '%v' must be a string conforming to regex "^[a-zA-Z0-9_-]{4,16}$"`,this.Name))
	}
	if !(this.Age > 0) {
		return github_com_mwitkow_go_proto_validators.FieldError("Age",fmt.Errorf(`value '%v' must be greater than '0'`,this.Age))
	}
	if !(this.Age < 100) {
		return github_com_mwitkow_go_proto_validators.FieldError("Age",fmt.Errorf(`value '%v' must be less than '100'`,this.Age))
	}
	return nil
}
func (this *String) Validate() error {
	return nil
}

调用这个函数,实现对字段的校验

demo地址:https://github.com/boilingfrog/daily-test/tree/master/gRPC/gRPC_validator

REST接口

开元社区中grpc-gateway项目就实现了将gRPC服务转为REST服务的能力。

grpc-gateway的工作原理如下图

通过在Protobuf文件中添加路由相关的元信息,通过自定义的代码插件生成路由相关的处理代码,最终将REST请求转给更后端的gRPC服务处理。

放上我的代码目录

gRPC_restful
├── client
│   └── main.go
├── hello.pb.go
├── hello.pb.gw.go
├── hello.proto
├── httpServer
│   └── main.go
└── server
    └── main.go

Protobuf代码

syntax = "proto3";

package gRPC_restful;

import "google/api/annotations.proto";

message StringMessage {
  string value = 1;
}

service RestService {
    rpc GetMes(StringMessage) returns (StringMessage) {
        option (google.api.http) = {
            get: "/mes/{value}"
        };
    }
    rpc PostMes(StringMessage) returns (StringMessage) {
        option (google.api.http) = {
            post: "/mes"
            body: "*"
        };
    }
}

上面的GetMes和PostMes提供了rpc接口,然后下面定义了相应的restful接口

我们需要安装下protoc-gen-grpc-gateway插件

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

然后生成代码

protoc --proto_path=. --proto_path=$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=. --go_out=plugins=grpc:. hello.proto

proto_path的patch需要注意下

grpc的server

package main

import (
	"context"
	"daily-test/gRPC/gRPC_restful"
	"log"
	"net"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) GetMes(ctx context.Context,args *gRPC_restful.StringMessage) (*gRPC_restful.StringMessage,error) {
	reply := &gRPC_restful.StringMessage{Value: args.Value}
	return reply,nil
}

func (p *HelloServiceImpl) PostMes(ctx context.Context,error) {
	reply := &gRPC_restful.StringMessage{Value: args.Value + "post"}
	return reply,nil
}

func main() {
	grpcServer := grpc.NewServer()
	gRPC_restful.RegisterRestServiceServer(grpcServer,":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

http的server

package main

import (
	"context"
	"daily-test/gRPC/gRPC_restful"
	"log"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"google.golang.org/grpc"
)

func main() {
	ctx := context.Background()
	ctx,cancel := context.WithCancel(ctx)
	defer cancel()

	mux := runtime.NewServeMux()

	err := gRPC_restful.RegisterRestServiceHandlerFromEndpoint(
		ctx,mux,"localhost:1234",[]grpc.DialOption{grpc.WithInsecure()},)
	if err != nil {
		log.Fatal(err)
	}

	http.ListenAndServe(":8080",mux)
}

demo地址:https://github.com/boilingfrog/daily-test/tree/master/gRPC/gRPC_restful

grpcurl工具

gRPC同样也提供了一个名为reflection的反射包,用于为gRPC服务提供查询。配合grpcurl一款使用go写,就能查询gRPC列表或调用gRPC方法。

启动反射服务

package main

import (
	"context"
	"daily-test/gRPC/gRPC_grpcurl"
	"log"
	"net"

	"google.golang.org/grpc/reflection"

	"google.golang.org/grpc"
)

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(ctx context.Context,args *gRPC_grpcurl.String) (*gRPC_grpcurl.String,error) {
	reply := &gRPC_grpcurl.String{Value: "hello:" + args.GetValue()}
	return reply,nil
}

func main() {

	grpcServer := grpc.NewServer()
	gRPC_grpcurl.RegisterHelloServiceServer(grpcServer,new(HelloServiceImpl))

	// Register reflection service on gRPC server.
	reflection.Register(grpcServer)

	lis,":1234")
	if err != nil {
		log.Fatal(err)
	}
	grpcServer.Serve(lis)
}

服务端的加入

	reflection.Register(grpcServer)

将grpc.Server注册到反射服务中,下游就能进行反射的查看。

查看服务列表

首先手动安装grpcurl工具

$ go get github.com/fullstorydev/grpcurl
$ go install github.com/fullstorydev/grpcurl/cmd/grpcurl

命令常用到的参数

  • -plaintext 忽略tls证书的验证过程
  • -cert 配置公钥信息
  • -key 配置私钥信息
  • list 获取端口上的grpc服务的列表
  • describe 查看服务的描述信息

查看服务列表

$ grpcurl -plaintext  localhost:1234 list
gRPC_grpcurl.HelloService
grpc.reflection.v1alpha.ServerReflection

查看某个服务的方法列表

$ grpcurl -plaintext  localhost:1234 list gRPC_grpcurl.HelloService
gRPC_grpcurl.HelloService.Hello

查看某个服务的描述信息

$ grpcurl -plaintext  localhost:1234 describe  gRPC_grpcurl.HelloService
gRPC_grpcurl.HelloService is a service:
service HelloService {
  rpc Hello ( .gRPC_grpcurl.String ) returns ( .gRPC_grpcurl.String );
}

获取类型信息

$ grpcurl -plaintext  localhost:1234 describe  gRPC_grpcurl.String
gRPC_grpcurl.String is a message:
message String {
  string value = 1;
}

测试请求

调用方法,可以使用grpcurl代替客户端进行访问请求

$ grpcurl -plaintext -d '{"value": "gopher"}' localhost:1234 gRPC_grpcurl.HelloService.Hello
{
  "value": "hello:gopher"
}

-d后面跟的是请求的json数据

如果-d参数是@则表示从标准输入读取json输入参数,这一般用于比较输入复杂的json数据,也可以用于测试流方法。

参考

https://www.cnblogs.com/yilezhu/p/10645804.html】https://www.cnblogs.com/yilezhu/p/10645804.html
【gRPC 官方文档中文版】https://doc.oschina.net/grpc?t=60133
【HTTP和RPC的优缺点】https://cloud.tencent.com/developer/article/1353110
【gRPC入门】https://chai2010.gitbooks.io/advanced-go-programming-book/content/ch4-rpc/ch4-04-grpc.html
【Using TLS/SSL certificates for gRPC client and server communications in Golang - Updated】http://www.inanzzz.com/index.php/post/jo4y/using-tls-ssl-certificates-for-grpc-client-and-server-communications-in-golang-updated

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


gRPC 前言 为什么使用gRPC 传输协议 传输效率 性能消耗 gRPC入门 gRPC流 证书认证 使用根证书 gRPC实现token认证 和Web服务共存 验证器 REST接口 grpcurl工具
参考文章: 1.&#160;https://www.cnblogs.com/kaixinyufeng/p/9651513.html 2.&#160;http://jia-shun.cn/2018/08
今天给大家翻译一篇由ASP.NET首席开发工程师 &quot;James Newton King&quot; 前几天发表的一篇博客,文中带来了一个实验性的产品gRPC Web。大家可以点击文末的讨论帖
上一篇文章我带着大家体验了一把《 &quot;ASP.NET Core 3.0 上的gRPC服务模板初体验(多图)&quot; 》,如果有兴趣的可以点击链接进行查看,相信跟着做的你,也是可以跑起来的。
早就听说ASP.NET Core 3.0中引入了gRPC的服务模板,正好趁着家里电脑刚做了新系统,然后装了VS2019的功夫来体验一把。同时记录体验的过程。如果你也想按照本文的步骤体验的话,那你得先安
这篇笔记主要是记录学习历程而不是怎么用~,以及protobuffers 和 gprc 各种文档的地址,等过上大半年后通过这篇笔记帮助自己快速重新掌握这个技术点 一、Protocolbuffers 关于
最近GRPC很火,感觉整RPC不用GRPC都快跟不上时髦了。 gRPC设计 gRPC是一种与语言无关的高性能远程过程调用 (RPC) 框架。刚好需要使用一个的RPC应用系统,自然而然就盯上了它,但是它
   gRPC是google开源提供的一个RPC软件框架,它的特点是极大简化了传统RPC的开发流程和代码量,使用户可以免除许多陷阱并聚焦于实际应用逻辑中。作为一种google的最新RPC解决方案,gRPC具备了以下这些强项: 1、gRPC在HTTP/2协议上用protobuf取代了json实现了最佳效率 2、用IDL(Interface Definition Language),一种简单的描述语言
  接着上期讨论的gRPC unary服务我们跟着介绍gRPC streaming,包括: Server-Streaming, Client-Streaming及Bidirectional-Streaming。我们首先在.proto文件里用IDL描述Server-Streaming服务: /* * responding stream of increment results */ servi
我已经设法通过GRPC使用流媒体模式的服务帐户为我的 Android应用程序运行Google Cloud Speech.但是,根据我所读到的内容,出于安全原因,我不应该在其中部署具有这些凭据的Android应用程序(当前存储为资源中的JSON文件).正确的是创建一个API密钥,如下所述: https://cloud.google.com/speech/docs/common/auth 这允许我限制
  安装protobuf go get -u github.com/golang/protobuf/proto go get -u github.com/golang/protobuf/protoc-gen-go 此时会生成protoc-gen-go,protoc一般是获取已经编译好的可执行文件(https://github.com/google/protobuf/releases)   安装gR
一、grpc安装 将 https://github.com/google/go-genproto 放到 $GOPATH/src/google.golang.org/genproto 将 https://github.com/grpc/grpc-go 放到 $GOPATH/src/google.golang.org/grpc 将 https://github.com/golang/t
参考URL: https://segmentfault.com/a/1190000015220713?utm_source=channel-hottest gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, P
我试图在电子应用程序中要求grpc,但我收到以下错误: Error: dlopen(/srv/node_modules/grpc/src/node/extension_binary/grpc_node.node, 1): Symbol not found: _GENERAL_NAME_free Referenced from: /srv/node_modules/grpc/src/node/e
我试图调用GRPC端点,但我想提供客户身份验证标头.我在哪里指定这个? var client = new proto.Publisher('127.0.0.1:50051', grpc.credentials.createInsecure()); var customHeader = { 'authorization': 'secret' } client.publish(d
我正在尝试创建一个 java grpc客户端来与go中的服务器通信.我是grpc的新手所以遵循本教程 gRPC Java Tutorial.在这些示例中,它们指的是阻塞和非阻塞存根,它们似乎是从 github的其他地方导入的. import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideBlockingStub; import io.gr
我正在尝试做类似下面的事情(即使用流式grpc调用从客户端向服务器发送数据).代码参考取自官方网站上给出的grpc示例,用于解释目的: 客户端代码: ClientContext context; context.AddMetadata("authorization", "abcd"); context.set_deadline(...); std::unique_ptr<ClientWriter
什么是gRPC gRPC是google开源的一个高性能、跨语言的RPC框架,基于HTTP2协议,采用ProtoBuf 定义的IDL。 gRPC 的主要优点是: 现代高性能轻量级 RPC 框架。 协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的实现。 可用于多种语言的工具,以生成强类型服务器和客户端。 支持客户端、服务器和双向流式处理调用。 使用 Protobuf 二进制序列化减少对网络
一.简介 gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架。 gRPC使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建。它使用HTTP/2作为通信协议,使用 Protocol Buffers 作为序列化协议。 它的主要优点: 现代高性能轻量级 RPC 框架。 约定优先的 API 开发,默认使用 Protocol Buffers 作为描述语言,允许
目录 ASP.NET Core 3.0 使用gRPC ASP.NET Core 3.0 gRPC 双向流 ASP.NET Core 3.0 gRPC 认证授权 一.前言 在前一文 《ASP.NET Core 3.0 使用gRPC》中有提到 gRPC 支持双向流调用,支持实时推送消息,这也是 gRPC的一大特点,且 gRPC 在对双向流的控制支持上也是非常强大的。 二. 什么是 gRPC 流 gRP