JSON-RPC
所谓的RPC,Remote Procedure Call
的简写,中文译作远程过程调用或者远程服务调用。
直观的理解就是,通过网络请求远程服务器获取指定接口的数据,而不用知晓底层网络协议的细节。
RPC
支持的数据格式很多,比如XML
格式,JSON
格式、ProtoBuf等等。最常用的肯定是json和ProtoBuf。我们接下来项目使用到的数据格式json格式。因为是属于前后端之间的数据交互。而如果是属于服务端之间的不同平台或服务端之间的不同语言之间的交互往往有很多人采用了ProtoBuf,因为ProtoBuf在执行和执行的过程中,都是非常高效的,因为ProtoBuf是属于二进制安全的。
JSON-RPC是一个无状态的、轻量级的远程过程调用(RPC)协议。
协议文档:https://www.jsonrpc.org/specification
译文文档:http://wiki.geekdream.com/Specification/json-rpc_2.0.html
JSON-RPC协议中的客户端一般是为了向远程服务器请求执行某个方法/函数。客户端向实现了JSON-RPC协议的服务端发送请求,多个输入参数能够通过数组或者对象传递到远程方法,这个远程方法也能返回多个输出数据,具体是什么,当然要看具体的方法实现。因为RPC可以通俗理解为:
客户端请求服务端完成某一个服务行为,所以JSON-RPC规范要求: 客户端发送的所有请求都是POST请求!!!
所有的传输数据都是单个对象,用JSON格式进行序列化。
请求要求包含三个特定属性:
jsonrpc: 用来声明JSON-RPC协议的版本,现在基本固定为“2.0”
method,方法,是等待调用的远程方法名,字符串类型
params,参数,对象类型或者是数组,向远程方法传递的多个参数值
id,任意类型值,用于和最后的响应进行匹配,也就是这里设定多少,后面响应里这个值也设定为相同的
响应的接收者必须能够给出所有请求以正确的响应。这个值一般不能为Null,且为数字时不能有小数。
{
"jsonrpc": "2.0",
"method": "模块名.方法名",
"params": {
"键": "值",
},
"id": "UUID格式...."
}
响应也有三个属性:
jsonrpc, 用来声明JSON-RPC协议的版本,现在基本固定为“2.0”
result,结果,是方法的返回值,调用方法出现错误时,必须不包含该成员。
error,错误,当出现错误时,返回一个特定的错误编码,如果没有错误产生,必须不包含该成员。
id,就是请求带的那个id值,必须与请求对象中的id成员的值相同。请求对象中的id时发生错误(如:转换错误或无效的请求),它必须为Null
# 操作成功
{
"jsonrpc": "2.0",
"reslt": {
data: {
}
}
"id": "UUID格式(来自请求中的ID,原样返回)...."
}
# 操作失败
{
"jsonrpc": "2.0",
"error": {
detail: {
}
}
"id": "UUID格式(来自请求中的ID,原样返回)...."
}
当然,有一些场景下,是不用返回值的,比如只对客户端进行通知,由于不用对请求的id进行匹配,所以这个id就是不必要的,置空或者直接不要了。
在flask中要实现提供json-rpc接口,开发中一般使用Flask JSON-RPC模块来实现。
git地址:https://github.com/cenobites/flask-jsonrpc
安装Flask-JSONRPC模块
pip install Flask-JSONRPC
Protobuf
基本介绍
Protobuf(Google Protocol Buffers)是google开发的的一套用于数据存储,网络通信时用于协议编解码的工具库.它和XML和Json数据差不多,把数据以某种格式组织并保存起来,Protobuf相对与XML和Json的不同之处,它是一种二进制的数据格式,具有更高的传输,打包和解包效率。Protobuf 提供了C++、java、python、nodejs、php、go语言的支持,提供了windows(proto.exe)和linux平台动态编译生成proto文件对应的源文件。proto文件定义了协议数据中的实体结构(message ,field)。因为有google背书,所以在各个语言中被迅速普及,常见的应用场景就是微服务架构和DDD架构!
protoBuf下载地址:https://github.com/protocolbuffers/protobuf/releases
flask中基于protobuf实现api接口
环境搭建
# 安装格式转换工具,不同的操作系统,转换工具不同。windows->proto.exe,Linux->protobuf-compiler
sudo apt install -y protobuf-compiler
[bubu@localhost ~]$protoc --version
libprotoc 3.20.1
pip install flask
# python下面用于对protobuf进行编码解码的模块
pip install protobuf==3.20.0
# 网络请求工具库
pip install requests
接口代码
编写Protobuf数据数据,UserInfo.proto
,代码:
syntax = "proto3";
// 请求体
message Request {
string data = 1;
int32 page = 2;
int32 pageSize = 3;
}
// 响应体
message Response {
int32 code = 1;
string message = 3;
repeated data dataList = 2;
message data {
string username = 1;
string email = 2;
int32 age = 3;
}
}
编译转换成python格式
protoc --python_out=./ UserInfo.proto
服务端接口,server.py
,代码:
from UserInfo_pb2 import Request, Response
from flask import Flask, request
app=Flask(__name__)
# RPC接口规范,强调要使用post方法!!!
@app.route("/api/user", methods=["POST"])
def user_info():
# 解析请求
request_data = Request()
print(request.get_data()) # 原生请求body
request_data.ParseFromString(request.get_data())
# 打印客户端的请求体
print("data",request_data.data) # data user
print("page",request_data.page) # page 1
print("pageSize",request_data.pageSize) # pageSize 10
# 编写响应信息
response = Response()
response.code = 200
response.message = "Success"
data_list = [
{"username":"小明", "age": 18, "email":"xiaoming@mofang.com"},
{"username":"小白", "age": 18, "email":"xiaobai@mofang.com"},
]
for data in data_list:
item = response.data()
item.username = data["username"]
item.age = data["age"]
item.email = data["email"]
response.dataList.append(item)
return response.SerializeToString(), 200
if __name__ == '__main__':
app.run("127.0.0.1", port = 6666)
客户端,client.py
,代码:
import requests
from UserInfo_pb2 import Request, Response
def test_protobuf_api():
"""
测试 接口
:return:
"""
request_data = Request()
# 编写请求信息
request_data.data = "user"
request_data.page = 1
request_data.pageSize = 10
req_data = request_data.SerializeToString() # 序列化
response = requests.post("http://127.0.0.1:6666/api/user", data=req_data)
# 获取响应内容
res = Response()
# response.content 原生响应体内容
res.ParseFromString(response.content) # 反序列化
print(res.code) # 200
print(res.message) # Success
for data in res.dataList:
print(f"用户名: {data.username}, 年龄:{data.age}, 邮箱: {data.email}")
if __name__ == '__main__':
test_protobuf_api()
先有运行服务端代码,再运行客户端代码,即可查看效果。