gRPC,爆赞,vivo研发面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Python全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Python知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注Python)
img

正文

if err != nil {

log.Fatal(err)

}

fmt.Println(reply.Message)

}

这样就完成了最基础的 gRPC 服务的开发,接下来我们就在这个「基础模板」上不断丰富,学习更多特性。

流方式

接下来看看流的方式,顾名思义,数据可以源源不断的发送和接收。

流的话分单向流和双向流,这里我们直接通过双向流来举例。

proto

service Greeter {

// Sends a greeting

rpc SayHello (HelloRequest) returns (HelloReply) {}

// Sends stream message

rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {}

}

增加一个流函数 SayHelloStream,通过 stream 关键词来指定流特性。

需要重新生成 helloworld.pb.go 文件,这里不再多说。

server

func (*greeter) SayHelloStream(stream proto.Greeter_SayHelloStreamServer) error {

for {

args, err := stream.Recv()

if err != nil {

if err == io.EOF {

return nil

}

return err

}

fmt.Println("Recv: " + args.Name)

reply := &proto.HelloReply{Message: "hi " + args.Name}

err = stream.Send(reply)

if err != nil {

return err

}

}

}

在「基础模板」上增加 SayHelloStream 函数,其他都不需要变。

client

client := proto.NewGreeterClient(conn)

// 流处理

stream, err := client.SayHelloStream(context.Background())

if err != nil {

log.Fatal(err)

}

// 发送消息

go func() {

for {

if err := stream.Send(&proto.HelloRequest{Name: “zhangsan”}); 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.Message)

}

通过一个 goroutine 发送消息,主程序的 for 循环接收消息。

执行程序会发现,服务端和客户端都不断有打印输出。

验证器

接下来是验证器,这个需求是很自然会想到的,因为涉及到接口之间的请求,那么对参数进行适当的校验是很有必要的。

在这里我们使用 protoc-gen-govalidators 和 go-grpc-middleware 来实现。

先安装:

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

go get github.com/grpc-ecosystem/go-grpc-middleware

接下来修改 proto 文件:

proto

import “github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto”;

message HelloRequest {

string name = 1 [

(validator.field) = {regex: “1{2,5}$”}

];

}

在这里对 name 参数进行校验,需要符合正则的要求才可以正常请求。

还有其他验证规则,比如对数字大小进行验证等,这里不做过多介绍。

接下来生成 *.pb.go 文件:

protoc \

–proto_path=${GOPATH}/pkg/mod \

–proto_path=${GOPATH}/pkg/mod/github.com/gogo/protobuf@v1.3.2 \

–proto_path=. \

–govalidators_out=. --go_out=plugins=grpc:.\

*.proto

执行成功之后,目录下会多一个 helloworld.validator.pb.go 文件。

这里需要特别注意一下,使用之前的简单命令是不行的,需要使用多个 proto_path 参数指定导入 proto 文件的目录。

官方给了两种依赖情况,一个是 google protobuf,一个是 gogo protobuf。我这里使用的是第二种。

即使使用上面的命令,也有可能会遇到这个报错:

Import “github.com/mwitkow/go-proto-validators/validator.proto” was not found or had errors

但不要慌,大概率是引用路径的问题,一定要看好自己的安装版本,以及在 GOPATH 中的具体路径。

最后是服务端代码改造:

引入包:

grpc_middleware “github.com/grpc-ecosystem/go-grpc-middleware”

grpc_validator “github.com/grpc-ecosystem/go-grpc-middleware/validator”

然后在初始化的时候增加验证器功能:

server := grpc.NewServer(

grpc.UnaryInterceptor(

grpc_middleware.ChainUnaryServer(

grpc_validator.UnaryServerInterceptor(),

),

),

grpc.StreamInterceptor(

grpc_middleware.ChainStreamServer(

grpc_validator.StreamServerInterceptor(),

),

),

)

启动程序之后,我们再用之前的客户端代码来请求,会收到报错:

2021/10/11 18:32:59 rpc error: code = InvalidArgument desc = invalid field Name: value ‘zhangsan’ must be a string conforming to regex “2{2,5}$”

exit status 1

因为 name: zhangsan 是不符合服务端正则要求的,但是如果传参 name: zzz,就可以正常返回了。

Token 认证

终于到认证环节了,先看 Token 认证方式,然后再介绍证书认证。

先改造服务端,有了上文验证器的经验,那么可以采用同样的方式,写一个拦截器,然后在初始化 server 时候注入。

认证函数:

func Auth(ctx context.Context) error {

md, ok := metadata.FromIncomingContext(ctx)

if !ok {

return fmt.Errorf(“missing credentials”)

}

var user string

var password string

if val, ok := md[“user”]; ok {

user = val[0]

}

if val, ok := md[“password”]; ok {

password = val[0]

}

if user != “admin” || password != “admin” {

return grpc.Errorf(codes.Unauthenticated, “invalid token”)

}

return nil

}

metadata.FromIncomingContext 从上下文读取用户名和密码,然后和实际数据进行比较,判断是否通过认证。

拦截器:

var authInterceptor grpc.UnaryServerInterceptor

authInterceptor = func(

ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,

) (resp interface{}, err error) {

//拦截普通方法请求,验证 Token

err = Auth(ctx)

if err != nil {

return

}

// 继续处理请求

return handler(ctx, req)

}

初始化:

server := grpc.NewServer(

grpc.UnaryInterceptor(

grpc_middleware.ChainUnaryServer(

authInterceptor,

grpc_validator.UnaryServerInterceptor(),

),

),

grpc.StreamInterceptor(

grpc_middleware.ChainStreamServer(

grpc_validator.StreamServerInterceptor(),

),

),

)

除了上文的验证器,又多了 Token 认证拦截器 authInterceptor

最后是客户端改造,客户端需要实现 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, error,

)

// RequireTransportSecurity indicates whether the credentials requires

// transport security.

RequireTransportSecurity() bool

}

GetRequestMetadata 方法返回认证需要的必要信息,RequireTransportSecurity 方法表示是否启用安全链接,在生产环境中,一般都是启用的,但为了测试方便,暂时这里不启用了。

实现接口:

type Authentication struct {

User string

Password string

}

func (a *Authentication) GetRequestMetadata(context.Context, …string) (

map[string]string, error,

) {

return map[string]string{“user”: a.User, “password”: a.Password}, nil

}

func (a *Authentication) RequireTransportSecurity() bool {

return false

}

连接:

conn, err := grpc.Dial(“localhost:50051”, grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))

好了,现在我们的服务就有 Token 认证功能了。如果用户名或密码错误,客户端就会收到:

2021/10/11 20:39:35 rpc error: code = Unauthenticated desc = invalid token

exit status 1

如果用户名和密码正确,则可以正常返回。

单向证书认证

证书认证分两种方式:

  1. 单向认证

  2. 双向认证

先看一下单向认证方式:

生成证书

首先通过 openssl 工具生成自签名的 SSL 证书。

1、生成私钥:

openssl genrsa -des3 -out server.pass.key 2048

2、去除私钥中密码:

openssl rsa -in server.pass.key -out server.key

3、生成 csr 文件:

openssl req -new -key server.key -out server.csr -subj “/C=CN/ST=beijing/L=beijing/O=grpcdev/OU=grpcdev/CN=example.grpcdev.cn”

4、生成证书:

openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

再多说一句,分别介绍一下 X.509 证书包含的三个文件:key,csr 和 crt。

  • key: 服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。

  • csr: 证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。

  • crt: 由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。

gRPC 代码

证书有了之后,剩下的就是改造程序了,首先是服务端代码。

// 证书认证-单向认证

creds, err := credentials.NewServerTLSFromFile(“keys/server.crt”, “keys/server.key”)

if err != nil {

log.Fatal(err)

return

}

server := grpc.NewServer(grpc.Creds(creds))

只有几行代码需要修改,很简单,接下来是客户端。

由于是单向认证,不需要为客户端单独生成证书,只需要把服务端的 crt 文件拷贝到客户端对应目录下即可。

// 证书认证-单向认证

creds, err := credentials.NewClientTLSFromFile(“keys/server.crt”, “example.grpcdev.cn”)

if err != nil {

log.Fatal(err)

return

}

conn, err := grpc.Dial(“localhost:50051”, grpc.WithTransportCredentials(creds))

好了,现在我们的服务就支持单向证书认证了。

但是还没完,这里可能会遇到一个问题:

2021/10/11 21:32:37 rpc error: code = Unavailable desc = connection error: desc = “transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0”

exit status 1

原因是 Go 1.15 开始废弃了 CommonName,推荐使用 SAN 证书。如果想要兼容之前的方式,可以通过设置环境变量的方式支持,如下:

export GODEBUG=“x509ignoreCN=0”

但是需要注意,从 Go 1.17 开始,环境变量就不再生效了,必须通过 SAN 方式才行。所以,为了后续的 Go 版本升级,还是早日支持为好。

双向证书认证

最后来看看双向证书认证。

生成带 SAN 的证书

还是先生成证书,但这次有一点不一样,我们需要生成带 SAN 扩展的证书。

什么是 SAN?

SAN(Subject Alternative Name)是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。

将默认的 OpenSSL 配置文件拷贝到当前目录。

Linux 系统在:

/etc/pki/tls/openssl.cnf

Mac 系统在:

/System/Library/OpenSSL/openssl.cnf

修改临时配置文件,找到 [ req ] 段落,然后将下面语句的注释去掉。

req_extensions = v3_req # The extensions to add to a certificate request

接着添加以下配置:

[ v3_req ]

Extensions to add to a certificate request

basicConstraints = CA:FALSE

keyUsage = nonRepudiation, digitalSignature, keyEncipherment

subjectAltName = @alt_names

[ alt_names ]

DNS.1 = www.example.grpcdev.cn

[ alt_names ] 位置可以配置多个域名,比如:

[ alt_names ]

DNS.1 = www.example.grpcdev.cn

DNS.2 = www.test.grpcdev.cn

为了测试方便,这里只配置一个域名。

1、生成 ca 证书:

openssl genrsa -out ca.key 2048

openssl req -x509 -new -nodes -key ca.key -subj “/CN=example.grpcdev.com” -days 5000 -out ca.pem

在这里插入图片描述

感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的:

① 2000多本Python电子书(主流和经典的书籍应该都有了)

② Python标准库资料(最全中文版)

③ 项目源码(四五十个有趣且经典的练手项目及源码)

④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)

⑤ Python学习路线图(告别不入流的学习)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注python)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ample.grpcdev.cn

[ alt_names ] 位置可以配置多个域名,比如:

[ alt_names ]

DNS.1 = www.example.grpcdev.cn

DNS.2 = www.test.grpcdev.cn

为了测试方便,这里只配置一个域名。

1、生成 ca 证书:

openssl genrsa -out ca.key 2048

openssl req -x509 -new -nodes -key ca.key -subj “/CN=example.grpcdev.com” -days 5000 -out ca.pem

在这里插入图片描述

感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的:

① 2000多本Python电子书(主流和经典的书籍应该都有了)

② Python标准库资料(最全中文版)

③ 项目源码(四五十个有趣且经典的练手项目及源码)

④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)

⑤ Python学习路线图(告别不入流的学习)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注python)
[外链图片转存中…(img-Ea2ofEQ9-1713300890952)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


  1. z ↩︎

  2. z ↩︎

  • 21
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值