使用base64解决protobuf数据在http上传输的问题 | 第5期
Posted 青衣极客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用base64解决protobuf数据在http上传输的问题 | 第5期相关的知识,希望对你有一定的参考价值。
有这样一种任务场景:使用protobuf序列化数据,然后通过http协议将这个序列化的数据传输给服务器。这个过程倒是比较简单,只是其中有几个问题如果不搞明白的话,很可能在应用时会遇到麻烦而耽误比较长的时间。google推出了一款grpc倒是可以非常方便地使用protobuf作为通讯接口,只是在个人使用时,总会遇到不愿意使用grpc的情况。比如已经搭建好了一个网站,如果再加个grpc的服务端就需要多启动一个服务。这时,如果数据不太敏感,直接在http协议中作为参数传递给服务端是最简单省事的一种。本文就是在这样的背景下展开。
1. 定义和编译proto文件
首先创建一个proto文件,并定一个message。然后编译生成对应的python文件。
syntax = "proto3";
message Person{
int32 year = 1;
float height = 2; // cm
float weight = 3; // kg
string id = 4;
}
使用如下指令编译proto文件
protoc -I=../../proto --python_out=../../output/ test.proto
当然,要这个指令正常运行需要安装protobuf这款工具。
2. 存储数据并序列化
使用Person这个类型创建数据类型,并进行序列化。一个简易的代码示例如下:
import sys
sys.path.append('../../output')
from test_pb2 import Person
p = Person(year=18, height=170, weight=60, id='hello-01')
print(p)
p_str = p.SerializeToString()
print('p_str = {}, type={}'.format(p_str, type(p_str)))
Output:
year: 18
height: 170.0
weight: 60.0
id: "hello-01"
p_str = b'\x08\x12\x15\x00\x00*C\x1d\x00\x00pB"\x08hello-01', type=<class 'bytes'>
这里需要注意了,使用protobuf序列化得到的字符串是bytes类型的。这里有必要说明一个python中两种字符序列类型:str和bytes。其中bytes是一种字节流,一般从文件中以二进制方式读取的就是这种类型。这种类型数据的优点是效率高,缺点也很明显就是可读性很差。另一个str是一种unicode类型的字符串,可以表示世界上所有的文字和标点,可读性和通用性都很不错,一般我们使用的就是这个类型。原本在网络传输中是应该使用bytes类型的,毕竟可以节省带宽。本文这种背景下的任务是通过http参数完成的,而http参数是以字符串str的形式在请求链接中存在的。因此这就需要将bytes转换成str类型,然后构造到http链接最后的参数上。
那么就有一个很直观的思路,就是直接将bytes字符串转换成str字符串,然后拼接上去,这样虽然效率很低,但是不妨一试。
uni_str = p_str.decode()
print('uni_str= {}, type(uni_str)={}'.format(uni_str, type(uni_str)))
Output:
uni_str=*CpBhello-01, type(uni_str)=<class 'str'>
这个时候严重的问题就出现了:将bytes字符串解码成str字符串之后内容是不一样的,而且其中还很可能包含一些http链接中不允许出现的字符。甚至连这种decode函数执行也不一定会成功。多番尝试发现,这种方式行不通,必须用其他办法。
3. 使用base64生成字符串
使用base64进行编码protobuf序列化产生的bytes字符串。
import base64
b64_str = base64.b64encode(p_str)
print('b64_str={}, type(b64_str)={}'.format(b64_str, type(b64_str)))
Output:
b64_str=b'CBIVAAAqQx0AAHBCIghoZWxsby0wMQ==', type(b64_str)=<class 'bytes'>
发现转换之后的类型还是bytes,所以需要进行一下decode的转化。还有一个问题是这种方式加密生成的字符串还是有可能包含http链接不允许的字符,比如:+/-,空格等等。所幸base64这个工具提供了一种解决这种问题的方式。
import base64
b64_str = base64.urlsafe_b64encode(p_str).decode()
print('b64_str={}, type(b64_str)={}'.format(b64_str, type(b64_str)))
Output:
b64_str=CBIVAAAqQx0AAHBCIghoZWxsby0wMQ==, type(b64_str)=<class 'str'>
这样得到的字符串就完全符合http参数传递的标准,然后通过 http://localhost?param=CBIVAAAqQx0AAHBCIghoZWxsby0wMQ== 就可以把字符串发送给服务器。
4. 服务器端的处理
在服务器端通过解析GET或者POST数据获取客户端传来的字符串,然后进行反序列化。
import base64
b_b64 = base64.urlsafe_b64decode(b64_str)
print('b_b64={}, type(b_b64)={}'.format(b_b64, type(b_b64)))
pp = Person()
pp.ParseFromString(b_b64)
print(pp)
Output:
b_b64=b'\x08\x12\x15\x00\x00*C\x1d\x00\x00pB"\x08hello-01', type(b_b64)=<class 'bytes'>
year: 18
height: 170.0
weight: 60.0
id: "hello-01"
可以看到解析成功,相关数据顺利传送到服务器端。到此就实现了通过http链接把protobuf数据传送到了服务器上。
以上是关于使用base64解决protobuf数据在http上传输的问题 | 第5期的主要内容,如果未能解决你的问题,请参考以下文章