Keep Simple RPC。免注册远程过程调用
!!! 注意:已经迭代成第二代。第一代请参考fastapi分支
注意:第二代只有Baisc认证和导入规则列表,其它功能无任何限制,甚至可以执行rm -rf /。所以强烈建议
- 账号只给少量可信之人
- 只部署在docker中
- 服务不用时最好停止,或定时任务启停
- 不要使用默认端口、默认账号、默认URL路径等
- 导入规则列表最后一条建议
"*": False
- 功能比较多:IP黑白名单、函数黑白名单、内网反代、数据缓存、跨语言多数据格式
- 实际考察发现,只有内网反代功能才是大家需要的,其他功能都极少有人用
- 缺点:由于当初没有在数据包中加入请求编号或会话编号,多人都挤同房间数据会混乱
- pip install ksrpc<0.6.0
- 考虑到大家只关注内网反代,所以决定用更专业的反代工具,如:
frp等 - 删减功能,只留
API调用+Baisc认证+导入规则列表 - 某平台中的
FastAPI所创建的服务只要访问,uvicorn就崩溃,最后决定将客服端和服务端都替换成aiohttp async和sync的互转导致系统非常混乱,清理只留async。客户端可以使用to_sync=True以同步方式调用- pip install ksrpc>=0.6.0
pip install ksrpc -i https://mirrors.aliyun.com/pypi/simple --upgrade
# 或
pip install ksrpc -i https://pypi.org/simple --upgrade- 服务端
# 直接运行
python -m ksrpc.run_app
# 使用配置运行
python -m ksrpc.run_app --config ./config.py- 异步客户端(推荐)
import asyncio
from ksrpc.client import RpcClient
from ksrpc.connections.http import HttpConnection # noqa
from ksrpc.connections.websocket import WebSocketConnection # noqa
# 动态URL
URL = 'http://127.0.0.1:8080/api/v1/{time}'
USERNAME = 'admin'
PASSWORD = 'password123'
async def async_main():
async with HttpConnection(URL, username=USERNAME, password=PASSWORD) as conn:
demo = RpcClient('ksrpc.server.demo', conn)
print(await demo.test())
asyncio.run(async_main())- 同步客户端
import nest_asyncio
from ksrpc.client import RpcClient
from ksrpc.connections.http import HttpConnection # noqa
from ksrpc.connections.websocket import WebSocketConnection # noqa
# 动态URL
URL = 'http://127.0.0.1:8080/api/v1/{time}'
USERNAME = 'admin'
PASSWORD = 'password123'
# 必用,否则同步模式只能调用第一次,第二次会报 RuntimeError: Event loop is closed
nest_asyncio.apply()
def sync_main():
with HttpConnection(URL, username=USERNAME, password=PASSWORD) as conn:
demo = RpcClient('ksrpc.server.demo', conn, to_sync=True)
print(demo.test())
conn = WebSocketConnection(URL, username=USERNAME, password=PASSWORD)
demo = RpcClient('ksrpc.server.demo', conn, to_sync=True)
print(demo.test())
print(demo.test())
sync_main()# eager模式
RpcClient(..., lazy=False)
await 一个模块.零到多个模块或方法或属性.一个方法或属性(参数)
# lazy模式
RpcClient(..., lazy=True)
await 一个模块.零到多个模块或方法或属性.一个方法或属性(参数).一个方法或属性(参数).collect()- 语句后接
()就会触发远程调用 .后的函数用来收集调用链[]本质是.__getitem__(item),内部会调整成.__getattr__("__getitem__").__call__(item),会立即触发远程调用- 大部分情况用户代码只在最后出现
()或[],eager模式已经够用
- 语句后接
collect()就会触发远程调用 .后的函数用来收集调用链,()用来收集参数[]本质同eager模式,但触发要等collect()- 只要语句中间出现
()或[],lazy模式才能处理
部分语句还是非常难实现
__getattr__('__str__')()# 一般都存在,就算不存在也会退回到__repr____getattr__('__int__')()# 自定义对象有可能实现,而str没有__int__
所以专门提供了一种特殊语法.func(Self),它能成功运行依赖于3个条件:
from ksrpc.client import Self,然后传入Self参数builtins中已经定义了func函数config.py中IMPORT_RULES已经设置了"builtins": True,或没有设置builtins, 但设置了"*": True
前面两句可以简化成
str(Self)int(Self)
建议先写原始代码测试通过,然后改成魔术方法版测试通过,最后才是远程异步版。例如:
from ksrpc.client import RpcClient, Self
from ksrpc.server import demo
print(len(demo.__file__)) # 1. 在服务端或本地测试是否通过
print(demo.__file__.__len__()) # 2. 翻译成魔术方法版。看是否正常
demo1 = RpcClient('ksrpc.server.demo', conn)
demo2 = RpcClient('ksrpc.server.demo', conn, lazy=True)
print(await demo1.__file__.__len__()) # 3. 改成远程异步版。网络中传输的是`int`
print((await demo1.__file__()).__len__()) # 得到结果一样,但网络中传输的是`str`,然后本地算的`len()`
print(await demo2.__file__.__len__().collect()) # lazy模式,collect_async()前的代码都会在服务端计算
print(await demo2.__file__.len(Self).collect()) # lazy模式下的Self扩展写法
print(demo1.__doc__) # 取的其实是RpcClient的__doc__
print(await demo1.__getattr__('__doc__')()) # 取的远程ksrpc.server.demo.__doc__更多调用方式参考examples
可以查询远程所支持的函数,可以支持IDE自动补全,更多细节参考stubs
- 创建
Web服务,接收请求后,调用服务器中的Python库,将结果二进制封装后返回 - 客户端将
API调用封装,然后向Web服务器请求,等待返回 - 返回结果解包成
Python对象 - 反代时
frp需要公网有服务器进行转发。当然你也可以使用其他组网工具,如easytier等
内网反代参考examples_frp
- 先整体
zlib压缩,然后分chunk传输- 由于整体压缩,所以可以用第三方软件直接解压
- 浏览器和其他工具能直接识别'Content-Encoding': 'deflate'并解压
- 先分
chunk,每个chunk都分别使用zlib压缩再传输- 分块压缩,只能分块解压。第三方工具失效
- 将大文件压缩耗时分拆了,速度显著提升
本项目的HTTP和WebSocket都使用了方案二,先分chunk后zlib压缩
开发到一定阶段后才发现与rpyc这个免注册暴露函数的功能类似,大家也可以去学习一下
https://github.com/tomerfiliba-org/rpyc
此库仅供学习交流,请在数据提供方的授权范围内使用。请勿向第三方转发数据