dubbo-ssl-python 0.9.2__tar.gz → 0.9.6__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.
- dubbo_ssl_python-0.9.6/PKG-INFO +193 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/__init__.py +1 -1
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/client.py +92 -43
- dubbo_ssl_python-0.9.6/dubbo_ssl_python.egg-info/PKG-INFO +193 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/pyproject.toml +3 -4
- dubbo_ssl_python-0.9.2/PKG-INFO +0 -395
- dubbo_ssl_python-0.9.2/dubbo_ssl_python.egg-info/PKG-INFO +0 -395
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/LICENSE +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/README.md +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/codec/__init__.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/codec/decoder.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/codec/encoder.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/__init__.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/constants.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/exceptions.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/util.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/connection/__init__.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/connection/connections.py +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/SOURCES.txt +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/dependency_links.txt +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/requires.txt +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/top_level.txt +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/setup.cfg +0 -0
- {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/tests/test_client.py +0 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dubbo-ssl-python
|
|
3
|
+
Version: 0.9.6
|
|
4
|
+
Summary: Python client for Apache Dubbo RPC protocol, with Hessian2 serialization and SSL/TLS support.
|
|
5
|
+
Author-email: gigi wang <gigiwgoo@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://iocoo.github.io
|
|
7
|
+
Project-URL: Repository, https://github.com/iocoo/dubbo-ssl-python/
|
|
8
|
+
Keywords: dubbo
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: kazoo>=2.8.0
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# dubbo-ssl-python
|
|
18
|
+
|
|
19
|
+
Python client for [Apache Dubbo](https://dubbo.apache.org) RPC protocol. Implements the dubbo wire protocol (16-byte header with magic `0xdabb`) + Hessian2 serialization,enabling Python application to invoke Java Dubbo 2.x services directly. Support TCP,SSL/TLS connections,Zookeeper services discovery.
|
|
20
|
+
Base on https://github.com/apache/dubbo-python2 and https://github.com/huisongyang/dubbo-python3, support Python3.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Full Hessian2 serialization/deserialization (primitives, objects, lists, maps, dates, null, circular references)
|
|
25
|
+
- TCP and SSL/TLS connections with custom CA certificate support
|
|
26
|
+
- Zookeeper-based services discovery with weighted load balancing
|
|
27
|
+
- Connection pooling with automatic heartbeat and reconnection
|
|
28
|
+
- Support `group` settings for Dubbo, Compatible with Dubbo 2.x
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python3.8+
|
|
33
|
+
- [kazoo](https://kazoo.readthedocs.io/). --Zookeeper client (required only if using ZkRegister)
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install dubbo-ssl-python
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Dependencies:
|
|
42
|
+
|
|
43
|
+
- `kazoo` -- Zookeeper client for service discovery
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
### Direct Connection (TCP)
|
|
48
|
+
|
|
49
|
+
Connect to a Dubbo service without Zookeeper:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from dubbo_ssl.client import DubboClient
|
|
53
|
+
from dubbo_ssl.codec.encoder import Object
|
|
54
|
+
|
|
55
|
+
client = DubboClient(
|
|
56
|
+
interface="com.example.service.UserService",
|
|
57
|
+
group="default",
|
|
58
|
+
version="1.0.0",
|
|
59
|
+
dubbo_version="2.7.23",
|
|
60
|
+
host="127.0.0.1:20880",
|
|
61
|
+
verify=None # Plain TCP,no SSL
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
request = Object("com.example.service.UserService.dto.UserRequest")
|
|
65
|
+
request["userId"] = "A000021"
|
|
66
|
+
request["type"] = "QUERY"
|
|
67
|
+
|
|
68
|
+
result = client.call(method="getUser",args=request)
|
|
69
|
+
print(request)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### SSL/TLS Connection
|
|
73
|
+
|
|
74
|
+
Connect to a Dubbo service over SSL with custom CA certificate verification:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from dubbo_ssl.client import DubboClient
|
|
78
|
+
|
|
79
|
+
client = DubboClient(
|
|
80
|
+
interface="com.example.service.UserService",
|
|
81
|
+
group="default",
|
|
82
|
+
version="1.0.0",
|
|
83
|
+
dubbo_version="2.7.23",
|
|
84
|
+
host="127.0.0.1:20880",
|
|
85
|
+
verify="/path/to/DubboRootCA.cer" # Custom CA certificate file
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
request = " Hello, SSL Provider."
|
|
89
|
+
result = client.call(method="getUser",args=request)
|
|
90
|
+
print(request)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The `verify` parameter supports three SSL models:
|
|
94
|
+
|
|
95
|
+
| Value | Behavior |
|
|
96
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
97
|
+
| `None` | Plain TCP,no SSL |
|
|
98
|
+
| `"/path/to/ca.crt"` | SSL with custom CA certificate verification, hostname check disabled |
|
|
99
|
+
| `False` | SSL with no server certificate verification (insecure) |
|
|
100
|
+
|
|
101
|
+
### Zookeeper Service discovery
|
|
102
|
+
|
|
103
|
+
Use Zookeeper for automatic service discovery and load balancing:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from dubbo_ssl.client import DubboClient, ZkRegister
|
|
107
|
+
|
|
108
|
+
zk = ZkRegister(hosts="127.0.0.1:2181", application_name="MyAPP")
|
|
109
|
+
client = DubboClient(
|
|
110
|
+
interface="com.example.service.UserService",
|
|
111
|
+
group="default",
|
|
112
|
+
version="1.0.0",
|
|
113
|
+
dubbo_version="2.7.23",
|
|
114
|
+
zk_register=zk
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
request = " Hello, SSL Provider."
|
|
118
|
+
result = client.call(method="getUser",args=request)
|
|
119
|
+
# When done
|
|
120
|
+
zk.close()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Multiple Arguments
|
|
124
|
+
|
|
125
|
+
Pass multiple arguments to a method
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
result = client.call(
|
|
129
|
+
method="query",
|
|
130
|
+
args=["keyword",10,5]
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Custom Context (Attachments)
|
|
135
|
+
|
|
136
|
+
Pass RPC attachment metadata ( key-value pairs sent alongside the request)
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
result = client.call(
|
|
140
|
+
method="getRequest",
|
|
141
|
+
args=["keyword",10,5],
|
|
142
|
+
context={"traceId":"x-123","tenantId":"T001"}
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Timeout
|
|
147
|
+
|
|
148
|
+
Set a request timeout in seconds.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
result = client.call(
|
|
152
|
+
method="getRequest",
|
|
153
|
+
args=["keyword"],
|
|
154
|
+
timeout=5
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Type Mapping
|
|
159
|
+
|
|
160
|
+
### Python -> Java (Request Encoding)
|
|
161
|
+
|
|
162
|
+
| Python Type | Java Type | Hessian2 Code | Note |
|
|
163
|
+
| -------------------------- | ------------------ | -------------------- | ------------------------------------------------ |
|
|
164
|
+
| `bool` | `boolean` | `T`/ `F` | |
|
|
165
|
+
| `int` (within int32 range) | `int` | Compact `I` encoding | -2^31 ~ 2^31-1 |
|
|
166
|
+
| `int` (exceed int32) | `long` | `L` (8-byte) | |
|
|
167
|
+
| `float` | `double` | Compact `D` encoding | Includes 0.0/1.0 short forms |
|
|
168
|
+
| `str` | `java.lang.String` | Short `S` encoding | UTF-8 |
|
|
169
|
+
| `Object` | Java class | `C` + `O` | Class definition + instance |
|
|
170
|
+
| `list` | Java array | Typed list encoding | Elements must be same type; empty list -> `null` |
|
|
171
|
+
| `None` | null | `N` | |
|
|
172
|
+
|
|
173
|
+
### Java -> Python (Response Decoding)
|
|
174
|
+
|
|
175
|
+
| Java Type | Python Type | Hessian2 Code |
|
|
176
|
+
| ---------------------- | ----------- | --------------------------------------- |
|
|
177
|
+
| `boolean` | `boolean` | `T`/ `F` |
|
|
178
|
+
| `int` | `int` | Compact `I` / `I` (4-byte) |
|
|
179
|
+
| `long` | `int` | Compact / `L` (8-byte) |
|
|
180
|
+
| `double` | `float` | Compact `D` / `D` (8-byte IEEE 754) |
|
|
181
|
+
| `java.lang.String` | `str` | Short / chunked `S` / `R` |
|
|
182
|
+
| java object | `dict` | `C` + `O` (field names as keys) |
|
|
183
|
+
| `java.math.BigDecimal` | `float` | Auto-converted from `value` field |
|
|
184
|
+
| `java.math.BigInteger` | `int` | Auto-converted from `value` field |
|
|
185
|
+
| List / array | `list` | Type / untyped, fixed / variable length |
|
|
186
|
+
| Map | `dict` | `M` / `H` (untyped) |
|
|
187
|
+
| `java.util.Date` | `str` | ISO 8601 format string |
|
|
188
|
+
| `null` | `None` | `N` |
|
|
189
|
+
| Circular reference | resolved | `Q` (ref) |
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Apache License 2.0
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
__version__ = "0.9.
|
|
2
|
+
__version__ = "0.9.6"
|
|
@@ -29,11 +29,12 @@ class DubboClient(object):
|
|
|
29
29
|
:param dubbo_version: dubbo的版本号,默认为2.4.10
|
|
30
30
|
:param zk_register: zookeeper注册中心管理端,参见类:ZkRegister
|
|
31
31
|
:param host: 远程主机地址,用于绕过zookeeper进行直连,例如:172.21.4.98:20882
|
|
32
|
+
:param verify:[None|True|False] None-> non-TLS True -> 启用TLS,验证 dubbo SSL/TLS证书 False-> 启用TLS 不验证服务端证书
|
|
32
33
|
"""
|
|
33
34
|
if not zk_register and not host:
|
|
34
35
|
raise RegisterException('zk_register和host至少需要填入一个')
|
|
35
36
|
|
|
36
|
-
logger.debug('Created client, interface={}, version={}'.format(interface, version))
|
|
37
|
+
logger.debug('Created client, interface={}, version={}, group={}'.format(interface, version, group))
|
|
37
38
|
|
|
38
39
|
self.__interface = interface
|
|
39
40
|
self.__group = group
|
|
@@ -51,7 +52,7 @@ class DubboClient(object):
|
|
|
51
52
|
args = [args]
|
|
52
53
|
|
|
53
54
|
if self.__zk_register:
|
|
54
|
-
host = self.__zk_register.get_provider_host(self.__interface)
|
|
55
|
+
host = self.__zk_register.get_provider_host(self.__interface, self.__version, self.__group)
|
|
55
56
|
else:
|
|
56
57
|
host = self.__host
|
|
57
58
|
|
|
@@ -81,8 +82,12 @@ class ZkRegister(object):
|
|
|
81
82
|
|
|
82
83
|
def __init__(self, hosts, verify_certs=False, use_ssl=False,
|
|
83
84
|
ca_bundle=None, client_cert=None, client_cert_key=None,
|
|
84
|
-
application_name='
|
|
85
|
+
application_name='ConsumerApp'):
|
|
85
86
|
self.hosts = {}
|
|
87
|
+
# watch callback
|
|
88
|
+
self._raw_providers = {}
|
|
89
|
+
self._subscribed_interfaces = set()
|
|
90
|
+
self._interface_lock = threading.Lock()
|
|
86
91
|
zk = KazooClient(hosts=hosts,verify_certs=verify_certs,ca=ca_bundle,
|
|
87
92
|
certfile=client_cert,keyfile=client_cert_key,use_ssl=use_ssl)
|
|
88
93
|
zk.add_listener(self.state_listener)
|
|
@@ -116,7 +121,7 @@ class ZkRegister(object):
|
|
|
116
121
|
由于与Zookeeper的连接断开,所以需要重新订阅消息
|
|
117
122
|
:return:
|
|
118
123
|
"""
|
|
119
|
-
for interface in self.
|
|
124
|
+
for interface in list(self._subscribed_interfaces):
|
|
120
125
|
self.zk.get_children(DUBBO_ZK_PROVIDERS.format(interface), watch=self._watch_children)
|
|
121
126
|
self.zk.get_children(DUBBO_ZK_CONFIGURATORS.format(interface), watch=self._watch_configurators)
|
|
122
127
|
|
|
@@ -129,50 +134,89 @@ class ZkRegister(object):
|
|
|
129
134
|
path = event.path
|
|
130
135
|
logger.debug('zookeeper path: {} changed'.format(path))
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
interface = path.split('/')[2]
|
|
133
138
|
|
|
134
139
|
providers = self.zk.get_children(path, watch=self._watch_children)
|
|
135
|
-
|
|
136
|
-
if not
|
|
137
|
-
logger.debug('no providers found for {}'.format(
|
|
138
|
-
self.
|
|
140
|
+
dubbo_providers = list(filter(lambda p: p['scheme'] == 'dubbo', map(parse_url, providers)))
|
|
141
|
+
if not dubbo_providers:
|
|
142
|
+
logger.debug('no providers found for {}'.format(interface))
|
|
143
|
+
self._raw_providers[interface] = []
|
|
144
|
+
self._clear_hosts_for_interface(interface)
|
|
139
145
|
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
|
|
147
|
+
self._raw_providers[interface] = dubbo_providers
|
|
148
|
+
self._refresh_host_cache(interface)
|
|
149
|
+
logger.debug('interface: {} raw_providers updated,count:{}'.format(interface, len(dubbo_providers)))
|
|
150
|
+
|
|
151
|
+
def _clear_hosts_for_interface(self,interface):
|
|
152
|
+
"""清空指定interface 下的缓存"""
|
|
153
|
+
keys_to_remove = [k for k in self.hosts if k[0] == interface]
|
|
154
|
+
for key in keys_to_remove:
|
|
155
|
+
self.hosts[key] = []
|
|
156
|
+
|
|
157
|
+
def _refresh_host_cache(self,interface):
|
|
158
|
+
"""按照group+version重新过滤更新已注册的缓存"""
|
|
159
|
+
dubbo_providers = self._raw_providers.get(interface, [])
|
|
160
|
+
subscribed_keys = [k for k in self.hosts if k[0] == interface]
|
|
161
|
+
for key in subscribed_keys:
|
|
162
|
+
_, version, group = key
|
|
163
|
+
target_providers = [
|
|
164
|
+
p for p in dubbo_providers if p.get('fields',{}).get('version') == version
|
|
165
|
+
and p.get('fields',{}).get('group') == group
|
|
166
|
+
]
|
|
167
|
+
self.hosts[key] = [p['host'] for p in target_providers]
|
|
168
|
+
logger.debug('refreshed cache for key:{},hosts:{}'.format(key, self.hosts[key]))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def get_provider_host(self, interface, version, group):
|
|
144
172
|
"""
|
|
145
173
|
从zk中可以根据接口名称获取到此接口某个provider的host
|
|
146
|
-
:param interface:
|
|
174
|
+
:param interface: dubbo interface name
|
|
175
|
+
:param version:
|
|
176
|
+
:param group:
|
|
147
177
|
:return:
|
|
148
178
|
"""
|
|
149
|
-
|
|
179
|
+
cache_key = (interface, version, group)
|
|
180
|
+
if cache_key not in self.hosts:
|
|
150
181
|
self.lock.acquire()
|
|
151
182
|
try:
|
|
152
|
-
if
|
|
183
|
+
if cache_key not in self.hosts:
|
|
153
184
|
path = DUBBO_ZK_PROVIDERS.format(interface)
|
|
154
185
|
if self.zk.exists(path):
|
|
155
|
-
self._get_providers_from_zk(path, interface)
|
|
186
|
+
self._get_providers_from_zk(path, interface, version, group)
|
|
156
187
|
self._get_configurators_from_zk(interface)
|
|
157
188
|
else:
|
|
158
189
|
raise RegisterException('No providers for interface {0}'.format(interface))
|
|
159
190
|
finally:
|
|
160
191
|
self.lock.release()
|
|
161
|
-
return self.
|
|
192
|
+
return self._routing_with_weight(cache_key)
|
|
162
193
|
|
|
163
|
-
def _get_providers_from_zk(self, path, interface):
|
|
194
|
+
def _get_providers_from_zk(self, path, interface, version, group):
|
|
164
195
|
"""
|
|
165
196
|
从zk中根据interface获取到providers信息
|
|
166
197
|
:param path:
|
|
167
198
|
:param interface:
|
|
199
|
+
:param version:
|
|
200
|
+
:param group:
|
|
168
201
|
:return:
|
|
169
202
|
"""
|
|
203
|
+
cache_key = (interface, version, group)
|
|
204
|
+
|
|
170
205
|
providers = self.zk.get_children(path, watch=self._watch_children)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
206
|
+
dubbo_providers = list(filter(lambda p: p['scheme'] == 'dubbo', map(parse_url, providers)))
|
|
207
|
+
target_providers = [
|
|
208
|
+
p for p in dubbo_providers
|
|
209
|
+
if p.get('fields',{}).get('version') == version
|
|
210
|
+
and p.get('fields',{}).get('group') == group
|
|
211
|
+
]
|
|
212
|
+
if not target_providers:
|
|
213
|
+
raise RegisterException('no providers for interface {} with version:{} in group:{}'.format(interface,version,group))
|
|
214
|
+
self.hosts[cache_key] = [p['host'] for p in target_providers]
|
|
215
|
+
self._raw_providers[interface] = dubbo_providers
|
|
216
|
+
self._register_consumer(dubbo_providers)
|
|
217
|
+
|
|
218
|
+
with self._interface_lock:
|
|
219
|
+
self._subscribed_interfaces.add(interface)
|
|
176
220
|
|
|
177
221
|
def _get_configurators_from_zk(self, interface):
|
|
178
222
|
"""
|
|
@@ -182,10 +226,10 @@ class ZkRegister(object):
|
|
|
182
226
|
"""
|
|
183
227
|
configurators = self.zk.get_children(DUBBO_ZK_CONFIGURATORS.format(interface), watch=self._watch_configurators)
|
|
184
228
|
if configurators:
|
|
185
|
-
configurators = map(parse_url, configurators)
|
|
229
|
+
configurators = list(map(parse_url, configurators))
|
|
186
230
|
conf = {}
|
|
187
231
|
for configurator in configurators:
|
|
188
|
-
conf[configurator['host']] = configurator['fields'].get('weight', 100) # 默认100
|
|
232
|
+
conf[configurator['host']] = int(configurator['fields'].get('weight', 100)) # 默认100
|
|
189
233
|
self.weights[interface] = conf
|
|
190
234
|
|
|
191
235
|
def _watch_configurators(self, event):
|
|
@@ -202,14 +246,14 @@ class ZkRegister(object):
|
|
|
202
246
|
configurators = self.zk.get_children(DUBBO_ZK_CONFIGURATORS.format(interface),
|
|
203
247
|
watch=self._watch_configurators)
|
|
204
248
|
if configurators:
|
|
205
|
-
configurators = map(parse_url, configurators)
|
|
249
|
+
configurators = list(map(parse_url, configurators))
|
|
206
250
|
conf = {}
|
|
207
251
|
for configurator in configurators:
|
|
208
|
-
conf[configurator['host']] = configurator['fields'].get('weight', 100)
|
|
252
|
+
conf[configurator['host']] = int(configurator['fields'].get('weight', 100))
|
|
209
253
|
logger.debug('{} configurators: {}'.format(interface, conf))
|
|
210
254
|
self.weights[interface] = conf
|
|
211
255
|
else:
|
|
212
|
-
logger.debug('No configurator for interface {}')
|
|
256
|
+
logger.debug('No configurator for interface {}'.format(interface))
|
|
213
257
|
self.weights[interface] = {}
|
|
214
258
|
|
|
215
259
|
def _register_consumer(self, providers):
|
|
@@ -235,6 +279,7 @@ class ZkRegister(object):
|
|
|
235
279
|
'side': 'consumer',
|
|
236
280
|
'timestamp': int(time.time() * 1000),
|
|
237
281
|
'version': provider_fields.get('version'),
|
|
282
|
+
'group': provider_fields.get('group'),
|
|
238
283
|
}
|
|
239
284
|
|
|
240
285
|
params = []
|
|
@@ -247,30 +292,34 @@ class ZkRegister(object):
|
|
|
247
292
|
self.zk.ensure_path(consumer_path)
|
|
248
293
|
self.zk.create_async(consumer_path + '/' + quote(consumer, safe=''), ephemeral=True)
|
|
249
294
|
|
|
250
|
-
def
|
|
295
|
+
def _routing_with_weight(self, cache_key):
|
|
251
296
|
"""
|
|
252
297
|
根据接口名称以及配置好的权重信息获取一个host
|
|
253
|
-
:param
|
|
298
|
+
:param cache_key:
|
|
254
299
|
:return:
|
|
255
300
|
"""
|
|
256
|
-
hosts = self.hosts[
|
|
301
|
+
hosts = self.hosts.get(cache_key,[])
|
|
257
302
|
if not hosts:
|
|
258
|
-
raise RegisterException('no providers for
|
|
303
|
+
raise RegisterException('no providers for {}'.format(cache_key))
|
|
259
304
|
# 此接口没有权重设置,使用朴素的路由算法
|
|
305
|
+
interface = cache_key[0]
|
|
260
306
|
if interface not in self.weights or not self.weights[interface]:
|
|
261
307
|
return random.choice(hosts)
|
|
262
308
|
|
|
263
309
|
weights = self.weights[interface]
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
310
|
+
weighted_hosts = [(host, int(weights.get(host,100))) for host in hosts if int(weights.get(host,100)) >0 ]
|
|
311
|
+
if not weighted_hosts:
|
|
312
|
+
raise RegisterException('no available providers for [{}] (all weights are 0).'.format(cache_key))
|
|
313
|
+
|
|
314
|
+
hosts_list,hosts_weight = zip(*weighted_hosts)
|
|
315
|
+
total = sum(hosts_weight)
|
|
316
|
+
hit = random.randint(0, total-1)
|
|
317
|
+
cumulative = 0
|
|
318
|
+
for i, w in enumerate(hosts_weight):
|
|
319
|
+
cumulative += w
|
|
320
|
+
if hit < cumulative:
|
|
321
|
+
return hosts_list[i]
|
|
322
|
+
return hosts_list[-1]
|
|
274
323
|
|
|
275
324
|
def close(self):
|
|
276
325
|
self.zk.stop()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dubbo-ssl-python
|
|
3
|
+
Version: 0.9.6
|
|
4
|
+
Summary: Python client for Apache Dubbo RPC protocol, with Hessian2 serialization and SSL/TLS support.
|
|
5
|
+
Author-email: gigi wang <gigiwgoo@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://iocoo.github.io
|
|
7
|
+
Project-URL: Repository, https://github.com/iocoo/dubbo-ssl-python/
|
|
8
|
+
Keywords: dubbo
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: kazoo>=2.8.0
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# dubbo-ssl-python
|
|
18
|
+
|
|
19
|
+
Python client for [Apache Dubbo](https://dubbo.apache.org) RPC protocol. Implements the dubbo wire protocol (16-byte header with magic `0xdabb`) + Hessian2 serialization,enabling Python application to invoke Java Dubbo 2.x services directly. Support TCP,SSL/TLS connections,Zookeeper services discovery.
|
|
20
|
+
Base on https://github.com/apache/dubbo-python2 and https://github.com/huisongyang/dubbo-python3, support Python3.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Full Hessian2 serialization/deserialization (primitives, objects, lists, maps, dates, null, circular references)
|
|
25
|
+
- TCP and SSL/TLS connections with custom CA certificate support
|
|
26
|
+
- Zookeeper-based services discovery with weighted load balancing
|
|
27
|
+
- Connection pooling with automatic heartbeat and reconnection
|
|
28
|
+
- Support `group` settings for Dubbo, Compatible with Dubbo 2.x
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python3.8+
|
|
33
|
+
- [kazoo](https://kazoo.readthedocs.io/). --Zookeeper client (required only if using ZkRegister)
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install dubbo-ssl-python
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Dependencies:
|
|
42
|
+
|
|
43
|
+
- `kazoo` -- Zookeeper client for service discovery
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
### Direct Connection (TCP)
|
|
48
|
+
|
|
49
|
+
Connect to a Dubbo service without Zookeeper:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from dubbo_ssl.client import DubboClient
|
|
53
|
+
from dubbo_ssl.codec.encoder import Object
|
|
54
|
+
|
|
55
|
+
client = DubboClient(
|
|
56
|
+
interface="com.example.service.UserService",
|
|
57
|
+
group="default",
|
|
58
|
+
version="1.0.0",
|
|
59
|
+
dubbo_version="2.7.23",
|
|
60
|
+
host="127.0.0.1:20880",
|
|
61
|
+
verify=None # Plain TCP,no SSL
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
request = Object("com.example.service.UserService.dto.UserRequest")
|
|
65
|
+
request["userId"] = "A000021"
|
|
66
|
+
request["type"] = "QUERY"
|
|
67
|
+
|
|
68
|
+
result = client.call(method="getUser",args=request)
|
|
69
|
+
print(request)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### SSL/TLS Connection
|
|
73
|
+
|
|
74
|
+
Connect to a Dubbo service over SSL with custom CA certificate verification:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from dubbo_ssl.client import DubboClient
|
|
78
|
+
|
|
79
|
+
client = DubboClient(
|
|
80
|
+
interface="com.example.service.UserService",
|
|
81
|
+
group="default",
|
|
82
|
+
version="1.0.0",
|
|
83
|
+
dubbo_version="2.7.23",
|
|
84
|
+
host="127.0.0.1:20880",
|
|
85
|
+
verify="/path/to/DubboRootCA.cer" # Custom CA certificate file
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
request = " Hello, SSL Provider."
|
|
89
|
+
result = client.call(method="getUser",args=request)
|
|
90
|
+
print(request)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The `verify` parameter supports three SSL models:
|
|
94
|
+
|
|
95
|
+
| Value | Behavior |
|
|
96
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
97
|
+
| `None` | Plain TCP,no SSL |
|
|
98
|
+
| `"/path/to/ca.crt"` | SSL with custom CA certificate verification, hostname check disabled |
|
|
99
|
+
| `False` | SSL with no server certificate verification (insecure) |
|
|
100
|
+
|
|
101
|
+
### Zookeeper Service discovery
|
|
102
|
+
|
|
103
|
+
Use Zookeeper for automatic service discovery and load balancing:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from dubbo_ssl.client import DubboClient, ZkRegister
|
|
107
|
+
|
|
108
|
+
zk = ZkRegister(hosts="127.0.0.1:2181", application_name="MyAPP")
|
|
109
|
+
client = DubboClient(
|
|
110
|
+
interface="com.example.service.UserService",
|
|
111
|
+
group="default",
|
|
112
|
+
version="1.0.0",
|
|
113
|
+
dubbo_version="2.7.23",
|
|
114
|
+
zk_register=zk
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
request = " Hello, SSL Provider."
|
|
118
|
+
result = client.call(method="getUser",args=request)
|
|
119
|
+
# When done
|
|
120
|
+
zk.close()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Multiple Arguments
|
|
124
|
+
|
|
125
|
+
Pass multiple arguments to a method
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
result = client.call(
|
|
129
|
+
method="query",
|
|
130
|
+
args=["keyword",10,5]
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Custom Context (Attachments)
|
|
135
|
+
|
|
136
|
+
Pass RPC attachment metadata ( key-value pairs sent alongside the request)
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
result = client.call(
|
|
140
|
+
method="getRequest",
|
|
141
|
+
args=["keyword",10,5],
|
|
142
|
+
context={"traceId":"x-123","tenantId":"T001"}
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Timeout
|
|
147
|
+
|
|
148
|
+
Set a request timeout in seconds.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
result = client.call(
|
|
152
|
+
method="getRequest",
|
|
153
|
+
args=["keyword"],
|
|
154
|
+
timeout=5
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Type Mapping
|
|
159
|
+
|
|
160
|
+
### Python -> Java (Request Encoding)
|
|
161
|
+
|
|
162
|
+
| Python Type | Java Type | Hessian2 Code | Note |
|
|
163
|
+
| -------------------------- | ------------------ | -------------------- | ------------------------------------------------ |
|
|
164
|
+
| `bool` | `boolean` | `T`/ `F` | |
|
|
165
|
+
| `int` (within int32 range) | `int` | Compact `I` encoding | -2^31 ~ 2^31-1 |
|
|
166
|
+
| `int` (exceed int32) | `long` | `L` (8-byte) | |
|
|
167
|
+
| `float` | `double` | Compact `D` encoding | Includes 0.0/1.0 short forms |
|
|
168
|
+
| `str` | `java.lang.String` | Short `S` encoding | UTF-8 |
|
|
169
|
+
| `Object` | Java class | `C` + `O` | Class definition + instance |
|
|
170
|
+
| `list` | Java array | Typed list encoding | Elements must be same type; empty list -> `null` |
|
|
171
|
+
| `None` | null | `N` | |
|
|
172
|
+
|
|
173
|
+
### Java -> Python (Response Decoding)
|
|
174
|
+
|
|
175
|
+
| Java Type | Python Type | Hessian2 Code |
|
|
176
|
+
| ---------------------- | ----------- | --------------------------------------- |
|
|
177
|
+
| `boolean` | `boolean` | `T`/ `F` |
|
|
178
|
+
| `int` | `int` | Compact `I` / `I` (4-byte) |
|
|
179
|
+
| `long` | `int` | Compact / `L` (8-byte) |
|
|
180
|
+
| `double` | `float` | Compact `D` / `D` (8-byte IEEE 754) |
|
|
181
|
+
| `java.lang.String` | `str` | Short / chunked `S` / `R` |
|
|
182
|
+
| java object | `dict` | `C` + `O` (field names as keys) |
|
|
183
|
+
| `java.math.BigDecimal` | `float` | Auto-converted from `value` field |
|
|
184
|
+
| `java.math.BigInteger` | `int` | Auto-converted from `value` field |
|
|
185
|
+
| List / array | `list` | Type / untyped, fixed / variable length |
|
|
186
|
+
| Map | `dict` | `M` / `H` (untyped) |
|
|
187
|
+
| `java.util.Date` | `str` | ISO 8601 format string |
|
|
188
|
+
| `null` | `None` | `N` |
|
|
189
|
+
| Circular reference | resolved | `Q` (ref) |
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Apache License 2.0
|
|
@@ -4,20 +4,19 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dubbo-ssl-python"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.6"
|
|
8
|
+
keywords = ["dubbo"]
|
|
8
9
|
authors = [
|
|
9
10
|
{ name="gigi wang", email="gigiwgoo@gmail.com" }
|
|
10
11
|
]
|
|
11
12
|
description = "Python client for Apache Dubbo RPC protocol, with Hessian2 serialization and SSL/TLS support."
|
|
12
13
|
readme = "README.md"
|
|
13
|
-
|
|
14
|
-
license = { file = "LICENSE" }
|
|
14
|
+
license-files = [ "LICENSE" ]
|
|
15
15
|
dependencies = [
|
|
16
16
|
"kazoo>=2.8.0",
|
|
17
17
|
]
|
|
18
18
|
classifiers = [
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
|
-
"License :: OSI Approved :: Apache Software License",
|
|
21
20
|
"Operating System :: OS Independent",
|
|
22
21
|
]
|
|
23
22
|
requires-python = ">=3.8"
|