xuri-rpc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xuri_rpc-0.1.0/LICENSE +21 -0
- xuri_rpc-0.1.0/PKG-INFO +428 -0
- xuri_rpc-0.1.0/README.md +410 -0
- xuri_rpc-0.1.0/pyproject.toml +22 -0
- xuri_rpc-0.1.0/xuri_rpc/__init__.py +2 -0
- xuri_rpc-0.1.0/xuri_rpc/core.py +511 -0
xuri_rpc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 prometheus-0017
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
xuri_rpc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: xuri-rpc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: xuri-rpc in python
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: prometheus-0017
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# xuri-rpc-python
|
|
19
|
+
xuri-rpc是一款在表面上支持了传递对象和回调的RPC框架。当然,实际上。并没有对象发生迁移,实际的计算还发生在它本来的位置。
|
|
20
|
+
|
|
21
|
+
目前只支持传递对象上的方法,暂不支持传递属性。
|
|
22
|
+
|
|
23
|
+
支持JavaScript和Python两种环境。
|
|
24
|
+
|
|
25
|
+
## 特点
|
|
26
|
+
|
|
27
|
+
* 像使用一个本地对象一样去使用一个远程对象。并且不局限于需要事先声明的对象。
|
|
28
|
+
* 因为对象首先会带着他的信息返回到本地来,你才会接着调用。这就在很大程度上避免了你调一个HTTP的请求,然后给你报404,你却找不着到底是哪个地方没对上的一种无力感。如果这一次再找不着你至少可以看到你还有什么可以选的。
|
|
29
|
+
|
|
30
|
+
* 不限制底层通信方式,你可以使用websocket,TCP,进程通信,合并到你现有的服务中,甚至基于轮询的http。
|
|
31
|
+
|
|
32
|
+
对于专用一个websocket的情况,我们实现了基于websocket的client
|
|
33
|
+
|
|
34
|
+
其它情况你可能需要:1,维护一个连接,2,实现一个用于client的sender,连接的维护和本框架无关,除了你需要将连接上收到的信息转发给本框架,而对于sender,你不会做太多事情
|
|
35
|
+
|
|
36
|
+
## 使用场景
|
|
37
|
+
|
|
38
|
+
* 浏览器workers之间通信
|
|
39
|
+
|
|
40
|
+
* iframes之间通信
|
|
41
|
+
|
|
42
|
+
* 浏览器前端和后端通信
|
|
43
|
+
|
|
44
|
+
## 安装
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
pip install xuri-rpc
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 示例
|
|
51
|
+
|
|
52
|
+
示例中使用了web socket作为信息载体。这个部分自己装这里不再赘述。
|
|
53
|
+
|
|
54
|
+
### 使用RPC框架进行一个远程的过程执行并触发一次回调。
|
|
55
|
+
|
|
56
|
+
服务端
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
import asyncio
|
|
60
|
+
import json
|
|
61
|
+
import websockets
|
|
62
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
63
|
+
from xuri_rpc import setDebugFlag
|
|
64
|
+
setDebugFlag(True)
|
|
65
|
+
# 设置hostName
|
|
66
|
+
setHostId('backend')
|
|
67
|
+
|
|
68
|
+
# 创建一个Sender
|
|
69
|
+
class Sender:
|
|
70
|
+
def __init__(self, ws):
|
|
71
|
+
self.ws = ws
|
|
72
|
+
|
|
73
|
+
async def send(self, message):
|
|
74
|
+
await self.ws.send(json.dumps(message))
|
|
75
|
+
|
|
76
|
+
# 设置用于提供起始方法的main对象
|
|
77
|
+
from xuri_rpc import dict2obj
|
|
78
|
+
async def plus(a,b,callback):
|
|
79
|
+
await callback(a + b)
|
|
80
|
+
return a+b
|
|
81
|
+
getMessageReceiver().setMain(dict2obj({
|
|
82
|
+
'plus': plus
|
|
83
|
+
}))
|
|
84
|
+
|
|
85
|
+
async def handle_connection(ws, path):
|
|
86
|
+
# 创建一个client用于发送返回信息
|
|
87
|
+
client = Client()
|
|
88
|
+
client.setSender(Sender(ws))
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
async for data in ws:
|
|
92
|
+
# 处理接收到的信息
|
|
93
|
+
message = json.loads(data)
|
|
94
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(message, client))
|
|
95
|
+
except Exception as error:
|
|
96
|
+
print('客户端连接错误:', error)
|
|
97
|
+
|
|
98
|
+
start_server = websockets.serve(handle_connection, "", 18081)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
asyncio.get_event_loop().run_until_complete(start_server)
|
|
102
|
+
asyncio.get_event_loop().run_forever()
|
|
103
|
+
except Exception as error:
|
|
104
|
+
print('服务器错误:', error)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
客户端
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
import asyncio
|
|
111
|
+
import json
|
|
112
|
+
import websockets
|
|
113
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
114
|
+
setHostId('backend')
|
|
115
|
+
from xuri_rpc import setDebugFlag
|
|
116
|
+
setDebugFlag(True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# define a sender
|
|
120
|
+
class Sender:
|
|
121
|
+
def __init__(self, ws):
|
|
122
|
+
self.ws = ws
|
|
123
|
+
|
|
124
|
+
async def send(self, message):
|
|
125
|
+
# message is an object can be jsonified
|
|
126
|
+
await self.ws.send(json.dumps(message))
|
|
127
|
+
|
|
128
|
+
async def main():
|
|
129
|
+
setHostId('frontend')
|
|
130
|
+
client = Client()
|
|
131
|
+
|
|
132
|
+
ws = await websockets.connect('ws:#localhost:18081')
|
|
133
|
+
|
|
134
|
+
async def on_message(data):
|
|
135
|
+
await getMessageReceiver().onReceiveMessage(json.loads(data), client)
|
|
136
|
+
print(f'收到服务器消息: {data}')
|
|
137
|
+
|
|
138
|
+
# Run message reception in the background
|
|
139
|
+
async def listen():
|
|
140
|
+
async for message in ws:
|
|
141
|
+
asyncio.ensure_future(on_message(message))
|
|
142
|
+
|
|
143
|
+
# Start listening in the background
|
|
144
|
+
asyncio.ensure_future(listen())
|
|
145
|
+
|
|
146
|
+
client.setSender(Sender(ws))
|
|
147
|
+
|
|
148
|
+
main_proxy = await client.getMain()
|
|
149
|
+
def callback(result):
|
|
150
|
+
# breakpoint()
|
|
151
|
+
print('from callback', result)
|
|
152
|
+
result = await main_proxy.plus(1, 2, asProxy(callback))
|
|
153
|
+
print('from rpc', result)
|
|
154
|
+
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
使用多组 RPC。
|
|
161
|
+
|
|
162
|
+
### 在调用的时候传递一个上下文变量
|
|
163
|
+
|
|
164
|
+
首先你定义的对象接收的第一个参数应当是一个表示上下文的字典。
|
|
165
|
+
|
|
166
|
+
服务端
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
170
|
+
from xuri_rpc import dict2obj
|
|
171
|
+
import websockets
|
|
172
|
+
import asyncio
|
|
173
|
+
import json
|
|
174
|
+
|
|
175
|
+
# 设置hostName
|
|
176
|
+
setHostId('backend')
|
|
177
|
+
|
|
178
|
+
# 创建一个Sender
|
|
179
|
+
class Sender:
|
|
180
|
+
def __init__(self, ws):
|
|
181
|
+
self.ws = ws
|
|
182
|
+
|
|
183
|
+
async def send(self, message):
|
|
184
|
+
await self.ws.send(json.dumps(message))
|
|
185
|
+
|
|
186
|
+
# 设置用于提供起始方法的main对象
|
|
187
|
+
getMessageReceiver().setMain(dict2obj({
|
|
188
|
+
}))
|
|
189
|
+
from xuri_rpc import dict2obj
|
|
190
|
+
getMessageReceiver().setObject("greeting",dict2obj( {
|
|
191
|
+
"greeting": lambda context: f"hi,{context['a']} and {context['b']}"
|
|
192
|
+
}), True)
|
|
193
|
+
async def a(context,message,client,next):
|
|
194
|
+
context['a']='mike'
|
|
195
|
+
await next()
|
|
196
|
+
async def b(context,message,client,next):
|
|
197
|
+
context['b']='john'
|
|
198
|
+
await next()
|
|
199
|
+
|
|
200
|
+
getMessageReceiver().addInterceptor(a)
|
|
201
|
+
getMessageReceiver().addInterceptor(b)
|
|
202
|
+
|
|
203
|
+
async def handle_connection(websocket, path):
|
|
204
|
+
# 创建一个client用于发送返回信息
|
|
205
|
+
client = Client()
|
|
206
|
+
client.setSender(Sender(websocket))
|
|
207
|
+
|
|
208
|
+
async for data in websocket:
|
|
209
|
+
try:
|
|
210
|
+
# 处理接收到的信息
|
|
211
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(json.loads(data), client))
|
|
212
|
+
except Exception as e:
|
|
213
|
+
print('客户端连接错误:', e)
|
|
214
|
+
|
|
215
|
+
async def main():
|
|
216
|
+
server = await websockets.serve(handle_connection, "localhost", 18081)
|
|
217
|
+
await server.wait_closed()
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
asyncio.run(main())
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
客户端
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
import asyncio
|
|
227
|
+
import json
|
|
228
|
+
import websockets
|
|
229
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
230
|
+
|
|
231
|
+
# define a sender
|
|
232
|
+
class Sender:
|
|
233
|
+
def __init__(self, ws):
|
|
234
|
+
self.ws = ws
|
|
235
|
+
|
|
236
|
+
async def send(self, message):
|
|
237
|
+
# message is an object can be jsonified
|
|
238
|
+
await self.ws.send(json.dumps(message))
|
|
239
|
+
|
|
240
|
+
async def main():
|
|
241
|
+
setHostId('frontend')
|
|
242
|
+
client = Client()
|
|
243
|
+
|
|
244
|
+
ws = await websockets.connect('ws:#localhost:18081')
|
|
245
|
+
|
|
246
|
+
# Listen for messages
|
|
247
|
+
async def listen():
|
|
248
|
+
async for data in ws:
|
|
249
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(json.loads(data), client))
|
|
250
|
+
print(f'收到服务器消息: {data}')
|
|
251
|
+
|
|
252
|
+
# Run listener and proceed
|
|
253
|
+
asyncio.create_task(listen())
|
|
254
|
+
|
|
255
|
+
client.setSender(Sender(ws))
|
|
256
|
+
|
|
257
|
+
main_obj = await client.getObject('greeting')
|
|
258
|
+
result = await main_obj.greeting()
|
|
259
|
+
print(result)
|
|
260
|
+
|
|
261
|
+
asyncio.run(main())
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## 教程
|
|
265
|
+
|
|
266
|
+
### 基本的信息发送流程
|
|
267
|
+
|
|
268
|
+
一次RPC调用应当包括以下过程:
|
|
269
|
+
|
|
270
|
+
- 一个从某个client当中获得的远程对象的一个方法被调用。
|
|
271
|
+
- 这个远程对象的代理调用client封装一系列的方法最后组成一条请求(Response)信息(Message)
|
|
272
|
+
- Client调用分配给其的ISender。发送消息。此远程方法通过异步的方式阻塞在这里。
|
|
273
|
+
- 接收端的receiver收到信息以后,委派给对应的对象进行处理。在receiver接受一个消息的时候,应当同步传递一个用于应答的client。
|
|
274
|
+
- 当委派对象处理完时返回结果。
|
|
275
|
+
- 返回结果通过应答client返回给请求侧。
|
|
276
|
+
- 请求侧的receiver收到消息后。此请求的promise被设置为resolve或reject完成本轮请求。
|
|
277
|
+
|
|
278
|
+
### 经典的使用流程
|
|
279
|
+
|
|
280
|
+
完整代码参见示例。
|
|
281
|
+
|
|
282
|
+
服务端
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
# 设置host Id
|
|
287
|
+
setHostId('backend')
|
|
288
|
+
|
|
289
|
+
#setMain&setObject接受一个对象作为参数而非字典。注意不要弄错了。
|
|
290
|
+
from xuri_rpc import dict2obj
|
|
291
|
+
|
|
292
|
+
#设置用于提供起始方法的main对象,在这个面对象里,你应当添加一些方法返回更多的远程对象。或者你也可以直接就在这个main方法里实现一些业务逻辑的调用。
|
|
293
|
+
async def plus(a,b,callback):
|
|
294
|
+
await callback(a + b)
|
|
295
|
+
return a+b
|
|
296
|
+
getMessageReceiver().setMain(dict2obj({
|
|
297
|
+
'plus': plus
|
|
298
|
+
}))
|
|
299
|
+
|
|
300
|
+
#你创建了某种方式的消息通道从中获得信息反序列化之后传递给 MessageReceiver,当你把收到的消息传递给MessageReceiver的时候,你还需要同时传递一个client,毕竟这次调用的返回结果你得找个东西发回去。
|
|
301
|
+
async def handle_connection(ws, path):
|
|
302
|
+
# 创建一个client用于发送返回信息
|
|
303
|
+
client = Client()
|
|
304
|
+
client.setSender(Sender(ws))
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
async for data in ws:
|
|
308
|
+
# 处理接收到的信息
|
|
309
|
+
message = json.loads(data)
|
|
310
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(message, client))
|
|
311
|
+
except Exception as error:
|
|
312
|
+
print('客户端连接错误:', error)
|
|
313
|
+
|
|
314
|
+
start_server = websockets.serve(handle_connection, "", 18081)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
客户端
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#定义一个发送装置。把一个消息对象序列化,并通过某一种底层的传输机制发送出去,比如说一个进程管道,或者是一个web socket连接。
|
|
322
|
+
class Sender:
|
|
323
|
+
def __init__(self, ws):
|
|
324
|
+
self.ws = ws
|
|
325
|
+
|
|
326
|
+
async def send(self, message):
|
|
327
|
+
# message is an object can be jsonified
|
|
328
|
+
await self.ws.send(json.dumps(message))
|
|
329
|
+
|
|
330
|
+
#设置host ID。
|
|
331
|
+
setHostId('frontend')
|
|
332
|
+
#创建一个client,这个client是完成了RPC调用的一些复杂操作的对象。
|
|
333
|
+
client = Client()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
#创建一个管道。这个管道。既被用于发送信息,也被用于接收返回结果。
|
|
337
|
+
ws = await websockets.connect('ws:#localhost:18081')
|
|
338
|
+
|
|
339
|
+
async def on_message(data):
|
|
340
|
+
#创建一个receiver,你发出去的东西总得找个地方接返回结果,是吧?
|
|
341
|
+
await getMessageReceiver().onReceiveMessage(json.loads(data), client)
|
|
342
|
+
print(f'收到服务器消息: {data}')
|
|
343
|
+
|
|
344
|
+
# Run message reception in the background
|
|
345
|
+
async def listen():
|
|
346
|
+
async for message in ws:
|
|
347
|
+
asyncio.ensure_future(on_message(message))
|
|
348
|
+
|
|
349
|
+
# Start listening in the background
|
|
350
|
+
asyncio.ensure_future(listen())
|
|
351
|
+
|
|
352
|
+
#给client绑定对应的sender
|
|
353
|
+
client.setSender(Sender(ws))
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
#获得main对象
|
|
358
|
+
main_proxy = await client.getMain()
|
|
359
|
+
#main对象是服务侧定义的一个远程对象,你应当从这里获得到你定义的函数。调用这些函数,获得更进一步的远程对象或者执行一些业务逻辑。
|
|
360
|
+
def callback(result):
|
|
361
|
+
# breakpoint()
|
|
362
|
+
print('from callback', result)
|
|
363
|
+
result = await main_proxy.plus(1, 2, asProxy(callback))
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
### host
|
|
370
|
+
|
|
371
|
+
这个东西相当于一个逻辑上的主机概念。正常情况下,它应当是对应于你这个程序的。但是如果你的程序里可能需要很多种rpc连接,那么每一种连接应当对应于一个这样的逻辑主机一个host。
|
|
372
|
+
|
|
373
|
+
你应当给你的主机起一个名字这个名字在你的一整套。 分布式。系统里应当是唯一的。 setHostId接受一个字符串作为参数指定默认的主机的名称。通常情况下,你应当且仅应当调用一次这个方法。
|
|
374
|
+
|
|
375
|
+
对于有多个rpc连接的情况,也就是你需要设置多个host的情况。你可以在client和receiver的构造函数中传入一个字符串参数作为这个client或者receiver的所属host。
|
|
376
|
+
|
|
377
|
+
### asProxy,setArgsAutoWrapper
|
|
378
|
+
|
|
379
|
+
一个远程对象接受的参数对象可以分为data类型和proxy类型。
|
|
380
|
+
|
|
381
|
+
Data类型的对象就是一个完全表示数据的对象,例如一个字符串或一个字典或者其它嵌套,复合结构,它可以被以一种确定的方式序列化。在系统实际运行的过程当中。
|
|
382
|
+
|
|
383
|
+
Proxy类型对象将会被复制到远程端被远程端处理。推荐这个对象是不可变的对象。而proxy类型的对象。应当是在系统中主要负责承载计算的承载系统逻辑的部分结构。通常这类对象具有广泛的关联,不适合也不能被序列化。在系统执行的过程当中,此对象将会产生一个代理对象发送到远程上。远程主机调用代理对象来控制对象本体执行具体函数。
|
|
384
|
+
|
|
385
|
+
虽然我们提供了一种调用远程对象的方法,但是,原则上我们不建议频繁的创建和使用远程对象,因为不可能把远程对象和本地对象当成一种东西来用。首先我们并没有提供一个健全的卸载远程对象的机制,即没有一种自动的垃圾回收系统。因此,这可能会导致某种意义上的内存泄露。其次是受通信延迟的影响,调用远程方法可能会降低程序效率。
|
|
386
|
+
|
|
387
|
+
我们提供了一个asProxy函数。显式地声明一个传递给远程方法的参数是proxy类型。在实现上,它返回了一个proxy类型对象的表示对象,这是一个PreArgObj的实例。
|
|
388
|
+
|
|
389
|
+
我们还在client上提供了一个setArgsAutoWrapper函数。如果在你的系统中,你能够确定某一种模式的参数必然是一个proxy类型的参数,那么你可以通过给这个函数传递一个函数作为参数。来实现一种自动的转换。注意,应当放过as proxy返回的结果。
|
|
390
|
+
|
|
391
|
+
### context,interceptor & setObject
|
|
392
|
+
|
|
393
|
+
我们可能需要面临这样的一个情况:对于所有的请求,在请求被实际处理之前,我们可能需要进行一些准备,例如创建一个数据库会话。
|
|
394
|
+
|
|
395
|
+
我们将这种机制称之为上下文。上下文应当能够在请求处理的全过程当中的任意一个地方都可以被引用。
|
|
396
|
+
|
|
397
|
+
但是这个机制在异步情况下实现起来比较困难,尤其是浏览器端目前不支持在asynchronized的环境下的一个持续的全局的字典。
|
|
398
|
+
|
|
399
|
+
作为一个替代方法。我们增加了一种机制。在这个机制中。负责处理请求的函数接受到的第一个参数为一个表示上下文的字典。在构建服务器的时候。通过调用addInterceptor 方法。增加请求前后的处理机制。并在其中await的调用next处理后续。使用这个方法的远程端只需要正常的传递参数。但是在服务端处理这个请求的时候会增加第一个参数为context。
|
|
400
|
+
|
|
401
|
+
具体流程为:
|
|
402
|
+
|
|
403
|
+
服务端
|
|
404
|
+
|
|
405
|
+
```
|
|
406
|
+
#在服务端的message server上设置一个Object。注意这个object上的每一个方法的第一个参数都是一个context。且set object的第三个参数为true,表示。这是一个启用了context的机制的对象。。
|
|
407
|
+
getMessageReceiver().setObject("greeting",dict2obj( {
|
|
408
|
+
"greeting": lambda context: f"hi,{context['a']} and {context['b']}"
|
|
409
|
+
}), True)
|
|
410
|
+
#添加拦截器。每个拦截器是如下声明的一个方法。参数依次是上下文。当前请求的message。返回的client。调用下一层。拦截器或者是函数本体的next。
|
|
411
|
+
async def b(context,message,client,next):
|
|
412
|
+
context['b']='john'# Context是一个字典,你可以在这里添加上任何你想要做的事情。
|
|
413
|
+
await next()#调用下一层
|
|
414
|
+
|
|
415
|
+
getMessageReceiver().addInterceptor(b)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
客户端
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
#通过 getObject 的获得具有上下文功能的对象。
|
|
422
|
+
main_obj = await client.getObject('greeting')
|
|
423
|
+
#调用函数的时候,不需要传递上下文这个参数。
|
|
424
|
+
result = await main_obj.greeting()
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
完整代码见示例
|
|
428
|
+
|
xuri_rpc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# xuri-rpc-python
|
|
2
|
+
xuri-rpc是一款在表面上支持了传递对象和回调的RPC框架。当然,实际上。并没有对象发生迁移,实际的计算还发生在它本来的位置。
|
|
3
|
+
|
|
4
|
+
目前只支持传递对象上的方法,暂不支持传递属性。
|
|
5
|
+
|
|
6
|
+
支持JavaScript和Python两种环境。
|
|
7
|
+
|
|
8
|
+
## 特点
|
|
9
|
+
|
|
10
|
+
* 像使用一个本地对象一样去使用一个远程对象。并且不局限于需要事先声明的对象。
|
|
11
|
+
* 因为对象首先会带着他的信息返回到本地来,你才会接着调用。这就在很大程度上避免了你调一个HTTP的请求,然后给你报404,你却找不着到底是哪个地方没对上的一种无力感。如果这一次再找不着你至少可以看到你还有什么可以选的。
|
|
12
|
+
|
|
13
|
+
* 不限制底层通信方式,你可以使用websocket,TCP,进程通信,合并到你现有的服务中,甚至基于轮询的http。
|
|
14
|
+
|
|
15
|
+
对于专用一个websocket的情况,我们实现了基于websocket的client
|
|
16
|
+
|
|
17
|
+
其它情况你可能需要:1,维护一个连接,2,实现一个用于client的sender,连接的维护和本框架无关,除了你需要将连接上收到的信息转发给本框架,而对于sender,你不会做太多事情
|
|
18
|
+
|
|
19
|
+
## 使用场景
|
|
20
|
+
|
|
21
|
+
* 浏览器workers之间通信
|
|
22
|
+
|
|
23
|
+
* iframes之间通信
|
|
24
|
+
|
|
25
|
+
* 浏览器前端和后端通信
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
pip install xuri-rpc
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 示例
|
|
34
|
+
|
|
35
|
+
示例中使用了web socket作为信息载体。这个部分自己装这里不再赘述。
|
|
36
|
+
|
|
37
|
+
### 使用RPC框架进行一个远程的过程执行并触发一次回调。
|
|
38
|
+
|
|
39
|
+
服务端
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
import asyncio
|
|
43
|
+
import json
|
|
44
|
+
import websockets
|
|
45
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
46
|
+
from xuri_rpc import setDebugFlag
|
|
47
|
+
setDebugFlag(True)
|
|
48
|
+
# 设置hostName
|
|
49
|
+
setHostId('backend')
|
|
50
|
+
|
|
51
|
+
# 创建一个Sender
|
|
52
|
+
class Sender:
|
|
53
|
+
def __init__(self, ws):
|
|
54
|
+
self.ws = ws
|
|
55
|
+
|
|
56
|
+
async def send(self, message):
|
|
57
|
+
await self.ws.send(json.dumps(message))
|
|
58
|
+
|
|
59
|
+
# 设置用于提供起始方法的main对象
|
|
60
|
+
from xuri_rpc import dict2obj
|
|
61
|
+
async def plus(a,b,callback):
|
|
62
|
+
await callback(a + b)
|
|
63
|
+
return a+b
|
|
64
|
+
getMessageReceiver().setMain(dict2obj({
|
|
65
|
+
'plus': plus
|
|
66
|
+
}))
|
|
67
|
+
|
|
68
|
+
async def handle_connection(ws, path):
|
|
69
|
+
# 创建一个client用于发送返回信息
|
|
70
|
+
client = Client()
|
|
71
|
+
client.setSender(Sender(ws))
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
async for data in ws:
|
|
75
|
+
# 处理接收到的信息
|
|
76
|
+
message = json.loads(data)
|
|
77
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(message, client))
|
|
78
|
+
except Exception as error:
|
|
79
|
+
print('客户端连接错误:', error)
|
|
80
|
+
|
|
81
|
+
start_server = websockets.serve(handle_connection, "", 18081)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
asyncio.get_event_loop().run_until_complete(start_server)
|
|
85
|
+
asyncio.get_event_loop().run_forever()
|
|
86
|
+
except Exception as error:
|
|
87
|
+
print('服务器错误:', error)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
客户端
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
import asyncio
|
|
94
|
+
import json
|
|
95
|
+
import websockets
|
|
96
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
97
|
+
setHostId('backend')
|
|
98
|
+
from xuri_rpc import setDebugFlag
|
|
99
|
+
setDebugFlag(True)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# define a sender
|
|
103
|
+
class Sender:
|
|
104
|
+
def __init__(self, ws):
|
|
105
|
+
self.ws = ws
|
|
106
|
+
|
|
107
|
+
async def send(self, message):
|
|
108
|
+
# message is an object can be jsonified
|
|
109
|
+
await self.ws.send(json.dumps(message))
|
|
110
|
+
|
|
111
|
+
async def main():
|
|
112
|
+
setHostId('frontend')
|
|
113
|
+
client = Client()
|
|
114
|
+
|
|
115
|
+
ws = await websockets.connect('ws:#localhost:18081')
|
|
116
|
+
|
|
117
|
+
async def on_message(data):
|
|
118
|
+
await getMessageReceiver().onReceiveMessage(json.loads(data), client)
|
|
119
|
+
print(f'收到服务器消息: {data}')
|
|
120
|
+
|
|
121
|
+
# Run message reception in the background
|
|
122
|
+
async def listen():
|
|
123
|
+
async for message in ws:
|
|
124
|
+
asyncio.ensure_future(on_message(message))
|
|
125
|
+
|
|
126
|
+
# Start listening in the background
|
|
127
|
+
asyncio.ensure_future(listen())
|
|
128
|
+
|
|
129
|
+
client.setSender(Sender(ws))
|
|
130
|
+
|
|
131
|
+
main_proxy = await client.getMain()
|
|
132
|
+
def callback(result):
|
|
133
|
+
# breakpoint()
|
|
134
|
+
print('from callback', result)
|
|
135
|
+
result = await main_proxy.plus(1, 2, asProxy(callback))
|
|
136
|
+
print('from rpc', result)
|
|
137
|
+
|
|
138
|
+
asyncio.run(main())
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
使用多组 RPC。
|
|
144
|
+
|
|
145
|
+
### 在调用的时候传递一个上下文变量
|
|
146
|
+
|
|
147
|
+
首先你定义的对象接收的第一个参数应当是一个表示上下文的字典。
|
|
148
|
+
|
|
149
|
+
服务端
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
153
|
+
from xuri_rpc import dict2obj
|
|
154
|
+
import websockets
|
|
155
|
+
import asyncio
|
|
156
|
+
import json
|
|
157
|
+
|
|
158
|
+
# 设置hostName
|
|
159
|
+
setHostId('backend')
|
|
160
|
+
|
|
161
|
+
# 创建一个Sender
|
|
162
|
+
class Sender:
|
|
163
|
+
def __init__(self, ws):
|
|
164
|
+
self.ws = ws
|
|
165
|
+
|
|
166
|
+
async def send(self, message):
|
|
167
|
+
await self.ws.send(json.dumps(message))
|
|
168
|
+
|
|
169
|
+
# 设置用于提供起始方法的main对象
|
|
170
|
+
getMessageReceiver().setMain(dict2obj({
|
|
171
|
+
}))
|
|
172
|
+
from xuri_rpc import dict2obj
|
|
173
|
+
getMessageReceiver().setObject("greeting",dict2obj( {
|
|
174
|
+
"greeting": lambda context: f"hi,{context['a']} and {context['b']}"
|
|
175
|
+
}), True)
|
|
176
|
+
async def a(context,message,client,next):
|
|
177
|
+
context['a']='mike'
|
|
178
|
+
await next()
|
|
179
|
+
async def b(context,message,client,next):
|
|
180
|
+
context['b']='john'
|
|
181
|
+
await next()
|
|
182
|
+
|
|
183
|
+
getMessageReceiver().addInterceptor(a)
|
|
184
|
+
getMessageReceiver().addInterceptor(b)
|
|
185
|
+
|
|
186
|
+
async def handle_connection(websocket, path):
|
|
187
|
+
# 创建一个client用于发送返回信息
|
|
188
|
+
client = Client()
|
|
189
|
+
client.setSender(Sender(websocket))
|
|
190
|
+
|
|
191
|
+
async for data in websocket:
|
|
192
|
+
try:
|
|
193
|
+
# 处理接收到的信息
|
|
194
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(json.loads(data), client))
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print('客户端连接错误:', e)
|
|
197
|
+
|
|
198
|
+
async def main():
|
|
199
|
+
server = await websockets.serve(handle_connection, "localhost", 18081)
|
|
200
|
+
await server.wait_closed()
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
asyncio.run(main())
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
客户端
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
import asyncio
|
|
210
|
+
import json
|
|
211
|
+
import websockets
|
|
212
|
+
from xuri_rpc import PlainProxyManager, RunnableProxyManager, MessageReceiver, Client, asProxy, getMessageReceiver, setHostId
|
|
213
|
+
|
|
214
|
+
# define a sender
|
|
215
|
+
class Sender:
|
|
216
|
+
def __init__(self, ws):
|
|
217
|
+
self.ws = ws
|
|
218
|
+
|
|
219
|
+
async def send(self, message):
|
|
220
|
+
# message is an object can be jsonified
|
|
221
|
+
await self.ws.send(json.dumps(message))
|
|
222
|
+
|
|
223
|
+
async def main():
|
|
224
|
+
setHostId('frontend')
|
|
225
|
+
client = Client()
|
|
226
|
+
|
|
227
|
+
ws = await websockets.connect('ws:#localhost:18081')
|
|
228
|
+
|
|
229
|
+
# Listen for messages
|
|
230
|
+
async def listen():
|
|
231
|
+
async for data in ws:
|
|
232
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(json.loads(data), client))
|
|
233
|
+
print(f'收到服务器消息: {data}')
|
|
234
|
+
|
|
235
|
+
# Run listener and proceed
|
|
236
|
+
asyncio.create_task(listen())
|
|
237
|
+
|
|
238
|
+
client.setSender(Sender(ws))
|
|
239
|
+
|
|
240
|
+
main_obj = await client.getObject('greeting')
|
|
241
|
+
result = await main_obj.greeting()
|
|
242
|
+
print(result)
|
|
243
|
+
|
|
244
|
+
asyncio.run(main())
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## 教程
|
|
248
|
+
|
|
249
|
+
### 基本的信息发送流程
|
|
250
|
+
|
|
251
|
+
一次RPC调用应当包括以下过程:
|
|
252
|
+
|
|
253
|
+
- 一个从某个client当中获得的远程对象的一个方法被调用。
|
|
254
|
+
- 这个远程对象的代理调用client封装一系列的方法最后组成一条请求(Response)信息(Message)
|
|
255
|
+
- Client调用分配给其的ISender。发送消息。此远程方法通过异步的方式阻塞在这里。
|
|
256
|
+
- 接收端的receiver收到信息以后,委派给对应的对象进行处理。在receiver接受一个消息的时候,应当同步传递一个用于应答的client。
|
|
257
|
+
- 当委派对象处理完时返回结果。
|
|
258
|
+
- 返回结果通过应答client返回给请求侧。
|
|
259
|
+
- 请求侧的receiver收到消息后。此请求的promise被设置为resolve或reject完成本轮请求。
|
|
260
|
+
|
|
261
|
+
### 经典的使用流程
|
|
262
|
+
|
|
263
|
+
完整代码参见示例。
|
|
264
|
+
|
|
265
|
+
服务端
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
# 设置host Id
|
|
270
|
+
setHostId('backend')
|
|
271
|
+
|
|
272
|
+
#setMain&setObject接受一个对象作为参数而非字典。注意不要弄错了。
|
|
273
|
+
from xuri_rpc import dict2obj
|
|
274
|
+
|
|
275
|
+
#设置用于提供起始方法的main对象,在这个面对象里,你应当添加一些方法返回更多的远程对象。或者你也可以直接就在这个main方法里实现一些业务逻辑的调用。
|
|
276
|
+
async def plus(a,b,callback):
|
|
277
|
+
await callback(a + b)
|
|
278
|
+
return a+b
|
|
279
|
+
getMessageReceiver().setMain(dict2obj({
|
|
280
|
+
'plus': plus
|
|
281
|
+
}))
|
|
282
|
+
|
|
283
|
+
#你创建了某种方式的消息通道从中获得信息反序列化之后传递给 MessageReceiver,当你把收到的消息传递给MessageReceiver的时候,你还需要同时传递一个client,毕竟这次调用的返回结果你得找个东西发回去。
|
|
284
|
+
async def handle_connection(ws, path):
|
|
285
|
+
# 创建一个client用于发送返回信息
|
|
286
|
+
client = Client()
|
|
287
|
+
client.setSender(Sender(ws))
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
async for data in ws:
|
|
291
|
+
# 处理接收到的信息
|
|
292
|
+
message = json.loads(data)
|
|
293
|
+
asyncio.ensure_future(getMessageReceiver().onReceiveMessage(message, client))
|
|
294
|
+
except Exception as error:
|
|
295
|
+
print('客户端连接错误:', error)
|
|
296
|
+
|
|
297
|
+
start_server = websockets.serve(handle_connection, "", 18081)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
客户端
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#定义一个发送装置。把一个消息对象序列化,并通过某一种底层的传输机制发送出去,比如说一个进程管道,或者是一个web socket连接。
|
|
305
|
+
class Sender:
|
|
306
|
+
def __init__(self, ws):
|
|
307
|
+
self.ws = ws
|
|
308
|
+
|
|
309
|
+
async def send(self, message):
|
|
310
|
+
# message is an object can be jsonified
|
|
311
|
+
await self.ws.send(json.dumps(message))
|
|
312
|
+
|
|
313
|
+
#设置host ID。
|
|
314
|
+
setHostId('frontend')
|
|
315
|
+
#创建一个client,这个client是完成了RPC调用的一些复杂操作的对象。
|
|
316
|
+
client = Client()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
#创建一个管道。这个管道。既被用于发送信息,也被用于接收返回结果。
|
|
320
|
+
ws = await websockets.connect('ws:#localhost:18081')
|
|
321
|
+
|
|
322
|
+
async def on_message(data):
|
|
323
|
+
#创建一个receiver,你发出去的东西总得找个地方接返回结果,是吧?
|
|
324
|
+
await getMessageReceiver().onReceiveMessage(json.loads(data), client)
|
|
325
|
+
print(f'收到服务器消息: {data}')
|
|
326
|
+
|
|
327
|
+
# Run message reception in the background
|
|
328
|
+
async def listen():
|
|
329
|
+
async for message in ws:
|
|
330
|
+
asyncio.ensure_future(on_message(message))
|
|
331
|
+
|
|
332
|
+
# Start listening in the background
|
|
333
|
+
asyncio.ensure_future(listen())
|
|
334
|
+
|
|
335
|
+
#给client绑定对应的sender
|
|
336
|
+
client.setSender(Sender(ws))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
#获得main对象
|
|
341
|
+
main_proxy = await client.getMain()
|
|
342
|
+
#main对象是服务侧定义的一个远程对象,你应当从这里获得到你定义的函数。调用这些函数,获得更进一步的远程对象或者执行一些业务逻辑。
|
|
343
|
+
def callback(result):
|
|
344
|
+
# breakpoint()
|
|
345
|
+
print('from callback', result)
|
|
346
|
+
result = await main_proxy.plus(1, 2, asProxy(callback))
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
### host
|
|
353
|
+
|
|
354
|
+
这个东西相当于一个逻辑上的主机概念。正常情况下,它应当是对应于你这个程序的。但是如果你的程序里可能需要很多种rpc连接,那么每一种连接应当对应于一个这样的逻辑主机一个host。
|
|
355
|
+
|
|
356
|
+
你应当给你的主机起一个名字这个名字在你的一整套。 分布式。系统里应当是唯一的。 setHostId接受一个字符串作为参数指定默认的主机的名称。通常情况下,你应当且仅应当调用一次这个方法。
|
|
357
|
+
|
|
358
|
+
对于有多个rpc连接的情况,也就是你需要设置多个host的情况。你可以在client和receiver的构造函数中传入一个字符串参数作为这个client或者receiver的所属host。
|
|
359
|
+
|
|
360
|
+
### asProxy,setArgsAutoWrapper
|
|
361
|
+
|
|
362
|
+
一个远程对象接受的参数对象可以分为data类型和proxy类型。
|
|
363
|
+
|
|
364
|
+
Data类型的对象就是一个完全表示数据的对象,例如一个字符串或一个字典或者其它嵌套,复合结构,它可以被以一种确定的方式序列化。在系统实际运行的过程当中。
|
|
365
|
+
|
|
366
|
+
Proxy类型对象将会被复制到远程端被远程端处理。推荐这个对象是不可变的对象。而proxy类型的对象。应当是在系统中主要负责承载计算的承载系统逻辑的部分结构。通常这类对象具有广泛的关联,不适合也不能被序列化。在系统执行的过程当中,此对象将会产生一个代理对象发送到远程上。远程主机调用代理对象来控制对象本体执行具体函数。
|
|
367
|
+
|
|
368
|
+
虽然我们提供了一种调用远程对象的方法,但是,原则上我们不建议频繁的创建和使用远程对象,因为不可能把远程对象和本地对象当成一种东西来用。首先我们并没有提供一个健全的卸载远程对象的机制,即没有一种自动的垃圾回收系统。因此,这可能会导致某种意义上的内存泄露。其次是受通信延迟的影响,调用远程方法可能会降低程序效率。
|
|
369
|
+
|
|
370
|
+
我们提供了一个asProxy函数。显式地声明一个传递给远程方法的参数是proxy类型。在实现上,它返回了一个proxy类型对象的表示对象,这是一个PreArgObj的实例。
|
|
371
|
+
|
|
372
|
+
我们还在client上提供了一个setArgsAutoWrapper函数。如果在你的系统中,你能够确定某一种模式的参数必然是一个proxy类型的参数,那么你可以通过给这个函数传递一个函数作为参数。来实现一种自动的转换。注意,应当放过as proxy返回的结果。
|
|
373
|
+
|
|
374
|
+
### context,interceptor & setObject
|
|
375
|
+
|
|
376
|
+
我们可能需要面临这样的一个情况:对于所有的请求,在请求被实际处理之前,我们可能需要进行一些准备,例如创建一个数据库会话。
|
|
377
|
+
|
|
378
|
+
我们将这种机制称之为上下文。上下文应当能够在请求处理的全过程当中的任意一个地方都可以被引用。
|
|
379
|
+
|
|
380
|
+
但是这个机制在异步情况下实现起来比较困难,尤其是浏览器端目前不支持在asynchronized的环境下的一个持续的全局的字典。
|
|
381
|
+
|
|
382
|
+
作为一个替代方法。我们增加了一种机制。在这个机制中。负责处理请求的函数接受到的第一个参数为一个表示上下文的字典。在构建服务器的时候。通过调用addInterceptor 方法。增加请求前后的处理机制。并在其中await的调用next处理后续。使用这个方法的远程端只需要正常的传递参数。但是在服务端处理这个请求的时候会增加第一个参数为context。
|
|
383
|
+
|
|
384
|
+
具体流程为:
|
|
385
|
+
|
|
386
|
+
服务端
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
#在服务端的message server上设置一个Object。注意这个object上的每一个方法的第一个参数都是一个context。且set object的第三个参数为true,表示。这是一个启用了context的机制的对象。。
|
|
390
|
+
getMessageReceiver().setObject("greeting",dict2obj( {
|
|
391
|
+
"greeting": lambda context: f"hi,{context['a']} and {context['b']}"
|
|
392
|
+
}), True)
|
|
393
|
+
#添加拦截器。每个拦截器是如下声明的一个方法。参数依次是上下文。当前请求的message。返回的client。调用下一层。拦截器或者是函数本体的next。
|
|
394
|
+
async def b(context,message,client,next):
|
|
395
|
+
context['b']='john'# Context是一个字典,你可以在这里添加上任何你想要做的事情。
|
|
396
|
+
await next()#调用下一层
|
|
397
|
+
|
|
398
|
+
getMessageReceiver().addInterceptor(b)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
客户端
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
#通过 getObject 的获得具有上下文功能的对象。
|
|
405
|
+
main_obj = await client.getObject('greeting')
|
|
406
|
+
#调用函数的时候,不需要传递上下文这个参数。
|
|
407
|
+
result = await main_obj.greeting()
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
完整代码见示例
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xuri-rpc"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "xuri-rpc in python"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "prometheus-0017"}
|
|
7
|
+
]
|
|
8
|
+
license = {text = "MIT"}
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
]
|
|
13
|
+
packages = [ { include = "xuri_rpc" } ] # 指定要包含的 Python 包目录
|
|
14
|
+
|
|
15
|
+
[tool.poetry]
|
|
16
|
+
|
|
17
|
+
[tool.poetry.group.dev.dependencies]
|
|
18
|
+
pytest = "^8.4.1"
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
22
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, TypeVar, Generic, Callable, Mapping, Union, Set
|
|
2
|
+
import weakref
|
|
3
|
+
import asyncio
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import TypedDict,Literal
|
|
6
|
+
|
|
7
|
+
T = TypeVar('T')
|
|
8
|
+
ArgObjType = Literal['proxy', 'data', None]
|
|
9
|
+
class dynamic_object(object):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PreArgObj(Generic[T]):
|
|
14
|
+
def __init__(self, arg_type: str, data: T):
|
|
15
|
+
self.type = arg_type
|
|
16
|
+
self.data = data
|
|
17
|
+
class ArgObj(TypedDict):
|
|
18
|
+
type:ArgObjType
|
|
19
|
+
data:Any
|
|
20
|
+
|
|
21
|
+
class PlainProxy(TypedDict):
|
|
22
|
+
id: str
|
|
23
|
+
hostId: str
|
|
24
|
+
members: List[Dict[str, str]]
|
|
25
|
+
|
|
26
|
+
hostId: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
def setHostId(id: str):
|
|
29
|
+
global hostId
|
|
30
|
+
hostId = id
|
|
31
|
+
getOrCreateOption(None).hostId = id
|
|
32
|
+
|
|
33
|
+
def _deleteProxy(id: str, host_id: Optional[str] = None):
|
|
34
|
+
getOrCreateOption(host_id).plainProxyManager.delete_by_id(id)
|
|
35
|
+
|
|
36
|
+
class Request(TypedDict):
|
|
37
|
+
id: str
|
|
38
|
+
objectId: str
|
|
39
|
+
method: str
|
|
40
|
+
args: List[ArgObj[Any]]
|
|
41
|
+
|
|
42
|
+
class Response(TypedDict):
|
|
43
|
+
id: str
|
|
44
|
+
idFor: Optional[str]
|
|
45
|
+
status: Optional[int]
|
|
46
|
+
trace: Optional[str]
|
|
47
|
+
data: Optional[ArgObj[Any]]
|
|
48
|
+
|
|
49
|
+
Message=Union[Response,Request]
|
|
50
|
+
# Not = Mapping[str, Any]
|
|
51
|
+
|
|
52
|
+
class RunnableProxy:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
class RunnableProxyManager:
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self.map: Dict[str, weakref.ReferenceType[RunnableProxy]] = {}
|
|
58
|
+
|
|
59
|
+
def set(self, id: str, proxy: RunnableProxy):
|
|
60
|
+
self.map[id] = weakref.ref(proxy)
|
|
61
|
+
|
|
62
|
+
def get(self, id: str) -> Optional[RunnableProxy]:
|
|
63
|
+
ref = self.map.get(id)
|
|
64
|
+
if ref is not None:
|
|
65
|
+
result = ref()
|
|
66
|
+
if result is None:
|
|
67
|
+
del self.map[id]
|
|
68
|
+
return result
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
class PlainProxyManager:
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.proxy_map: Dict[dynamic_object, str] = {}
|
|
74
|
+
self.reverse_proxy_map: Dict[str, dynamic_object] = {}
|
|
75
|
+
self.holding={}
|
|
76
|
+
self.pythonId=id
|
|
77
|
+
|
|
78
|
+
def set(self, obj: dynamic_object, id: str):
|
|
79
|
+
self.proxy_map[self.pythonId(obj)] = id
|
|
80
|
+
self.reverse_proxy_map[id] = obj
|
|
81
|
+
self.holding[self.pythonId(obj)]=obj
|
|
82
|
+
|
|
83
|
+
def getById(self, id: str) -> Optional[dynamic_object]:
|
|
84
|
+
return self.reverse_proxy_map.get(id)
|
|
85
|
+
|
|
86
|
+
def get(self, obj: dynamic_object) -> str:
|
|
87
|
+
return self.proxy_map[self.pythonId(obj)]
|
|
88
|
+
|
|
89
|
+
def has(self, obj: dynamic_object) -> bool:
|
|
90
|
+
return self.pythonId(obj) in self.proxy_map
|
|
91
|
+
|
|
92
|
+
def deleteById(self, id: str):
|
|
93
|
+
obj = self.reverse_proxy_map.get(id)
|
|
94
|
+
if obj is not None:
|
|
95
|
+
del self.proxy_map[obj]
|
|
96
|
+
del self.reverse_proxy_map[id]
|
|
97
|
+
del self.holding[self.pythonId(obj)]
|
|
98
|
+
|
|
99
|
+
def delete(self, obj: dynamic_object):
|
|
100
|
+
id = self.proxy_map.get(obj)
|
|
101
|
+
if id is not None:
|
|
102
|
+
del self.reverse_proxy_map[id]
|
|
103
|
+
del self.proxy_map[obj]
|
|
104
|
+
del self.holding[self.pythonId(obj)]
|
|
105
|
+
|
|
106
|
+
def asProxy(obj: dynamic_object, host_id_from: Optional[str] = None) -> PreArgObj[Union[PlainProxy, None]]:
|
|
107
|
+
option = getOrCreateOption(host_id_from)
|
|
108
|
+
proxy_manager = option.plainProxyManager
|
|
109
|
+
host_id = option.hostId
|
|
110
|
+
|
|
111
|
+
if host_id is None:
|
|
112
|
+
raise ValueError("hostId is null")
|
|
113
|
+
|
|
114
|
+
if not proxy_manager.has(obj):
|
|
115
|
+
id_ = getId()
|
|
116
|
+
proxy_manager.set(obj, id_)
|
|
117
|
+
|
|
118
|
+
id_ = proxy_manager.get(obj)
|
|
119
|
+
|
|
120
|
+
if callable(obj):
|
|
121
|
+
proxy: PlainProxy = {
|
|
122
|
+
'id': id_,
|
|
123
|
+
'hostId': host_id,
|
|
124
|
+
'members': [{'type': 'function', 'name': '__call__'}]
|
|
125
|
+
}
|
|
126
|
+
else:
|
|
127
|
+
if obj is None:
|
|
128
|
+
proxy = None
|
|
129
|
+
else:
|
|
130
|
+
members = [
|
|
131
|
+
{'name': k, 'type': 'function'}
|
|
132
|
+
for k in dir(obj)
|
|
133
|
+
if callable(getattr(obj, k)) and not k.startswith('__')
|
|
134
|
+
]
|
|
135
|
+
proxy = {
|
|
136
|
+
'id': id_,
|
|
137
|
+
'hostId': host_id,
|
|
138
|
+
'members': members
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return PreArgObj('proxy', proxy)
|
|
142
|
+
|
|
143
|
+
def generateErrorReply(message: Request, error_text: str, status: int = 500) -> Response:
|
|
144
|
+
reply: Response = {
|
|
145
|
+
'id': getId(),
|
|
146
|
+
'idFor': message['id'],
|
|
147
|
+
'trace': error_text,
|
|
148
|
+
'status': status,
|
|
149
|
+
'data': None
|
|
150
|
+
}
|
|
151
|
+
return reply
|
|
152
|
+
|
|
153
|
+
def dict2obj(d: dict):
|
|
154
|
+
obj=dynamic_object()
|
|
155
|
+
for k, v in d.items():
|
|
156
|
+
setattr(obj, k, v)
|
|
157
|
+
return obj
|
|
158
|
+
class ISender:
|
|
159
|
+
def send(self, message: Union[Request, Response]):
|
|
160
|
+
raise NotImplementedError('Not implement')
|
|
161
|
+
|
|
162
|
+
class NotImplementSender(ISender):
|
|
163
|
+
def send(self, message: Union[Request, Response]):
|
|
164
|
+
raise NotImplementedError('Not implement')
|
|
165
|
+
|
|
166
|
+
class Client:
|
|
167
|
+
def __init__(self, host_id: Optional[str] = None):
|
|
168
|
+
self.sender: ISender = NotImplementSender()
|
|
169
|
+
self.host_id = host_id
|
|
170
|
+
self.args_auto_wrapper = shallowAutoWrapper
|
|
171
|
+
|
|
172
|
+
def setArgsAutoWrapper(self, auto_wrapper: Callable[[Any], Any]):
|
|
173
|
+
self.args_auto_wrapper = auto_wrapper
|
|
174
|
+
|
|
175
|
+
def setSender(self, sender: ISender):
|
|
176
|
+
if self.sender is not None and not isinstance(self.sender, NotImplementSender):
|
|
177
|
+
raise ValueError('sender already set')
|
|
178
|
+
self.sender = sender
|
|
179
|
+
|
|
180
|
+
def putAwait(self, id_: str, resolve: Callable[[Any], None], reject: Callable[[Any], None]):
|
|
181
|
+
# print(f"{self.getHostId()} is waiting for {id_}")
|
|
182
|
+
getOrCreateOption(self.host_id).request_pending_dict[id_] = {'resolve': resolve, 'reject': reject}
|
|
183
|
+
|
|
184
|
+
async def waitForRequest(self, request: Request) -> dynamic_object:
|
|
185
|
+
if(debugFlag):
|
|
186
|
+
print(f"{self.getHostId()} is waiting for {request['id']}")
|
|
187
|
+
print(request)
|
|
188
|
+
sender = self.sender
|
|
189
|
+
future = asyncio.Future()
|
|
190
|
+
|
|
191
|
+
def callback(resolve, reject):
|
|
192
|
+
if sender is None:
|
|
193
|
+
raise ValueError('sender not set')
|
|
194
|
+
self.putAwait(request['id'], resolve, reject)
|
|
195
|
+
asyncio.ensure_future(sender.send(request))
|
|
196
|
+
|
|
197
|
+
callback(future.set_result, future.set_exception)
|
|
198
|
+
return await future
|
|
199
|
+
|
|
200
|
+
def toArgObj(self, obj: Any) -> ArgObj[Any]:
|
|
201
|
+
if isinstance(obj, PreArgObj):
|
|
202
|
+
return dict(type='proxy',data=obj.data)
|
|
203
|
+
else:
|
|
204
|
+
return dict(type='data', data=obj)
|
|
205
|
+
|
|
206
|
+
def getHostId(self) -> str:
|
|
207
|
+
if self.host_id is None:
|
|
208
|
+
return getOrCreateOption(None).hostId
|
|
209
|
+
else:
|
|
210
|
+
return self.host_id
|
|
211
|
+
|
|
212
|
+
def getProxyManager(self) -> PlainProxyManager:
|
|
213
|
+
return getOrCreateOption(self.host_id).plainProxyManager
|
|
214
|
+
|
|
215
|
+
def getRunnableProxyManager(self) -> RunnableProxyManager:
|
|
216
|
+
return getOrCreateOption(self.host_id).runnable_proxy_manager
|
|
217
|
+
|
|
218
|
+
def reverseToArgObj(self, arg_obj: ArgObj[Any]) -> Any:
|
|
219
|
+
if arg_obj['type'] == 'data':
|
|
220
|
+
return arg_obj['data']
|
|
221
|
+
else:
|
|
222
|
+
result=dynamic_object()
|
|
223
|
+
data: PlainProxy = arg_obj['data']
|
|
224
|
+
|
|
225
|
+
if data['hostId'] == self.host_id:
|
|
226
|
+
return self.getProxyManager().getById(data['id'])
|
|
227
|
+
|
|
228
|
+
dynamic_object_ = self.getRunnableProxyManager().get(data['id'])
|
|
229
|
+
if dynamic_object_ is not None:
|
|
230
|
+
return dynamic_object_
|
|
231
|
+
|
|
232
|
+
for _member in data['members']:
|
|
233
|
+
key = _member['type']
|
|
234
|
+
if key == 'property':
|
|
235
|
+
print('not implemented')
|
|
236
|
+
elif key == 'function':
|
|
237
|
+
def closure():
|
|
238
|
+
member=_member
|
|
239
|
+
async def func(*args):
|
|
240
|
+
args_transformed = [self.args_auto_wrapper(arg) for arg in args]
|
|
241
|
+
args_transformed = [self.toArgObj(arg) for arg in args_transformed]
|
|
242
|
+
request: Request = {
|
|
243
|
+
'objectId': data['id'],
|
|
244
|
+
'id': getId(),
|
|
245
|
+
'meta':{},
|
|
246
|
+
'method': member['name'],
|
|
247
|
+
'args': args_transformed
|
|
248
|
+
}
|
|
249
|
+
res = await self.waitForRequest(request)
|
|
250
|
+
return res
|
|
251
|
+
return func
|
|
252
|
+
func=closure()
|
|
253
|
+
setattr(result, _member['name'], func)
|
|
254
|
+
else:
|
|
255
|
+
raise ValueError('no such function')
|
|
256
|
+
|
|
257
|
+
if(hasattr(result,'__call__')):
|
|
258
|
+
def closure(result0):
|
|
259
|
+
async def call_func(*args):
|
|
260
|
+
return await result0.__call__(*args)
|
|
261
|
+
def __getitem__(key):
|
|
262
|
+
if(key=="__call__"):
|
|
263
|
+
return call_func
|
|
264
|
+
return result0[key]
|
|
265
|
+
|
|
266
|
+
call_func.__getitem__ = __getitem__
|
|
267
|
+
return call_func
|
|
268
|
+
result=closure(result)
|
|
269
|
+
|
|
270
|
+
self.getRunnableProxyManager().set(data['id'], result)
|
|
271
|
+
return result
|
|
272
|
+
|
|
273
|
+
async def getObject(self, objectId: str) -> RunnableProxy:
|
|
274
|
+
request: Request = {
|
|
275
|
+
'id': getId(),
|
|
276
|
+
'objectId': 'main0',
|
|
277
|
+
'method': 'getMain',
|
|
278
|
+
'args': [self.toArgObj(objectId)]
|
|
279
|
+
}
|
|
280
|
+
res = await self.waitForRequest(request)
|
|
281
|
+
return res
|
|
282
|
+
|
|
283
|
+
async def getMain(self) -> RunnableProxy:
|
|
284
|
+
return await self.getObject('main')
|
|
285
|
+
|
|
286
|
+
message_receiver: Optional['MessageReceiver'] = None
|
|
287
|
+
|
|
288
|
+
def getMessageReceiver() -> 'MessageReceiver':
|
|
289
|
+
global message_receiver
|
|
290
|
+
if message_receiver is None:
|
|
291
|
+
message_receiver = MessageReceiver()
|
|
292
|
+
return message_receiver
|
|
293
|
+
|
|
294
|
+
RpcContext = Dict[str, Any]
|
|
295
|
+
NextFunction=Callable[[], None]
|
|
296
|
+
NextGenerator = Callable[[], NextFunction]
|
|
297
|
+
Interceptor = Callable[[RpcContext, Request, Client, NextFunction], None]
|
|
298
|
+
NextFunction = Callable[[], None]
|
|
299
|
+
AutoWrapper = Callable[[Any], Any]
|
|
300
|
+
|
|
301
|
+
shallowAutoWrapper: AutoWrapper = lambda obj: (
|
|
302
|
+
asProxy(obj) if callable(obj) else
|
|
303
|
+
asProxy(obj) if isinstance(obj, list) and any(callable(item) for item in obj) else
|
|
304
|
+
asProxy(obj) if isinstance(obj, dict) and any(callable(value) for value in obj.values()) else
|
|
305
|
+
obj
|
|
306
|
+
)
|
|
307
|
+
class symbol:
|
|
308
|
+
def __init__(self,id_:str):
|
|
309
|
+
self.id=id_
|
|
310
|
+
pass
|
|
311
|
+
options: Dict[Union[str, symbol], 'MessageReceiverOptions'] = {}
|
|
312
|
+
default_host = symbol('defaultHost')
|
|
313
|
+
|
|
314
|
+
def getOrCreateOption(id_: Optional[Union[str, symbol]] = None) -> 'MessageReceiverOptions':
|
|
315
|
+
if id_ is None or id_ == hostId:
|
|
316
|
+
id_ = default_host
|
|
317
|
+
if isinstance(id_, (str, symbol)):
|
|
318
|
+
if id_ not in options:
|
|
319
|
+
options[id_] = MessageReceiverOptions()
|
|
320
|
+
options[id_].hostId=id_
|
|
321
|
+
return options[id_]
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError('Invalid argument passed')
|
|
324
|
+
|
|
325
|
+
RequestPendingDict = Dict[str, Dict[str, Callable[[Any], None]]]
|
|
326
|
+
from typing import Optional
|
|
327
|
+
|
|
328
|
+
class MessageReceiverOptions:
|
|
329
|
+
def __init__(
|
|
330
|
+
self,
|
|
331
|
+
):
|
|
332
|
+
"""
|
|
333
|
+
初始化 MessageReceiverOptions 对象。
|
|
334
|
+
"""
|
|
335
|
+
self.plainProxyManager = PlainProxyManager()
|
|
336
|
+
self.runnable_proxy_manager = RunnableProxyManager()
|
|
337
|
+
self.hostId = ''
|
|
338
|
+
self.request_pending_dict = {}
|
|
339
|
+
debugFlag=False
|
|
340
|
+
def setDebugFlag(flag):
|
|
341
|
+
global debugFlag
|
|
342
|
+
debugFlag=flag
|
|
343
|
+
|
|
344
|
+
class MessageReceiver:
|
|
345
|
+
def __init__(self, host_id=None):
|
|
346
|
+
self.rpc_server = None
|
|
347
|
+
self.interceptors = []
|
|
348
|
+
self.object_with_context = set()
|
|
349
|
+
self.resultAutoWrapper = shallowAutoWrapper
|
|
350
|
+
self.host_id = host_id
|
|
351
|
+
self.getProxyManager().set(dict2obj({'getMain': lambda objectId: asProxy(self.getProxyManager().getById(objectId), self.getHostId())}), 'main0')
|
|
352
|
+
|
|
353
|
+
def setResultAutoWrapper(self, auto_wrapper):
|
|
354
|
+
self.resultAutoWrapper = auto_wrapper
|
|
355
|
+
|
|
356
|
+
async def withContext(self, message, client, args, func):
|
|
357
|
+
result = {}
|
|
358
|
+
def setResult(reply:Response):
|
|
359
|
+
result['value'] = reply
|
|
360
|
+
context = {
|
|
361
|
+
"setResult":setResult
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
def generate_interceptor_executor(index_of_interceptor):
|
|
365
|
+
if index_of_interceptor < len(self.interceptors):
|
|
366
|
+
async def execute_this_interceptor():
|
|
367
|
+
|
|
368
|
+
interceptor = self.interceptors[index_of_interceptor]
|
|
369
|
+
async def generateAndExecuteNext():
|
|
370
|
+
executor=generate_interceptor_executor(index_of_interceptor+1)
|
|
371
|
+
await executor()
|
|
372
|
+
v=await interceptor(context, message, client, generateAndExecuteNext)
|
|
373
|
+
if(hasattr(v, '__await__')):
|
|
374
|
+
v=await v
|
|
375
|
+
return execute_this_interceptor
|
|
376
|
+
else:
|
|
377
|
+
async def execute_this_interceptor():
|
|
378
|
+
v=func(context, *args)
|
|
379
|
+
if(hasattr(v,'__await__')):
|
|
380
|
+
v=await v
|
|
381
|
+
result['value'] = v
|
|
382
|
+
return execute_this_interceptor
|
|
383
|
+
|
|
384
|
+
first_interceptor_executor = generate_interceptor_executor(0)
|
|
385
|
+
await first_interceptor_executor()
|
|
386
|
+
return result['value']
|
|
387
|
+
|
|
388
|
+
def getProxyManager(self)->PlainProxyManager:
|
|
389
|
+
return getOrCreateOption(self.host_id).plainProxyManager
|
|
390
|
+
|
|
391
|
+
def getRunnableProxyManager(self)->RunnableProxyManager:
|
|
392
|
+
return getOrCreateOption(self.host_id).runnable_proxy_manager
|
|
393
|
+
|
|
394
|
+
def getHostId(self):
|
|
395
|
+
return getOrCreateOption(self.host_id).hostId
|
|
396
|
+
|
|
397
|
+
def getReqPending(self):
|
|
398
|
+
return getOrCreateOption(self.host_id).request_pending_dict
|
|
399
|
+
|
|
400
|
+
def setMain(self, obj):
|
|
401
|
+
self.rpc_server = obj
|
|
402
|
+
self.setObject('main', self.rpc_server, False)
|
|
403
|
+
|
|
404
|
+
def setObject(self, id, obj, with_context):
|
|
405
|
+
if(isinstance(obj,dict)):
|
|
406
|
+
print('warning: the object of setObject should be an object, but you passed a dict')
|
|
407
|
+
self.getProxyManager().set(obj, id)
|
|
408
|
+
if with_context:
|
|
409
|
+
self.object_with_context.add(id)
|
|
410
|
+
|
|
411
|
+
def addInterceptor(self, interceptor):
|
|
412
|
+
self.interceptors.append(interceptor)
|
|
413
|
+
|
|
414
|
+
def putAwait(self, id, resolve, reject):
|
|
415
|
+
self.getReqPending()[id] = {'resolve': resolve, 'reject': reject}
|
|
416
|
+
|
|
417
|
+
async def onReceiveMessage(self,message:Union[Request,Response], client_for_call_back:Client):
|
|
418
|
+
if(client_for_call_back==None):
|
|
419
|
+
raise Exception("clientForCallBack must not null")
|
|
420
|
+
if(isinstance(client_for_call_back,Client)==False):
|
|
421
|
+
raise Exception("clientForCallBack must be a Client")
|
|
422
|
+
if(debugFlag):
|
|
423
|
+
print(f"{self.getHostId()} received a "+
|
|
424
|
+
f"{'reply, which is for ' + message['id'] + ' and it is ' + message.get('idFor') if message.get('idFor') else 'request, which id is ' + message['id']}")
|
|
425
|
+
print(message);
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def isRequest(message:Union[Request,Response]):
|
|
429
|
+
return message.get('idFor')==None
|
|
430
|
+
|
|
431
|
+
# Is request, not reply
|
|
432
|
+
if(isRequest(message)):
|
|
433
|
+
args = [client_for_call_back.reverseToArgObj(x) for x in message['args']]
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
object = self.getProxyManager().getById(message['objectId'])
|
|
437
|
+
if object is None:
|
|
438
|
+
coroutine=client_for_call_back.sender.send(generateErrorReply(message, 'object not found', 100))
|
|
439
|
+
asyncio.ensure_future(coroutine)
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
result = None
|
|
443
|
+
should_with_context = message['objectId'] in self.object_with_context
|
|
444
|
+
|
|
445
|
+
if message['method'] == '__call__':
|
|
446
|
+
if should_with_context:
|
|
447
|
+
result = await self.withContext(message, client_for_call_back, args, object)
|
|
448
|
+
else:
|
|
449
|
+
result = object(*args)
|
|
450
|
+
else:
|
|
451
|
+
if should_with_context:
|
|
452
|
+
result = await self.withContext(message, client_for_call_back, args, getattr(object, message['method']))
|
|
453
|
+
else:
|
|
454
|
+
result = getattr(object, message['method'])(*args)
|
|
455
|
+
|
|
456
|
+
if(hasattr(result,'__await__')):
|
|
457
|
+
result=await result
|
|
458
|
+
|
|
459
|
+
result = self.resultAutoWrapper(result)
|
|
460
|
+
wrapped_result = client_for_call_back.toArgObj(result)
|
|
461
|
+
cor=client_for_call_back.sender.send({
|
|
462
|
+
'id': getId(),
|
|
463
|
+
'idFor': message['id'],
|
|
464
|
+
'data': wrapped_result,
|
|
465
|
+
'trace':None,
|
|
466
|
+
'status': 200
|
|
467
|
+
})
|
|
468
|
+
asyncio.ensure_future(cor)
|
|
469
|
+
except Exception as e:
|
|
470
|
+
trace_str = '\n'.join([x.strip() for x in str(e.__traceback__).split('\n')])
|
|
471
|
+
client_for_call_back.sender.send({
|
|
472
|
+
'id': getId(),
|
|
473
|
+
'idFor': message['id'],
|
|
474
|
+
'data': dict(type='data',data=None),
|
|
475
|
+
'trace': trace_str,
|
|
476
|
+
'status': -1
|
|
477
|
+
})
|
|
478
|
+
import traceback
|
|
479
|
+
traceback.print_exc()
|
|
480
|
+
|
|
481
|
+
else:
|
|
482
|
+
idFor=message.get('idFor')
|
|
483
|
+
req_pending = self.getReqPending()
|
|
484
|
+
if req_pending[idFor] is None:
|
|
485
|
+
print(f"[{self.getHostId()}] no pending request for id {idFor}", message)
|
|
486
|
+
return
|
|
487
|
+
|
|
488
|
+
req = req_pending[idFor]
|
|
489
|
+
del req_pending[idFor]
|
|
490
|
+
if message['status'] == 200:
|
|
491
|
+
req['resolve'](client_for_call_back.reverseToArgObj(message['data']))
|
|
492
|
+
else:
|
|
493
|
+
req['reject'](Exception(message))
|
|
494
|
+
idCOunt=0
|
|
495
|
+
def getId() -> str:
|
|
496
|
+
global idCOunt
|
|
497
|
+
idCOunt+=1
|
|
498
|
+
return f'{hostId}{idCOunt}'
|
|
499
|
+
|
|
500
|
+
# Example usage
|
|
501
|
+
if __name__ == "__main__":
|
|
502
|
+
client = Client()
|
|
503
|
+
try:
|
|
504
|
+
main_proxy = asyncio.run(client.getMain())
|
|
505
|
+
print(main_proxy)
|
|
506
|
+
except Exception as e:
|
|
507
|
+
import traceback
|
|
508
|
+
traceback.print_exc()
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|