| |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| -> Python知识库 -> 如何使用 gRPC 和Mutual TLS 连接 Python 和 Go 应用程序? -> 正文阅读 |
|
|
[Python知识库]如何使用 gRPC 和Mutual TLS 连接 Python 和 Go 应用程序? |
|
创作不易,且读且珍惜。记得点赞、关注、转发的哦~ 本教程通过使用Mutual TLS 身份验证的 gRPC 框架,向你介绍用 Python 和 Go 编写的服务连接的过程。因多数开发者对 Python/Django 和 Go 开发比较熟悉,本文将省略大多数无聊的东西,比如使用 Django 应用程序引导 virtualenv 或者如何“ manage.py runserver”它。
引言有一个Python中的旧系统正在进行大的修改。它是由两部分组成的系统:
在进行一些清理、重构和测试工作之后,客户机基本上满足了它的需求。另一方面,服务器有稳定性和性能问题,所以在Go (Golang)重写服务器是对性能提升很有帮助的解决方案。 而Python和Go之间的通信成了唯一的障碍。 用于客户机和服务器之间通信的现有JSON API是旧的,没有文档记录。正是因为从零开始重建它比试图复兴它更容易。将此API重写为REST/JSON相对容易,但JSON作为交换格式将不能提供Python和Go之间的互换性和类型兼容性。在这两种语言中,类型系统是不同的,要让它工作是很繁琐且容易出错的。 一个更好的解决方案是使用跨平台的序列化格式,比如协议缓冲区(protobuf)。它的构建是为了提供跨平台兼容性,在Python和Go中得到很好的支持,而且它比JSON更小、更快。Protobuf可以与REST API一起使用,以确保编程语言之间的数据互操作性。但是一个更好的解决方案是使用gRPC框架来完全替换旧的API。 gRPC是一个远程过程调用(RPC)框架,它在跨服务通信场景中工作得非常好。它使用协议缓冲区作为接口定义语言(Interface Definition Language, IDL)和消息交换格式。gRPC使用HTTP/2作为传输,并支持传输层安全(TLS)协议,它可以在没有TLS的情况下工作——基本上,这是大多数教程告诉我们的。这样,通信是通过h2c协议完成的,本质上是纯文本HTTP/2,没有TLS加密。然而,当通过公共网络进行通信时,TLS是必需的。考虑到现代安全威胁,TLS甚至应该被考虑用于私有网络连接[1]。 在本系统中,服务到服务的通信不需要区分客户机,也不需要向它们授予不同的权限。尽管如此,确保只有授权的客户机才能与服务器通信是很重要的。使用互TLS (mTLS)作为身份验证机制很容易实现。 通常在TLS中,服务器有证书和公钥/私钥对,而客户端没有。然后,服务器将其证书发送给客户机进行验证。在mTLS中,服务器和客户机都有证书,服务器也验证客户机的证书。只有在这之后,服务器才会授予对客户端[2]的访问权。 让我们创建一个类似的东西——一个简单的Python/Django web服务,它将通过gRPC/mTLS调用Go服务器,并在浏览器中显示结果,并从存储库的结构开始。 代码布局对于这样的项目,使用单个存储库(monorepo)就不需要共享API模式。对于如何组织代码库,每个人都有自己的偏好,只要记住protobuf编译器,protoc,对如何组织代码有自己的想法。 原型文件的位置会影响编译后的代码。它可能需要对编译器标志进行一些试验来生成工作代码。将原型文件放在带代码的主文件夹之外,这样重新组织代码就不会破坏原型编译。 我建议这样的目录结构: tree?-L?1?-d?.? .? ├──?certs? ├──?client? ├──?proto? └──?server?
公钥基础设施要开始使用TLS,您需要客户端和服务器的证书。要创建自签名证书,我建议使用CloudFlare的PKI工具包CFSSL。 首先,您需要创建一个证书颁发机构(CA),该机构将用于为服务器和客户端生成TLS证书。此CA证书还用于在建立TLS连接时验证另一方证书的真实性。 通过JSON文件配置CFSSL,并提供命令来生成默认的配置模板开始: cd?certs? cfssl?print-defaults?config?>?ca-config.json? 默认ca-config。Json提供了足够满足我们需求的配置文件。让我们生成一个CA证书签名请求配置,证书和私钥: cat?>?ca-csr.json?<<EOF?
{?
"CN":?"CA",?
"key":?{?
"algo":?"ecdsa",?
"size":?256?
},?
"names":?[?
{?
"C":?"US",?
"ST":?"CA",?
"L":?"San?Francisco"?
}?
]?
}?
EOF?
?
cfssl?gencert?-initca?ca-csr.json?|?cfssljson?-bare?ca?-?
客户端证书、公钥和私钥: cat?>?client-csr.json?<<EOF?
{?
"CN":?"client",?
"key":?{?
"algo":?"ecdsa",?
"size":?256?
},?
"names":?[?
{?
"C":?"US",?
"ST":?"CA",?
"L":?"San?Francisco"?
}?
]?
}?
EOF?
?
cfssl?gencert?\?
-ca=ca.pem?\?
-ca-key=ca-key.pem?\?
-config=ca-config.json?\?
-profile=client?client-csr.json?|?cfssljson?-bare?client?
服务器的IP地址必须包含在API服务器证书的主题替代名称列表中。这将确保远程客户端可以验证证书。 cat?>?server-csr.json?<<EOF?
{?
"CN":?"server",?
"key":?{?
"algo":?"ecdsa",?
"size":?256?
},?
"names":?[?
{?
"C":?"US",?
"ST":?"CA",?
"L":?"San?Francisco"?
}?
]?
}?
EOF?
?
cfssl?gencert?\?
-ca=ca.pem?\?
-ca-key=ca-key.pem?\?
-config=ca-config.json?\?
-hostname=127.0.0.1?\?
-profile=server?server-csr.json?|?cfssljson?-bare?server?
Protobuf和gRPC现在证书已经准备好了,下一步是为gRPC所需的API创建一个模式定义。它将是一个名为DiceService的简单服务,以演示gRPC和mTLS是如何工作的。 下面是proto/api.proto文件。它定义了一个RPC端点RollDie,该端点接受RollDieRequest并在rolldierresponse的值字段中返回滚模的值。 syntax?=?"proto3";?
?
option?go_package?=?"server/api";?
?
package?api;?
?
message?RollDieRequest?{}?
?
message?RollDieResponse?{?
int32?value?=?1;?
}?
?
service?DiceService?{?
rpc?RollDie?(RollDieRequest)?returns?(RollDieResponse)?{}?
}?
下一步是使用protobuf编译器- protoc从原型定义中为每种语言生成代码。此外,每种语言都需要自己的一组依赖项。 安装所需的包,并构建Python的原型文件: pip?install?grpcio?grpcio-tools? python?-m?grpc_tools.protoc?-I?proto?--proto_path=proto?\? --python_out=client/api?--grpc_python_out=client/api?proto/api.proto? 编译后的文件位于client/api目录中。由于某些原因,protocol的Python编译器在生成的代码中使用了绝对导入[3],它应该是固定的: cd?client/api?&&?cat?api_pb2_grpc.py?|?\? sed?-E?'s/^(import?api_pb2.*)/from?client.api?\1/g'?>?api_pb2_grpc.tmp?&&?\? mv?-f?api_pb2_grpc.tmp?api_pb2_grpc.py? 安装所需的模块和构建原型文件。protoc-gen-go和protoc-gen-go-grpc默认安装在GOBIN目录下。你可以覆盖GOBIN并将其指向virtualenv的bin目录——这使得之后更容易清理。 go?install?google.golang.org/protobuf/cmd/protoc-gen-go@latest? go?install?google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest? protoc?-I.?--go_out=.?--go-grpc_out=.?proto/api.proto? 编译后的文件位于server/api目录中。 Python客户端为了将其余的代码库与Protobuf/gRPC代码隔离开来,创建一个简单的包装器:api/client.py。该包装器需要CA证书、客户端证书和密钥来建立到提供地址的TLS连接。 import?grpc? ? from?.?import?api_pb2,?api_pb2_grpc? ? class?Certs:? root?=?None? cert?=?None? key?=?None? ? def?__init__(self,?root,?cert,?key):? self.root?=?open(root,?'rb').read()? self.cert?=?open(cert,?'rb').read()? self.key?=?open(key,?'rb').read()? ? class?Client:? rpc?=?None? ? def?__init__(self,?addr:?str,?crt:?Certs):? creds?=?grpc.ssl_channel_credentials(crt.root,?crt.key,?crt.cert)? channel?=?grpc.secure_channel(addr,?creds)? self.rpc?=?api_pb2_grpc.DiceServiceStub(channel)? ? def?roll_die(self)?->?int:? return?self.rpc.RollDie(api_pb2.RollDieRequest()).value? 这是如何在web应用程序的视图中使用这个客户端。这里的变量值包含RPC调用的结果。 ICONS?=?["?",?"?",?"?",?"?",?"?",?"?",?"?"]?
?
def?grpc(request):?
grpc_addr?=?"127.0.0.1:8443"?
?
crt?=?api.Certs('certs/ca.pem',?'certs/client.pem',?'certs/client-key.pem')?
try:?
value?=?api.Client(grpc_addr,?crt).roll_die()?
except?Exception?as?e:?
logger.exception(e)?
?
return?HttpResponse('Value:?'?+?ICONS[value])?
现在,如果你试图通过启动web应用程序并点击相应的视图来执行这段代码,你会得到一个错误。这是预期的-服务器还没有创建。这里有趣的部分是错误——它会说一些关于“连接到所有地址失败”的东西,这并不多。但是设置环境变量GRPC_VERBOSITY=debug会使gRPC输出更加详细,并有助于进行故障排除。它可以在client/settings.py文件中完成,例如: if?DEBUG:? os.environ['GRPC_VERBOSITY']?=?'debug'? 服务器端在server/api/server.go中实现DiceService逻辑。它初始化伪随机数生成器,并根据请求返回范围从1到6的随机值。 //?Number?of?dots?on?a?die?
const?Dots?=?6?
?
type?Server?struct?{?
UnimplementedDiceServiceServer?
rnd?*rand.Rand?
}?
?
func?NewServer()?*Server?{?
return?&Server{?
rnd:?rand.New(rand.NewSource(time.Now().UnixNano())),?
}?
}?
?
func?(s?*Server)?RollDie(ctx?context.Context,?req?*RollDieRequest)?(*RollDieResponse,?error)?{?
//?rand.Intn?returns?a?value?in?[0,?Dots)?interval?
value?:=?s.rnd.Intn(Dots)?+?1?
return?&RollDieResponse{Value:?int32(value)},?nil?
}?
服务实现已经准备就绪。下一步是为gRPC服务器提供证书并启动它。你可以把它放在这里服务器/服务器。启用mTLS的一个重要时刻是设置tls。配置{ClientAuth: tls。RequireAndVerifyClientCert},它指示服务器请求并验证客户端的证书。 secureAddress?:=?"127.0.0.1:8443"?
?
serverCert,?err?:=?tls.LoadX509KeyPair("certs/server.pem",?"certs/server-key.pem")?
if?err?!=?nil?{?
log.Printf("failed?to?load?server?cert/key:?%s",?err)?
os.Exit(1)?
}?
?
caCert,?err?:=?ioutil.ReadFile("certs/ca.pem")?
if?err?!=?nil?{?
log.Printf("failed?to?load?CA?cert:?%s",?err)?
os.Exit(1)?
}?
?
caCertPool?:=?x509.NewCertPool()?
caCertPool.AppendCertsFromPEM(caCert)?
creds?:=?credentials.NewTLS(&tls.Config{?
Certificates:?[]tls.Certificate{serverCert},?
ClientCAs:?caCertPool,?
ClientAuth:?tls.RequireAndVerifyClientCert,?
})?
?
secureSrv?:=?grpc.NewServer(grpc.Creds(creds))?
log.Printf("Starting?gRPC?server,?address=%q",?secureAddress)?
lis,?err?:=?net.Listen("tcp",?secureAddress)?
if?err?!=?nil?{?
log.Printf("failed?to?listen:?%s",?err)?
os.Exit(1)?
}?
?
api.RegisterDiceServiceServer(secureSrv,?api.NewServer())?
if?err?:=?secureSrv.Serve(lis);?err?!=?nil?{?
log.Printf("failed?to?serve:?%s",?err)?
os.Exit(1)?
}?
现在使用运行server/server运行服务器。去,确保web应用程序运行和访问它的url -你应该看到RPC请求的结果。gRPC服务器不会记录任何关于传入请求的信息,而且很难通过查看服务器的输出来判断发生了什么。 幸运的是,有一个API可以拦截RPC请求的执行,您可以使用它添加类似于任何HTTP服务器的日志记录。它接近于Django中间件的工作方式。下面是一个简单的日志拦截器。要使用它,您需要将它传递给grpc.NewServer。 func?loggingInterceptor(ctx?context.Context,?req?interface{},?info?*grpc.UnaryServerInfo,?handler?grpc.UnaryHandler)?(interface{},?error)?{?
ts?:=?time.Now()?
?
peer,?ok?:=?peer.FromContext(ctx)?
if?!ok?{?
return?nil,?status.Errorf(codes.InvalidArgument,?"missing?peer")?
}?
md,?ok?:=?metadata.FromIncomingContext(ctx)?
if?!ok?{?
return?nil,?status.Errorf(codes.InvalidArgument,?"missing?metadata")?
}?
?
res,?err?:=?handler(ctx,?req)?
?
log.Printf("server=%q?ip=%q?method=%q?status=%s?duration=%s?user-agent=%q",?
md[":authority"][0],?
peer.Addr.String(),?
info.FullMethod,?
status.FromContextError(err).Code(),?
time.Since(ts),?
md["user-agent"][0],?
)?
return?res,?err?
}?
?
...?
?
secureSrv?:=?grpc.NewServer(grpc.Creds(creds),?grpc.UnaryInterceptor(loggingInterceptor))?
看来目标已经达到了。客户端通过gRPC与服务器通信,由于mTLS,连接是安全的,并且是相互验证的。但请记住,该服务器非常基础,需要进行一些工作和加固,才能在公共网络上的生产环境中使用它。 让我们把服务器放在Nginx后面另一种方法是把Nginx放在服务器之前,我更喜欢它,而不是把Go服务暴露在互联网上。开箱即用,你将获得所有经过战斗测试的功能,如负载平衡和速率限制,它还将减少你需要编写和支持的代码数量。 Nginx从1.13.10版本开始就支持gPRC,并且可以终止、检查和路由gRPC方法调用。所以让我们在服务器之前添加Nginx,让它处理mTLS和通过未加密的HTTP/2的代理请求。这个设置有点复杂,所以这里是图表:
让我们从客户机中的另一个视图开始。它将使用不同的端口号。 def?nginx(request):?
nginx_addr?=?"127.0.0.1:9443"?
?
crt?=?api.Certs('certs/ca.pem',?'certs/client.pem',?'certs/client-key.pem')?
try:?
value?=?api.Client(nginx_addr,?crt).roll_die()?
except?Exception?as?e:?
logger.exception(e)?
?
return?HttpResponse('Value:?'?+?ICONS[value])?
因为Nginx将完成所有围绕TLS的工作,所以没有必要在服务器代码中为gRPC服务器提供证书: insecureAddress?:=?"127.0.0.1:50051"?
insecureSrv?:=?grpc.NewServer(grpc.UnaryInterceptor(loggingInterceptor))?
log.Printf("Starting?gRPC?server?(h2c),?address=%q",?insecureAddress)?
lis,?err?:=?net.Listen("tcp",?insecureAddress)?
if?err?!=?nil?{?
log.Printf("failed?to?listen:?%s",?err)?
os.Exit(1)?
}?
?
api.RegisterDiceServiceServer(insecureSrv,?api.NewServer())?
if?err?:=?insecureSrv.Serve(lis);?err?!=?nil?{?
log.Printf("failed?to?serve:?%s",?err)?
os.Exit(1)?
}?
Nginx的配置文件:Nginx .conf这个配置禁用了妖魔化,并启动了一个记录到stdout的进程。这对于演示目的来说更方便。 NoneBashCSSCC#GoHTMLJavaJavaScriptJSONPHPPowershellPythonRubySQLTypeScriptYAMLCopy events?{?
worker_connections?1024;?
}?
?
#?Do?not?use?it?in?production!?
daemon?off;?
master_process?off;?
?
http?{?
?
upstream?grpcservers?{?
server?127.0.0.1:50051;?
}?
?
server?{?
listen?9443?ssl?http2;?
?
error_log?/dev/stdout;?
access_log?/dev/stdout;?
?
#?Server's?tls?config?
ssl_certificate?certs/server.pem;?
ssl_certificate_key?certs/server-key.pem;?
?
#?mTLS?part?
ssl_client_certificate?certs/ca.pem;?
ssl_verify_client?on;?
?
location?/?{?
grpc_pass?grpc://grpcservers;?
}?
}?
}?
开启Nginx。 nginx?-p?$(pwd)?-c?nginx.conf? 确保所有的服务都启动了,并访问之前创建的视图的URL——您应该看到RPC请求的结果。如果有些东西不能工作-检查这篇文章在GitHub上的代码。 ? ? |
|
|
|
|
| 上一篇文章 下一篇文章 查看所有文章 |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| 360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年11日历 | -2025/11/29 3:23:35- |
|
| 网站联系: qq:121756557 email:121756557@qq.com IT数码 |