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.
Files changed (24) hide show
  1. dubbo_ssl_python-0.9.6/PKG-INFO +193 -0
  2. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/__init__.py +1 -1
  3. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/client.py +92 -43
  4. dubbo_ssl_python-0.9.6/dubbo_ssl_python.egg-info/PKG-INFO +193 -0
  5. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/pyproject.toml +3 -4
  6. dubbo_ssl_python-0.9.2/PKG-INFO +0 -395
  7. dubbo_ssl_python-0.9.2/dubbo_ssl_python.egg-info/PKG-INFO +0 -395
  8. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/LICENSE +0 -0
  9. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/README.md +0 -0
  10. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/codec/__init__.py +0 -0
  11. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/codec/decoder.py +0 -0
  12. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/codec/encoder.py +0 -0
  13. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/__init__.py +0 -0
  14. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/constants.py +0 -0
  15. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/exceptions.py +0 -0
  16. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/common/util.py +0 -0
  17. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/connection/__init__.py +0 -0
  18. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl/connection/connections.py +0 -0
  19. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/SOURCES.txt +0 -0
  20. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/dependency_links.txt +0 -0
  21. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/requires.txt +0 -0
  22. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/dubbo_ssl_python.egg-info/top_level.txt +0 -0
  23. {dubbo_ssl_python-0.9.2 → dubbo_ssl_python-0.9.6}/setup.cfg +0 -0
  24. {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.0"
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='search_platform'):
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.hosts.keys():
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
- interfaces = path.split['/'][2]
137
+ interface = path.split('/')[2]
133
138
 
134
139
  providers = self.zk.get_children(path, watch=self._watch_children)
135
- providers = filter(lambda provider: provider['schema'] == 'dubbo', map(parse_url, providers))
136
- if not providers:
137
- logger.debug('no providers found for {}'.format(interfaces))
138
- self.hosts[interfaces] = []
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
- self.hosts[interfaces] = map(lambda provider: provider['host'], providers)
141
- logger.debug('interfaces: {} providers {}'.format(interfaces, self.hosts[interfaces]))
142
-
143
- def get_provider_host(self, interface):
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
- if interface not in self.hosts:
179
+ cache_key = (interface, version, group)
180
+ if cache_key not in self.hosts:
150
181
  self.lock.acquire()
151
182
  try:
152
- if interface not in self.hosts:
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._routing_with_wight(interface)
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
- providers = list(filter(lambda provider: provider['scheme'] == 'dubbo', map(parse_url, providers)))
172
- if not providers:
173
- raise RegisterException('no providers for interface {}'.format(interface))
174
- self._register_consumer(providers)
175
- self.hosts[interface] = list(map(lambda provider: provider['host'], providers))
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 _routing_with_wight(self, interface):
295
+ def _routing_with_weight(self, cache_key):
251
296
  """
252
297
  根据接口名称以及配置好的权重信息获取一个host
253
- :param interface:
298
+ :param cache_key:
254
299
  :return:
255
300
  """
256
- hosts = self.hosts[interface]
301
+ hosts = self.hosts.get(cache_key,[])
257
302
  if not hosts:
258
- raise RegisterException('no providers for interface {}'.format(interface))
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
- hosts_weight = []
265
- for host in hosts:
266
- hosts_weight.append(int(weights.get(host, 100)))
267
-
268
- hit = random.randint(0, sum(hosts_weight) - 1)
269
- for i in range(len(hosts)):
270
- if hit <= sum(hosts_weight[:i + 1]):
271
- return hosts[i]
272
-
273
- raise RegisterException('Error for finding [{}] host with weight.'.format(interface))
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.2"
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"