dubbo-ssl-python 0.9.1__tar.gz → 0.9.5__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.5/PKG-INFO +194 -0
  2. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/client.py +93 -44
  3. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/connection/connections.py +1 -1
  4. dubbo_ssl_python-0.9.5/dubbo_ssl_python.egg-info/PKG-INFO +194 -0
  5. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/pyproject.toml +3 -3
  6. dubbo_ssl_python-0.9.1/PKG-INFO +0 -395
  7. dubbo_ssl_python-0.9.1/dubbo_ssl_python.egg-info/PKG-INFO +0 -395
  8. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/LICENSE +0 -0
  9. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/README.md +0 -0
  10. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/__init__.py +0 -0
  11. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/codec/__init__.py +0 -0
  12. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/codec/decoder.py +0 -0
  13. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/codec/encoder.py +0 -0
  14. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/common/__init__.py +0 -0
  15. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/common/constants.py +0 -0
  16. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/common/exceptions.py +0 -0
  17. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/common/util.py +0 -0
  18. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl/connection/__init__.py +0 -0
  19. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl_python.egg-info/SOURCES.txt +0 -0
  20. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl_python.egg-info/dependency_links.txt +0 -0
  21. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl_python.egg-info/requires.txt +0 -0
  22. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/dubbo_ssl_python.egg-info/top_level.txt +0 -0
  23. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/setup.cfg +0 -0
  24. {dubbo_ssl_python-0.9.1 → dubbo_ssl_python-0.9.5}/tests/test_client.py +0 -0
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: dubbo-ssl-python
3
+ Version: 0.9.5
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
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://iocoo.github.io
8
+ Project-URL: Repository, https://github.com/iocoo/dubbo-ssl-python/
9
+ Keywords: dubbo
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: kazoo>=2.8.0
16
+ Dynamic: license-file
17
+
18
+ # dubbo-ssl-python
19
+
20
+ 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.
21
+ Base on https://github.com/apache/dubbo-python2 and https://github.com/huisongyang/dubbo-python3, support Python3.
22
+
23
+ ## Features
24
+
25
+ - Full Hessian2 serialization/deserialization (primitives, objects, lists, maps, dates, null, circular references)
26
+ - TCP and SSL/TLS connections with custom CA certificate support
27
+ - Zookeeper-based services discovery with weighted load balancing
28
+ - Connection pooling with automatic heartbeat and reconnection
29
+ - Support `group` settings for Dubbo, Compatible with Dubbo 2.x
30
+
31
+ ## Requirements
32
+
33
+ - Python3.8+
34
+ - [kazoo](https://kazoo.readthedocs.io/). --Zookeeper client (required only if using ZkRegister)
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install dubbo-ssl-python
40
+ ```
41
+
42
+ Dependencies:
43
+
44
+ - `kazoo` -- Zookeeper client for service discovery
45
+
46
+ ## Quick start
47
+
48
+ ### Direct Connection (TCP)
49
+
50
+ Connect to a Dubbo service without Zookeeper:
51
+
52
+ ```python
53
+ from dubbo_ssl.client import DubboClient
54
+ from dubbo_ssl.codec.encoder import Object
55
+
56
+ client = DubboClient(
57
+ interface="com.example.service.UserService",
58
+ group="default",
59
+ version="1.0.0",
60
+ dubbo_version="2.7.23",
61
+ host="127.0.0.1:20880",
62
+ verify=None # Plain TCP,no SSL
63
+ )
64
+
65
+ request = Object("com.example.service.UserService.dto.UserRequest")
66
+ request["userId"] = "A000021"
67
+ request["type"] = "QUERY"
68
+
69
+ result = client.call(method="getUser",args=request)
70
+ print(request)
71
+ ```
72
+
73
+ ### SSL/TLS Connection
74
+
75
+ Connect to a Dubbo service over SSL with custom CA certificate verification:
76
+
77
+ ```python
78
+ from dubbo_ssl.client import DubboClient
79
+
80
+ client = DubboClient(
81
+ interface="com.example.service.UserService",
82
+ group="default",
83
+ version="1.0.0",
84
+ dubbo_version="2.7.23",
85
+ host="127.0.0.1:20880",
86
+ verify="/path/to/DubboRootCA.cer" # Custom CA certificate file
87
+ )
88
+
89
+ request = " Hello, SSL Provider."
90
+ result = client.call(method="getUser",args=request)
91
+ print(request)
92
+ ```
93
+
94
+ The `verify` parameter supports three SSL models:
95
+
96
+ | Value | Behavior |
97
+ | ------------------- | -------------------------------------------------------------------- |
98
+ | `None` | Plain TCP,no SSL |
99
+ | `"/path/to/ca.crt"` | SSL with custom CA certificate verification, hostname check disabled |
100
+ | `False` | SSL with no server certificate verification (insecure) |
101
+
102
+ ### Zookeeper Service discovery
103
+
104
+ Use Zookeeper for automatic service discovery and load balancing:
105
+
106
+ ```python
107
+ from dubbo_ssl.client import DubboClient, ZkRegister
108
+
109
+ zk = ZkRegister(hosts="127.0.0.1:2181", application_name="MyAPP")
110
+ client = DubboClient(
111
+ interface="com.example.service.UserService",
112
+ group="default",
113
+ version="1.0.0",
114
+ dubbo_version="2.7.23",
115
+ zk_register=zk
116
+ )
117
+
118
+ request = " Hello, SSL Provider."
119
+ result = client.call(method="getUser",args=request)
120
+ # When done
121
+ zk.close()
122
+ ```
123
+
124
+ ### Multiple Arguments
125
+
126
+ Pass multiple arguments to a method
127
+
128
+ ```python
129
+ result = client.call(
130
+ method="query",
131
+ args=["keyword",10,5]
132
+ )
133
+ ```
134
+
135
+ ### Custom Context (Attachments)
136
+
137
+ Pass RPC attachment metadata ( key-value pairs sent alongside the request)
138
+
139
+ ```python
140
+ result = client.call(
141
+ method="getRequest",
142
+ args=["keyword",10,5],
143
+ context={"traceId":"x-123","tenantId":"T001"}
144
+ )
145
+ ```
146
+
147
+ ### Timeout
148
+
149
+ Set a request timeout in seconds.
150
+
151
+ ```python
152
+ result = client.call(
153
+ method="getRequest",
154
+ args=["keyword"],
155
+ timeout=5
156
+ )
157
+ ```
158
+
159
+ ## Type Mapping
160
+
161
+ ### Python -> Java (Request Encoding)
162
+
163
+ | Python Type | Java Type | Hessian2 Code | Note |
164
+ | -------------------------- | ------------------ | -------------------- | ------------------------------------------------ |
165
+ | `bool` | `boolean` | `T`/ `F` | |
166
+ | `int` (within int32 range) | `int` | Compact `I` encoding | -2^31 ~ 2^31-1 |
167
+ | `int` (exceed int32) | `long` | `L` (8-byte) | |
168
+ | `float` | `double` | Compact `D` encoding | Includes 0.0/1.0 short forms |
169
+ | `str` | `java.lang.String` | Short `S` encoding | UTF-8 |
170
+ | `Object` | Java class | `C` + `O` | Class definition + instance |
171
+ | `list` | Java array | Typed list encoding | Elements must be same type; empty list -> `null` |
172
+ | `None` | null | `N` | |
173
+
174
+ ### Java -> Python (Response Decoding)
175
+
176
+ | Java Type | Python Type | Hessian2 Code |
177
+ | ---------------------- | ----------- | --------------------------------------- |
178
+ | `boolean` | `boolean` | `T`/ `F` |
179
+ | `int` | `int` | Compact `I` / `I` (4-byte) |
180
+ | `long` | `int` | Compact / `L` (8-byte) |
181
+ | `double` | `float` | Compact `D` / `D` (8-byte IEEE 754) |
182
+ | `java.lang.String` | `str` | Short / chunked `S` / `R` |
183
+ | java object | `dict` | `C` + `O` (field names as keys) |
184
+ | `java.math.BigDecimal` | `float` | Auto-converted from `value` field |
185
+ | `java.math.BigInteger` | `int` | Auto-converted from `value` field |
186
+ | List / array | `list` | Type / untyped, fixed / variable length |
187
+ | Map | `dict` | `M` / `H` (untyped) |
188
+ | `java.util.Date` | `str` | ISO 8601 format string |
189
+ | `null` | `None` | `N` |
190
+ | Circular reference | resolved | `Q` (ref) |
191
+
192
+ ## License
193
+
194
+ Apache License 2.0
@@ -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,52 +134,91 @@ 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('interfaces: {} 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_wight(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
  """
170
- 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))
203
+ cache_key = (interface, version, group)
176
204
 
177
- def _get_configurators_from_zk(self, interface):
205
+ providers = self.zk.get_children(path, watch=self._watch_children)
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)
220
+
221
+ def _get_configurats_from_zk(self, interface):
178
222
  """
179
223
  试图从配置中取出权重相关的信息
180
224
  :param interface:
@@ -182,7 +226,7 @@ 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
232
  conf[configurator['host']] = configurator['fields'].get('weight', 100) # 默认100
@@ -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_wight(self, cached_key):
251
296
  """
252
297
  根据接口名称以及配置好的权重信息获取一个host
253
- :param interface:
298
+ :param cached_key:
254
299
  :return:
255
300
  """
256
- hosts = self.hosts[interface]
301
+ hosts = self.hosts.get(cached_key,[])
257
302
  if not hosts:
258
- raise RegisterException('no providers for interface {}'.format(interface))
303
+ raise RegisterException('no providers for {}'.format(cached_key))
259
304
  # 此接口没有权重设置,使用朴素的路由算法
305
+ interface = cached_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 = [(hosts, 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 weight are 0).'.format(cached_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()
@@ -82,7 +82,7 @@ class BaseConnectionPool(object):
82
82
  self._conn_lock.acquire()
83
83
  try:
84
84
  if host not in self._connection_pool:
85
- self._new_connection(host)
85
+ self._new_connection(host,verify)
86
86
  finally:
87
87
  self._conn_lock.release()
88
88
  return self._connection_pool[host]
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: dubbo-ssl-python
3
+ Version: 0.9.5
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
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://iocoo.github.io
8
+ Project-URL: Repository, https://github.com/iocoo/dubbo-ssl-python/
9
+ Keywords: dubbo
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: kazoo>=2.8.0
16
+ Dynamic: license-file
17
+
18
+ # dubbo-ssl-python
19
+
20
+ 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.
21
+ Base on https://github.com/apache/dubbo-python2 and https://github.com/huisongyang/dubbo-python3, support Python3.
22
+
23
+ ## Features
24
+
25
+ - Full Hessian2 serialization/deserialization (primitives, objects, lists, maps, dates, null, circular references)
26
+ - TCP and SSL/TLS connections with custom CA certificate support
27
+ - Zookeeper-based services discovery with weighted load balancing
28
+ - Connection pooling with automatic heartbeat and reconnection
29
+ - Support `group` settings for Dubbo, Compatible with Dubbo 2.x
30
+
31
+ ## Requirements
32
+
33
+ - Python3.8+
34
+ - [kazoo](https://kazoo.readthedocs.io/). --Zookeeper client (required only if using ZkRegister)
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install dubbo-ssl-python
40
+ ```
41
+
42
+ Dependencies:
43
+
44
+ - `kazoo` -- Zookeeper client for service discovery
45
+
46
+ ## Quick start
47
+
48
+ ### Direct Connection (TCP)
49
+
50
+ Connect to a Dubbo service without Zookeeper:
51
+
52
+ ```python
53
+ from dubbo_ssl.client import DubboClient
54
+ from dubbo_ssl.codec.encoder import Object
55
+
56
+ client = DubboClient(
57
+ interface="com.example.service.UserService",
58
+ group="default",
59
+ version="1.0.0",
60
+ dubbo_version="2.7.23",
61
+ host="127.0.0.1:20880",
62
+ verify=None # Plain TCP,no SSL
63
+ )
64
+
65
+ request = Object("com.example.service.UserService.dto.UserRequest")
66
+ request["userId"] = "A000021"
67
+ request["type"] = "QUERY"
68
+
69
+ result = client.call(method="getUser",args=request)
70
+ print(request)
71
+ ```
72
+
73
+ ### SSL/TLS Connection
74
+
75
+ Connect to a Dubbo service over SSL with custom CA certificate verification:
76
+
77
+ ```python
78
+ from dubbo_ssl.client import DubboClient
79
+
80
+ client = DubboClient(
81
+ interface="com.example.service.UserService",
82
+ group="default",
83
+ version="1.0.0",
84
+ dubbo_version="2.7.23",
85
+ host="127.0.0.1:20880",
86
+ verify="/path/to/DubboRootCA.cer" # Custom CA certificate file
87
+ )
88
+
89
+ request = " Hello, SSL Provider."
90
+ result = client.call(method="getUser",args=request)
91
+ print(request)
92
+ ```
93
+
94
+ The `verify` parameter supports three SSL models:
95
+
96
+ | Value | Behavior |
97
+ | ------------------- | -------------------------------------------------------------------- |
98
+ | `None` | Plain TCP,no SSL |
99
+ | `"/path/to/ca.crt"` | SSL with custom CA certificate verification, hostname check disabled |
100
+ | `False` | SSL with no server certificate verification (insecure) |
101
+
102
+ ### Zookeeper Service discovery
103
+
104
+ Use Zookeeper for automatic service discovery and load balancing:
105
+
106
+ ```python
107
+ from dubbo_ssl.client import DubboClient, ZkRegister
108
+
109
+ zk = ZkRegister(hosts="127.0.0.1:2181", application_name="MyAPP")
110
+ client = DubboClient(
111
+ interface="com.example.service.UserService",
112
+ group="default",
113
+ version="1.0.0",
114
+ dubbo_version="2.7.23",
115
+ zk_register=zk
116
+ )
117
+
118
+ request = " Hello, SSL Provider."
119
+ result = client.call(method="getUser",args=request)
120
+ # When done
121
+ zk.close()
122
+ ```
123
+
124
+ ### Multiple Arguments
125
+
126
+ Pass multiple arguments to a method
127
+
128
+ ```python
129
+ result = client.call(
130
+ method="query",
131
+ args=["keyword",10,5]
132
+ )
133
+ ```
134
+
135
+ ### Custom Context (Attachments)
136
+
137
+ Pass RPC attachment metadata ( key-value pairs sent alongside the request)
138
+
139
+ ```python
140
+ result = client.call(
141
+ method="getRequest",
142
+ args=["keyword",10,5],
143
+ context={"traceId":"x-123","tenantId":"T001"}
144
+ )
145
+ ```
146
+
147
+ ### Timeout
148
+
149
+ Set a request timeout in seconds.
150
+
151
+ ```python
152
+ result = client.call(
153
+ method="getRequest",
154
+ args=["keyword"],
155
+ timeout=5
156
+ )
157
+ ```
158
+
159
+ ## Type Mapping
160
+
161
+ ### Python -> Java (Request Encoding)
162
+
163
+ | Python Type | Java Type | Hessian2 Code | Note |
164
+ | -------------------------- | ------------------ | -------------------- | ------------------------------------------------ |
165
+ | `bool` | `boolean` | `T`/ `F` | |
166
+ | `int` (within int32 range) | `int` | Compact `I` encoding | -2^31 ~ 2^31-1 |
167
+ | `int` (exceed int32) | `long` | `L` (8-byte) | |
168
+ | `float` | `double` | Compact `D` encoding | Includes 0.0/1.0 short forms |
169
+ | `str` | `java.lang.String` | Short `S` encoding | UTF-8 |
170
+ | `Object` | Java class | `C` + `O` | Class definition + instance |
171
+ | `list` | Java array | Typed list encoding | Elements must be same type; empty list -> `null` |
172
+ | `None` | null | `N` | |
173
+
174
+ ### Java -> Python (Response Decoding)
175
+
176
+ | Java Type | Python Type | Hessian2 Code |
177
+ | ---------------------- | ----------- | --------------------------------------- |
178
+ | `boolean` | `boolean` | `T`/ `F` |
179
+ | `int` | `int` | Compact `I` / `I` (4-byte) |
180
+ | `long` | `int` | Compact / `L` (8-byte) |
181
+ | `double` | `float` | Compact `D` / `D` (8-byte IEEE 754) |
182
+ | `java.lang.String` | `str` | Short / chunked `S` / `R` |
183
+ | java object | `dict` | `C` + `O` (field names as keys) |
184
+ | `java.math.BigDecimal` | `float` | Auto-converted from `value` field |
185
+ | `java.math.BigInteger` | `int` | Auto-converted from `value` field |
186
+ | List / array | `list` | Type / untyped, fixed / variable length |
187
+ | Map | `dict` | `M` / `H` (untyped) |
188
+ | `java.util.Date` | `str` | ISO 8601 format string |
189
+ | `null` | `None` | `N` |
190
+ | Circular reference | resolved | `Q` (ref) |
191
+
192
+ ## License
193
+
194
+ Apache License 2.0
@@ -4,20 +4,20 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dubbo-ssl-python"
7
- version = "0.9.1"
7
+ version = "0.9.5"
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
 
14
- license = { file = "LICENSE" }
15
+ license = "Apache-2.0"
15
16
  dependencies = [
16
17
  "kazoo>=2.8.0",
17
18
  ]
18
19
  classifiers = [
19
20
  "Programming Language :: Python :: 3",
20
- "License :: OSI Approved :: Apache Software License",
21
21
  "Operating System :: OS Independent",
22
22
  ]
23
23
  requires-python = ">=3.8"