diff --git a/.asf.yaml b/.asf.yaml
index 7ebda11..8d84e69 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -3,32 +3,3 @@ notifications:
issues: notifications@dubbo.apache.org
pullrequests: notifications@dubbo.apache.org
jira_options: link label link label
-
-
-github:
- homepage: https://dubbo.apache.org/
- description: "The python Implementation For Apache Dubbo."
- features:
- # Enable wiki for documentation
- wiki: false
- # Enable issue management
- issues: false
- # Enable projects for project management boards
- projects: false
- collaborators:
- - cnzakii
- enabled_merge_buttons:
- # enable squash button:
- squash: true
- # enable merge button:
- merge: false
- # disable rebase button:
- rebase: false
- labels:
- - dubbo
- - python
- - rpc
- - framework
- - http2
- - h2
- - grpc
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
deleted file mode 100644
index 060b8c4..0000000
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ /dev/null
@@ -1,22 +0,0 @@
-## What is the purpose of the change
-
-
-ISSUE: [#issue_number](issue_link)
-
-
-## Brief changelog
-
-
-## Verifying this change
-
-
-
-
-## Checklist
-- [x] Make sure there is a [GitHub_issue](https://github.com/apache/dubbo/issues) field for the change (usually before you start working on it). Trivial changes like typos do not require a GitHub issue. Your pull request should address just this issue, without pulling in other changes - one PR resolves one issue.
-- [ ] Each commit in the pull request should have a meaningful subject line and body.
-- [ ] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
-- [ ] Write necessary unit-test to verify your logic correction, more mock a little better when cross module dependency exist.
-- [ ] Add some description to [dubbo-website](https://github.com/apache/dubbo-website) project if you are requesting to add a feature.
-- [ ] GitHub Actions works fine on your own branch.
-- [ ] If this contribution is large, please follow the [Software Donation Guide](https://github.com/apache/dubbo/wiki/Software-donation-guide).
diff --git a/.github/workflows/license-check.yaml b/.github/workflows/license-check.yaml
deleted file mode 100644
index abf3754..0000000
--- a/.github/workflows/license-check.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: License Template Check
-
-on: [push, pull_request]
-
-
-jobs:
- license-check:
- runs-on: ubuntu-latest
- permissions: write-all
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: '11'
- - name: Check License Header
- run: "scripts/rat.sh"
diff --git a/.github/workflows/test-suite.yaml b/.github/workflows/test-suite.yaml
deleted file mode 100644
index cc2bb7c..0000000
--- a/.github/workflows/test-suite.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Check and test
-on: [push, pull_request]
-
-
-jobs:
- tests:
- name: "Python ${{ matrix.python-version }}"
- runs-on: "ubuntu-latest"
-
- strategy:
- matrix:
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
-
- steps:
- - uses: "actions/checkout@v4"
- - uses: "actions/setup-python@v5"
- with:
- python-version: "${{ matrix.python-version }}"
-
- - name: "Install dependencies"
- run: "scripts/install-dev.sh"
-
- - name: "Check Code Style"
- run: "scripts/check.sh"
-
- - name: "Build package"
- run: "scripts/build.sh"
-
- - name: "Run tests and report coverage"
- run: "scripts/test.sh"
diff --git a/.gitignore b/.gitignore
index 1daa1a7..3cb9027 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,66 +1,3 @@
-# Compiler
-.idea/
-.vscode/
-
-# Byte-compiled / optimized / DLL files / logs
-__pycache__/
-*.py[cod]
-*$py.class
-*.log
-
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
+*.pyc
+.idea
+*.log
\ No newline at end of file
diff --git a/.license-ignore b/.license-ignore
deleted file mode 100644
index f8980ab..0000000
--- a/.license-ignore
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file is used to ignore files and directories from the license scan
-# Note: these patterns are applied to single files or directories, not full paths
-
-# file extensions
-.*md
-.*proto
-.*_pb2.py
-
-# files
-.asf.yaml
-LICENSE
-NOTICE
-requirements-dev.txt
-
-# directories
-.vscode
-.idea
-.github
-.gitignore
-.license-ignore
-docs
\ No newline at end of file
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 0000000..8758df3
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,5 @@
+# Original Author and First Commit
+* Joe Cao, [@JoeCao](https://github.com/joecao)
+
+# Contributors (alpha by username)
+* jingpeicomp [@jingpeicomp](https://github.com/jingpeicomp)
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..d4a0e7f
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include README.md
+include AUTHORS.md
+include LICENSE
+include version.txt
\ No newline at end of file
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 2744a49..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,5 +0,0 @@
-Apache Dubbo
-Copyright 2018-2024 The Apache Software Foundation
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
index 1639f04..d880bf0 100644
--- a/README.md
+++ b/README.md
@@ -1,135 +1,52 @@
-# Apache Dubbo for Python
-
-
-
-
-
-
-
-
-
-
-
-Apache Dubbo is an easy-to-use, high-performance WEB and RPC framework with builtin service discovery, traffic management, observability, security features, tools and best practices for building enterprise-level microservices.
-
-Dubbo-python is a Python implementation of the [triple protocol](https://dubbo.apache.org/zh-cn/overview/reference/protocols/triple-spec/) (a protocol fully compatible with gRPC and friendly to HTTP) and various features designed by Dubbo for constructing microservice architectures.
-
-Visit [the official website](https://dubbo.apache.org/) for more information.
-
-### 🚧 Early-Stage Project 🚧
-
-> **Disclaimer:** This project is in the early stages of development. Features are subject to change, and some components may not be fully stable. Contributions and feedback are welcome as the project evolves.
-
-
-## Architecture
-
-
-## Features
-
-- **Service Discovery**: Zookeeper
-- **Load Balance**: Random, CPU
-- **RPC Protocols**: Triple(gRPC compatible and HTTP-friendly)
-- **Transport**: asyncio(uvloop)
-- **Serialization**: Customizable(protobuf, json...)
-
-
-## Installation
-
-Before you start, make sure you have **`python 3.9+`** installed.
-
-1. Install Directly
-
- ```sh
- pip install apache-dubbo
- ```
-2. Install from source
-
- ```sh
- git clone https://github.com/apache/dubbo-python.git
- cd dubbo-python && pip install .
- ```
-
-## Getting started
-
-Get up and running with Dubbo-Python in just 5 minutes by following our [Quick Start Guide](https://github.com/apache/dubbo-python/tree/main/samples).
-
-It's as simple as the code snippet below. With just a few lines of code, you can launch a fully functional point-to-point RPC service:
-
-1. Build and start the server
-
- ```python
- import dubbo
- from dubbo.configs import ServiceConfig
- from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-
-
- class UnaryServiceServicer:
- def say_hello(self, message: bytes) -> bytes:
- print(f"Received message from client: {message}")
- return b"Hello from server"
-
-
- def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(
- method=UnaryServiceServicer().say_hello, method_name="unary"
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.HelloWorld",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
- if __name__ == "__main__":
- # build service config
- service_handler = build_service_handler()
- service_config = ServiceConfig(
- service_handler=service_handler, host="127.0.0.1", port=50051
- )
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
- ```
-
-1. Build and start the Client
-
- ```python
- import dubbo
- from dubbo.configs import ReferenceConfig
-
-
- class UnaryServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(method_name="unary")
-
- def say_hello(self, message: bytes) -> bytes:
- return self.unary(message)
-
-
- if __name__ == "__main__":
- # Create a client
- reference_config = ReferenceConfig.from_url(
- "tri://127.0.0.1:50051/org.apache.dubbo.samples.HelloWorld"
- )
- dubbo_client = dubbo.Client(reference_config)
- unary_service_stub = UnaryServiceStub(dubbo_client)
-
- # Call the remote method
- result = unary_service_stub.say_hello(b"Hello from client")
- print(result)
-
- ```
-
-## Contributing
-
-We are excited to welcome contributions to the Dubbo-Python project! Whether you are fixing bugs, adding new features, or improving documentation, your input is highly valued.
-
-To ensure a smooth collaboration, please review our [Contributing Guide](./docs/CONTRIBUTING.md) for detailed instructions on how to get started, adhere to coding standards, and submit your contributions effectively.
-
-## License
-
-Apache Dubbo-python software is licensed under the Apache License Version 2.0. See
-the [LICENSE](https://github.com/apache/dubbo-python/blob/main/LICENSE) file for details.
+## Python Client For Apache Dubbo
+## Achieve load balancing on the client side、auto discovery service function with Zookeeper
+### Python calls the Dubbo interface's jsonrpc protocol
+Please use dubbo-rpc-jsonrpc and configure protocol in Dubbo for jsonrpc protocol
+*Reference* [https://github.com/apache/incubator-dubbo-rpc-jsonrpc](https://github.com/apache/incubator-dubbo-rpc-jsonrpc)
+
+### Installation
+
+Download code
+python setup.py install
+pip install
+pip install dubbo-client==1.0.0b5
+Git install
+pip install git+[http://git.dev.qianmi.com/tda/dubbo-client-py.git@1.0.0b5](http://git.dev.qianmi.com/tda/dubbo-client-py.git@1.0.0b5)
+or
+pip install git+[https://github.com/qianmiopen/dubbo-client-py.git@1.0.0b5](https://github.com/qianmiopen/dubbo-client-py.git@1.0.0b5)
+
+### Load balancing on the client side, service discovery
+
+Get the registration information of the service through the zookeeper of the registry.
+Dubbo-client-py supports configuring multiple zookeeper service addresses.
+"host":"192.168.1.183:2181,192.168.1.184:2181,192.168.1.185:2181"
+Then the load balancing algorithm is implemented by proxy, and the server is called.
+Support Version and Group settings.
+### Example
+ config = ApplicationConfig('test_rpclib')
+ service_interface = 'com.ofpay.demo.api.UserProvider'
+ #Contains a connection to zookeeper, which needs caching.
+ registry = ZookeeperRegistry('192.168.59.103:2181', config)
+ user_provider = DubboClient(service_interface, registry, version='1.0')
+ for i in range(1000):
+ try:
+ print user_provider.getUser('A003')
+ print user_provider.queryUser(
+ {u'age': 18, u'time': 1428463514153, u'sex': u'MAN', u'id': u'A003', u'name': u'zhangsan'})
+ print user_provider.queryAll()
+ print user_provider.isLimit('MAN', 'Joe')
+ print user_provider('getUser', 'A005')
+
+ except DubboClientError, client_error:
+ print client_error
+ time.sleep(5)
+
+### TODO
+Optimize performance, minimize the impact of service upper and lower lines.
+Support Retry parameters
+Support weight call
+Unit test coverage
+### Licenses
+Apache License
+### Thanks
+Thank @jingpeicomp for being a Guinea pig. It has been running normally for several months in the production environment. Thank you!
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
deleted file mode 100755
index e5f24b0..0000000
--- a/docs/CONTRIBUTING.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# CONTRIBUTING
-
-## Contributing to Dubbo Python
-
-Dubbo Python is released under the non-restrictive Apache 2.0 licenses and follows a very standard Github development process, using Github tracker for issues and merging pull requests into main. Contributions of all form to this repository is acceptable, as long as it follows the prescribed community guidelines enumerated below.
-
-### Sign the Contributor License Agreement
-
-Before we accept a non-trivial patch or pull request (PRs), we will need you to sign the Contributor License Agreement. Signing the contributors' agreement does not grant anyone commits rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors may get invited to join the core team that will grant them privileges to merge existing PRs.
-
-### Contact
-
-### Mailing list
-
-The mailing list is the recommended way of pursuing a discussion on almost anything related to Dubbo. Please refer to this [guide](https://github.com/apache/dubbo/wiki/Mailing-list-subscription-guide) for detailed documentation on how to subscribe.
-
-- [dev@dubbo.apache.org](mailto:dev-subscribe@dubbo.apache.org): the developer mailing list where you can ask questions about an issue you may have encountered while working with Dubbo.
-- [commits@dubbo.apache.org](mailto:commits-subscribe@dubbo.apache.org): the commit updates will get broadcasted on this mailing list. You can subscribe to it, should you be interested in following Dubbo's development.
-- [notifications@dubbo.apache.org](mailto:notifications-subscribe@dubbo.apache.org): all the Github [issue](https://github.com/apache/dubbo/issues) updates and [pull request](https://github.com/apache/dubbo/pulls) updates will be sent to this mailing list.
-
-### Reporting issue
-
-Please follow the [template](https://github.com/apache/dubbo/issues/new?template=dubbo-issue-report-template.md) for reporting any issues.
-
-NOTE: Issues related to Dubbo Python should be submitted in the [Dubbo](https://github.com/apache/dubbo/issues) repository, and the **Apache Dubbo Component** option should be set to `Python SDK`.
-
-### Code Conventions
-
-Our code style almost fully adheres to the [**PEP 8 style guide**](https://peps.python.org/pep-0008/), with the following adjustments and new constraints:
-
-1. We have relaxed the **Maximum Line Length** limit from 79 to **120**.
-2. For **Documentation Strings**, or **comment style**, we follow the `reStructuredText` format.
-3. ...
-
-### Contribution flow
-
-A rough outline of an ideal contributors' workflow is as follows:
-
-- Fork the current repository
-- Create a topic branch from where to base the contribution. Mostly, it's the main branch.
-- Make commits of logical units.
-- Make sure the commit messages are in the proper format (see below).
-- Push changes in a topic branch to your forked repository.
-- Follow the checklist in the [pull request template](https://github.com/apache/dubbo-python/blob/main/.github/PULL_REQUEST_TEMPLATE.md)
-- Before sending out the pull request, please sync your forked repository with the remote repository to ensure that your PR is elegant, concise. Reference the guide below:
-
-```
-git remote add upstream git@github.com:apache/dubbo-python.git
-git fetch upstream
-git rebase upstream/master
-git checkout -b your_awesome_patch
-... add some work
-git push origin your_awesome_patch
-
-```
-
-- Submit a pull request to apache/dubbo-python and wait for the reply.
-
-Thanks for contributing!
-
-
-
-### Development & Testing
-
-Before you start working on development, please install the necessary dependencies for Dubbo-Python using the following command:
-
-```shell
-pip install -r requirements-dev.txt
-```
-
-Our project uses a `src` layout, and packaging is required before running tests. We strongly recommend using the **editable installation mode** for packaging and testing:
-
-```shell
-pip install -e .
-```
-
-
-
-### Code style
-
-We use **ruff** as the linter and code formatter for Dubbo-Python, and **Mypy** as the static type checker.
-
-Therefore, when developing, you should install both tools:
-
-- ruff: [https://github.com/astral-sh/ruff](https://github.com/astral-sh/ruff)
-- Mypy: [https://github.com/python/mypy](https://github.com/python/mypy)
-
-We have already set up the configurations for ruff and Mypy in the `pyproject.toml` file. You only need to specify the configuration path (`pyproject.toml`) when using them.
-
-1. Code Formatting
-
- By default, ruff will look for the `pyproject.toml` file in the current directory and its parent directories and load its configuration.
-
- ```shell
- # Default
- ruff format
-
- # Specify configuration file
- ruff format --config pyproject.toml
- ```
-
-2. Code Linting
-
- ```shell
- # Just check
- ruff check
-
- # Check and fix
- ruff check --fix
- ```
-
-3. Static Type Checking
-
- Mypy will also automatically look for the `pyproject.toml` file and load its configuration.
-
- ```shell
- # Default
- mypy
-
- # Specify configuration file
- mypy --config-file pyproject.toml
- ```
\ No newline at end of file
diff --git a/docs/img/Architecture.svg b/docs/img/Architecture.svg
deleted file mode 100644
index dcb710a..0000000
--- a/docs/img/Architecture.svg
+++ /dev/null
@@ -1,2 +0,0 @@
-
\ No newline at end of file
diff --git a/dubbo_client/__init__.py b/dubbo_client/__init__.py
new file mode 100644
index 0000000..c5d518f
--- /dev/null
+++ b/dubbo_client/__init__.py
@@ -0,0 +1,31 @@
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+
+from rpclib import (
+ DubboClient,
+)
+from rpcerror import *
+
+from registry import (
+ Registry,
+ ZookeeperRegistry,
+ MulticastRegistry
+)
+from config import (
+ ApplicationConfig,
+)
diff --git a/dubbo_client/common.py b/dubbo_client/common.py
new file mode 100644
index 0000000..9d6e45f
--- /dev/null
+++ b/dubbo_client/common.py
@@ -0,0 +1,96 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+from urlparse import urlparse, parse_qsl
+
+
+class ServiceURL(object):
+ protocol = 'jsonrpc'
+ location = '' # ip+port
+ path = '' # like /com.qianmi.dubbo.UserProvider
+ ip = '127.0.0.1'
+ port = '9090'
+ version = ''
+ group = ''
+ disabled = False
+ weight = 100
+ has_disable_value = False
+ has_weight_value = False
+
+ def __init__(self, url):
+ result = urlparse(url)
+ self.protocol = result[0]
+ self.location = result[1]
+ self.path = result[2]
+ if self.location.find(':') > -1:
+ self.ip, self.port = result[1].split(':')
+ params = parse_qsl(result[4])
+ for key, value in params:
+ # url has a default.timeout property, but it can not add in python object
+ # so keep the last one
+ pos = key.find('.')
+ if pos > -1:
+ key = key[pos + 1:]
+ # print key
+ if key == 'disabled':
+ value = value.lower() == 'true' if value else False
+ self.has_disable_value = True
+ elif key == 'weight':
+ value = int(value) if value else 100
+ self.has_weight_value = True
+ self.__dict__[key] = value
+
+ def __repr__(self):
+ return str(self.__dict__)
+
+ def init_default_config(self):
+ """
+ 恢复默认设置,dubbo配置是覆盖形式,如果恢复默认值,那么configurators下的配置会被清空
+ :return:
+ """
+ self.disabled = False
+ self.weight = 100
+
+ def set_config(self, url_list):
+ """
+ 设置自定义dubbo配置
+ :param url_list:
+ :return:
+ """
+ if not url_list:
+ return
+
+ param_list = []
+ for configuration_url in url_list:
+ result = urlparse(configuration_url)
+ params = parse_qsl(result[4])
+ param_list.extend(params)
+ has_disable_value = False
+ has_weight_value = False
+ for key, value in param_list:
+ if key == 'disabled':
+ self.disabled = value.lower() == 'true' if value else False
+ has_disable_value = True
+ if key == 'weight':
+ self.weight = int(value) if value else 100
+ has_weight_value = True
+
+ if not has_disable_value:
+ self.disabled = False
+ if not has_weight_value:
+ self.weight = 100
diff --git a/dubbo_client/config.py b/dubbo_client/config.py
new file mode 100644
index 0000000..df67752
--- /dev/null
+++ b/dubbo_client/config.py
@@ -0,0 +1,56 @@
+# coding=utf-8
+
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+
+
+
+class ApplicationConfig(object):
+ # 应用名称
+ name = 'default'
+ # 模块版本
+ version = '1.0.0'
+ # 应用负责人
+ owner = ''
+ # 组织名(BU或部门)
+ organization = ''
+ # 分层
+ architecture = 'web'
+ # 环境,如:dev/test/run
+ environment = 'run'
+
+ def __init__(self, name, **kwargs):
+ self.name = name
+ object_property = dir(ApplicationConfig)
+ for key, value in kwargs.items():
+ if key in object_property:
+ setattr(self, key, value)
+
+ def __str__(self):
+ return 'ApplicationConfig is {0}'.format(",".join(k + ':' + v for k, v in vars(self).iteritems()))
+
+
+class ReferenceConfig(object):
+ registry = None
+ interface = ''
+ version = ''
+
+
+if __name__ == '__main__':
+ application_config = ApplicationConfig('test_app', version='2.0.0', owner='caozupeng', error='ssd')
+ print application_config
diff --git a/dubbo_client/registry.py b/dubbo_client/registry.py
new file mode 100644
index 0000000..2459455
--- /dev/null
+++ b/dubbo_client/registry.py
@@ -0,0 +1,436 @@
+# coding=utf-8
+
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+
+import logging.config
+import os
+import os.path
+import random
+import socket
+import struct
+import threading
+import time
+import urllib
+from threading import Thread
+
+from kazoo.client import KazooClient
+from kazoo.protocol.states import KazooState
+
+from dubbo_client.common import ServiceURL
+from dubbo_client.config import ApplicationConfig
+from dubbo_client.rpcerror import NoProvider
+
+
+# 创建一个logger
+if os.path.exists('logging.conf'):
+ logging.config.fileConfig('logging.conf')
+else:
+ logging.basicConfig()
+logger = logging.getLogger('dubbo')
+
+
+class Registry(object):
+ """
+ 所有注册过的服务端将在这里
+ interface=com.ofpay.demo.DemoService
+ location = ip:port/url 比如 172.19.20.111:38080/com.ofpay.demo.DemoService2
+ providername = servicename|version|group
+ dict 格式为{interface:{providername:{ip+port:service_url}}}
+
+ """
+
+ def __init__(self):
+ self._service_providers = {}
+ self._mutex = threading.Lock()
+
+ def _do_event(self, event):
+ """
+ protect方法,处理回调,留给子类实现
+ :param event:
+ :return:
+ """
+ pass
+
+ def _do_config_event(self, event):
+ """
+ protect方法,处理管理台的禁用,倍权,半权等操作
+ :param event:
+ :return:
+ """
+ pass
+
+ def register(self, interface, **kwargs):
+ """
+ 客户端注册到注册中心,亮出自己的身份
+ :param interface:
+ :param kwargs:
+ :return:
+ """
+ pass
+
+ def subscribe(self, interface, **kwargs):
+ """
+ 监听注册中心的服务上下线
+ :param provide_name: 类似com.ofpay.demo.api.UserProvider这样的服务名
+ :param kwargs: version , group
+ :return: 无返回
+ """
+ pass
+
+ def get_providers(self, interface, **kwargs):
+ """
+ 获取已经注册的服务URL对象
+ :param interface: com.ofpay.demo.api.UserProvider
+ :param default:
+ :return: 返回一个dict的服务集合
+ """
+ group = kwargs.get('group', '')
+ version = kwargs.get('version', '')
+ key = self._to_key(interface, version, group)
+ second = self._service_provides.get(interface, {})
+ return second.get(key, {})
+
+ def get_random_provider(self, interface, **kwargs):
+ """
+ 根据权重和是否禁用获取一个provider
+ :param interface:
+ :param kwargs:
+ :return:
+ """
+ group = kwargs.get('group', '')
+ version = kwargs.get('version', '')
+ key = self._to_key(interface, version, group)
+ second_dict = self._service_providers.get(interface, {})
+ service_url_list = [service_url for service_url in second_dict.get(key, {}).itervalues() if
+ not service_url.disabled and service_url.weight > 0]
+ if not service_url_list:
+ raise NoProvider('can not find provider', interface)
+
+ total_weight = 0
+ same_weight = True
+ last_service_url = None
+ for service_url in service_url_list:
+ total_weight += service_url.weight
+ if same_weight and last_service_url and last_service_url.weight != service_url.weight:
+ same_weight = False
+ last_service_url = service_url
+
+ if total_weight > 0 and not same_weight:
+ offset = random.randint(0, total_weight - 1)
+ for service_url in service_url_list:
+ offset -= service_url.weight
+ if offset < 0:
+ return service_url
+
+ return random.choice(service_url_list)
+
+ def event_listener(self, event):
+ """
+ node provides上下线的监听回调函数
+ :param event:
+ :return:
+ """
+ self._do_event(event)
+
+ def configuration_listener(self, event):
+ """
+ 监听
+ :param event:
+ :return:
+ """
+ self._do_config_event(event)
+
+ def _to_key(self, interface, version, group):
+ """
+ 计算存放在内存中的服务的key,以接口、版本、分组计算
+ :param interface: 接口 类似com.ofpay.demo.DemoProvider
+ :param version: 版本 1.0
+ :param group: 分组 product
+ :return: key 字符串
+ """
+ return '{0}|{1}|{2}'.format(interface, version, group)
+
+ def _add_node(self, interface, service_url):
+ key = self._to_key(service_url.interface, service_url.version, service_url.group)
+ second_dict = self._service_providers.get(interface)
+ if second_dict:
+ # 获取最内层的nest的dict
+ inner_dict = second_dict.get(key)
+ if inner_dict:
+ inner_dict[service_url.location] = service_url
+ else:
+ second_dict[key] = {service_url.location: service_url}
+ else:
+ # create the second dict
+ self._service_providers[interface] = {key: {service_url.location: service_url}}
+
+ def _remove_node(self, interface, service_url):
+ key = self._to_key(service_url.interface, service_url.version, service_url.group)
+ second_dict = self._service_providers.get(interface)
+ if second_dict:
+ inner_dict = second_dict.get(key)
+ if inner_dict:
+ del inner_dict[service_url.location]
+
+ def _compare_swap_nodes(self, interface, nodes):
+ """
+ 比较,替换现有内存中的节点信息,节点url类似如下
+ jsonrpc://192.168.2.1:38080/com.ofpay.demo.api.UserProvider?
+ anyhost=true&application=demo-provider&default.timeout=10000&dubbo=2.4.10&
+ environment=product&interface=com.ofpay.demo.api.UserProvider&
+ methods=getUser,queryAll,queryUser,isLimit&owner=wenwu&pid=61578&
+ side=provider×tamp=1428904600188
+ 首先将url转为ServiceUrl对象,然保持到缓存中
+ :param nodes: 节点列表
+ :return: 不需要返回
+ """
+ if self._mutex.acquire():
+ # 存在并发问题,需要线程锁
+ try:
+ # 如果已经存在,首先删除原有的服务的集合
+ if interface in self._service_providers:
+ del self._service_providers[interface]
+ logger.debug("delete node {0}".format(interface))
+ for child_node in nodes:
+ node = urllib.unquote(child_node).decode('utf8')
+ logger.debug('child of node is {0}'.format(node))
+ if node.startswith('jsonrpc'):
+ service_url = ServiceURL(node)
+ self._add_node(interface, service_url)
+ except Exception as e:
+ logger.warn('swap json-rpc provider error %s', str(e))
+ finally:
+ self._mutex.release()
+
+ def _set_provider_configuration(self, interface, nodes):
+ """
+ 设置provider配置
+ :param interface:
+ :param nodes:
+ :return:
+ """
+ if not nodes:
+ return
+ try:
+ configuration_dict = {}
+ for _child_node in nodes:
+ _node = urllib.unquote(_child_node).decode('utf8')
+ if _node.startswith('override'):
+ service_url = ServiceURL(_node)
+ key = self._to_key(interface, service_url.version, service_url.group)
+
+ if key not in configuration_dict:
+ configuration_dict[key] = {}
+ if service_url.location not in configuration_dict[key]:
+ configuration_dict[key][service_url.location] = []
+ configuration_dict[key][service_url.location].append(_node)
+
+ if interface in self._service_providers:
+ provider_dict = self._service_providers.get(interface)
+ for provider_key, second_dict in provider_dict.iteritems():
+ for service_location, service_url in second_dict.iteritems():
+ configuration_service_urls = configuration_dict.get(provider_key, {}).get(service_location)
+ if not configuration_service_urls:
+ service_url.init_default_config()
+ else:
+ service_url.set_config(configuration_service_urls)
+
+ except Exception as e:
+ logger.warn('set provider configuration error %s', str(e))
+
+
+class ZookeeperRegistry(Registry):
+ _app_config = ApplicationConfig('default_app')
+ _connect_state = 'UNCONNECT'
+
+ def __init__(self, zk_hosts, application_config=None):
+ Registry.__init__(self)
+ if application_config:
+ self._app_config = application_config
+ self.__zk = KazooClient(hosts=zk_hosts)
+ self.__zk.add_listener(self.__state_listener)
+ self.__zk.start()
+
+ def __state_listener(self, state):
+ if state == KazooState.LOST:
+ # Register somewhere that the session was lost
+ self._connect_state = state
+ elif state == KazooState.SUSPENDED:
+ # Handle being disconnected from Zookeeper
+ # print 'disconnect from zookeeper'
+ self._connect_state = state
+ else:
+ # Handle being connected/reconnected to Zookeeper
+ # print 'connected'
+ self._connect_state = state
+
+ def __unquote(self, origin_nodes):
+ return (urllib.unquote(child_node).decode('utf8') for child_node in origin_nodes if child_node)
+
+ def _do_event(self, event):
+ # event.path 是类似/dubbo/com.ofpay.demo.api.UserProvider/providers 这样的
+ # 如果要删除,必须先把/dubbo/和最后的/providers去掉
+ # 将zookeeper中查询到的服务节点列表加入到一个dict中
+ # zookeeper中保持的节点url类似如下
+ logger.info("receive event is {0}, event state is {1}".format(event, event.state))
+ provide_name = event.path[7:event.path.rfind('/')]
+ if event.state in ['CONNECTED', 'DELETED']:
+ children = self.__zk.get_children(event.path, watch=self.event_listener)
+ self._compare_swap_nodes(provide_name, self.__unquote(children))
+ configurators_nodes = self._get_provider_configuration(provide_name)
+ self._set_provider_configuration(provide_name, configurators_nodes)
+ print self._service_providers
+
+ def _do_config_event(self, event):
+ """
+ zk的目录路径为 /dubbo/com.qianmi.pc.api.es.item.EsGoodsQueryProvider/configurators
+ :param event:
+ :return:
+ """
+ logger.info("receive config event is {0}, event state is {1}".format(event, event.state))
+ provide_name = event.path[7:event.path.rfind('/')]
+ configurators_nodes = self._get_provider_configuration(provide_name)
+ self._set_provider_configuration(provide_name, configurators_nodes)
+
+ print self._service_providers
+
+ def register(self, interface, **kwargs):
+ ip = self.__zk._connection._socket.getsockname()[0]
+ params = {
+ 'interface': interface,
+ 'application': self._app_config.name,
+ 'application.version': self._app_config.version,
+ 'category': 'consumer',
+ 'dubbo': 'dubbo-client-py-1.0.0',
+ 'environment': self._app_config.environment,
+ 'method': '',
+ 'owner': self._app_config.owner,
+ 'side': 'consumer',
+ 'pid': os.getpid(),
+ 'version': '1.0'
+ }
+ url = 'consumer://{0}/{1}?{2}'.format(ip, interface, urllib.urlencode(params))
+ # print urllib.quote(url, safe='')
+
+ consumer_path = '{0}/{1}/{2}'.format('dubbo', interface, 'consumers')
+ self.__zk.ensure_path(consumer_path)
+ self.__zk.create(consumer_path + '/' + urllib.quote(url, safe=''), ephemeral=True)
+
+ def subscribe(self, interface, **kwargs):
+ """
+ 监听注册中心的服务上下线
+ :param interface: 类似com.ofpay.demo.api.UserProvider这样的服务名
+ :return: 无返回
+ """
+ version = kwargs.get('version', '')
+ group = kwargs.get('group', '')
+ providers_children = self.__zk.get_children('{0}/{1}/{2}'.format('dubbo', interface, 'providers'),
+ watch=self.event_listener)
+ logger.debug("watch node is {0}".format(providers_children))
+ self.__zk.get_children('{0}/{1}/{2}'.format('dubbo', interface, 'configurators'),
+ watch=self.configuration_listener)
+ # 全部重新添加
+ self._compare_swap_nodes(interface, self.__unquote(providers_children))
+
+ configurators_nodes = self._get_provider_configuration(interface)
+ self._set_provider_configuration(interface, configurators_nodes)
+
+ def _get_provider_configuration(self, interface):
+ """
+ 获取dubbo自定义配置数据,从"/dubbo/{interface}/configurators" 路径下获取配置
+ :param interface:
+ :return:
+ """
+ try:
+ configurators_nodes = self.__zk.get_children('{0}/{1}/{2}'.format('dubbo', interface, 'configurators'),
+ watch=self.configuration_listener)
+ logger.debug("configurators node is {0}".format(configurators_nodes))
+ return self.__unquote(configurators_nodes)
+ except Exception as e:
+ logger.warn("get provider %s configuration error %s", interface, str(e))
+
+
+class MulticastRegistry(Registry):
+ class _Loop(Thread):
+ def __init__(self, address, callback):
+ Thread.__init__(self)
+ self.multicast_group, self.multicast_port = address.split(':')
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+ # in osx we should use SO_REUSEPORT instead of SO_REUSEADDRESS
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ self.sock.bind(('', int(self.multicast_port)))
+ mreq = struct.pack("4sl", socket.inet_aton(self.multicast_group), socket.INADDR_ANY)
+ self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+ self.callback = callback
+
+ def run(self):
+ while True:
+ event = self.sock.recv(10240)
+ print event
+ self.callback(event.rstrip())
+
+ def set_mssage(self, msg):
+ self.sock.sendto(msg, (self.multicast_group, int(self.multicast_port)))
+
+ def __init__(self, address, application_config=None):
+ Registry.__init__(self)
+ if application_config:
+ self._app_config = application_config
+ self.event_loop = self._Loop(address, self.event_listener)
+ self.event_loop.setDaemon(True)
+ self.event_loop.start()
+
+ def _do_event(self, event):
+ if event.startswith('register'):
+ url = event[9:]
+ if url.startswith('jsonrpc'):
+ service_provide = ServiceURL(url)
+ self._add_node(service_provide.interface, service_provide)
+ if event.startswith('unregister'):
+ url = event[11:]
+ if url.startswith('jsonrpc'):
+ service_provide = ServiceURL(url)
+ self._remove_node(service_provide.interface, service_provide)
+
+
+if __name__ == '__main__':
+ zk = KazooClient(hosts='192.168.59.103:2181')
+ zk.start()
+ parent_node = '{0}/{1}/{2}'.format('dubbo', 'com.ofpay.demo.api.UserProvider', '')
+ nodes = zk.get_children(parent_node)
+ for child_node in nodes:
+ node = urllib.unquote(child_node).decode('utf8')
+ print node
+ configurators_node = '{0}/{1}/{2}'.format('dubbo', 'com.ofpay.demo.api.UserProvider', 'configurators')
+ nodes = zk.get_children(configurators_node)
+ for child_node in nodes:
+ node = urllib.unquote(child_node).decode('utf8')
+ print node
+ providers_node = '{0}/{1}/{2}'.format('dubbo', 'com.ofpay.demo.api.UserProvider', 'providers')
+ nodes = zk.get_children(providers_node)
+ for child_node in nodes:
+ node = urllib.unquote(child_node).decode('utf8')
+ print node
+ # zk.delete(parent_node+'/'+child_node, recursive=True)
+ # registry = MulticastRegistry('224.5.6.7:1234')
+ registry = ZookeeperRegistry('zookeeper:2181')
+ registry.subscribe('com.ofpay.demo.api.UserProvider')
+ print registry.get_providers('com.ofpay.demo.api.UserProvider')
+
+ time.sleep(500)
diff --git a/dubbo_client/rpcerror.py b/dubbo_client/rpcerror.py
new file mode 100644
index 0000000..f2e95b7
--- /dev/null
+++ b/dubbo_client/rpcerror.py
@@ -0,0 +1,115 @@
+# coding=utf-8
+
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+
+
+dubbo_client_errors = {}
+
+
+class DubboClientError(RuntimeError):
+ code = None
+ message = None
+ data = None
+
+ def __init__(self, message=None, data=None, code=None):
+ RuntimeError.__init__(self)
+ self.message = message or self.message
+ self.data = data
+ self.code = code or self.code
+ assert self.code, "Error without code is not allowed."
+
+ def __str__(self):
+ return "DubboError({code}): {message}".format(
+ code=self.code,
+ message=str(self.message)
+ )
+
+ def __unicode__(self):
+ return u"DubboClientError({code}): {message}".format(
+ code=self.code,
+ message=self.message
+ )
+
+
+class MethodNotFound(DubboClientError):
+ code = -32601
+ message = u"The method does not exist / is not available."
+
+ def __init__(self, message=None, data=None):
+ DubboClientError.__init__(self, message=message, data=data)
+
+
+class ConnectionFail(DubboClientError):
+ code = 504
+ message = u'connect failed {0}'
+
+ def __init__(self, message=None, data=None):
+ message = self.message.format(data)
+ DubboClientError.__init__(self, message=message, data=data)
+
+
+class NoProvider(DubboClientError):
+ code = 5050
+ message = u'No provide name {0}'
+ provide_name = u''
+
+ def __init__(self, message=None, data=None):
+ self.provide_name = data
+ DubboClientError.__init__(self, message=self.message.format(data), data=data)
+
+
+class InvalidParams(DubboClientError):
+ code = -32602
+ message = u"Invalid method parameter(s)."
+
+ def __init__(self, message=None, data=None):
+ DubboClientError.__init__(self, message=message, data=data)
+
+
+class InternalError(DubboClientError):
+ code = -32603
+ message = u"Internal JSON-RPC error."
+
+ def __init__(self, message=None, data=None):
+ DubboClientError.__init__(self, message=message, data=data)
+
+
+class InvalidRequest(DubboClientError):
+ code = -32600
+ message = u"The JSON sent is not a valid Request object."
+
+ def __init__(self, message=None, data=None):
+ DubboClientError.__init__(self, message=message, data=data)
+
+
+class UserDefinedError(DubboClientError):
+ code = -32000
+ message = u'User defined error happend'
+
+ def __init__(self, message=None, data=None):
+ DubboClientError.__init__(self, message=message, data=data)
+
+
+dubbo_client_errors[MethodNotFound.code] = MethodNotFound
+dubbo_client_errors[NoProvider.code] = NoProvider
+dubbo_client_errors[ConnectionFail.code] = ConnectionFail
+dubbo_client_errors[InvalidParams.code] = InvalidParams
+dubbo_client_errors[InternalError.code] = InternalError
+dubbo_client_errors[InvalidRequest.code] = InvalidRequest
+dubbo_client_errors[UserDefinedError.code] = UserDefinedError
diff --git a/dubbo_client/rpclib.py b/dubbo_client/rpclib.py
new file mode 100644
index 0000000..1671394
--- /dev/null
+++ b/dubbo_client/rpclib.py
@@ -0,0 +1,85 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+
+from urllib2 import HTTPError
+
+from pyjsonrpc import HttpClient, JsonRpcError
+
+from dubbo_client.registry import Registry
+from dubbo_client.rpcerror import ConnectionFail, dubbo_client_errors, InternalError, DubboClientError
+
+
+
+class DubboClient(object):
+ interface = ''
+ group = ''
+ version = ''
+
+ class _Method(object):
+
+ def __init__(self, client_instance, method):
+ self.client_instance = client_instance
+ self.method = method
+
+ def __call__(self, *args, **kwargs):
+ return self.client_instance.call(self.method, *args, **kwargs)
+
+ def __init__(self, interface, registry, **kwargs):
+ assert isinstance(registry, Registry)
+ self.interface = interface
+ self.registry = registry
+ self.group = kwargs.get('group', '')
+ self.version = kwargs.get('version', '')
+ self.registry.subscribe(interface)
+ self.registry.register(interface)
+
+ def call(self, method, *args, **kwargs):
+ provider = self.registry.get_random_provider(self.interface, version=self.version, group=self.group)
+ # print service_url.location
+ client = HttpClient(url="http://{0}{1}".format(provider.location, provider.path))
+ try:
+ return client.call(method, *args, **kwargs)
+ except HTTPError, e:
+ raise ConnectionFail(None, e.filename)
+ except JsonRpcError, error:
+ if error.code in dubbo_client_errors:
+ raise dubbo_client_errors[error.code](message=error.message, data=error.data)
+ else:
+ raise DubboClientError(code=error.code, message=error.message, data=error.data)
+ except Exception, ue:
+ if hasattr(ue, 'reason'):
+ raise InternalError(ue.message, ue.reason)
+ else:
+ raise InternalError(ue.message, None)
+
+ def __call__(self, method, *args, **kwargs):
+ """
+ Redirects the direct call to *self.call*
+ """
+ return self.call(method, *args, **kwargs)
+
+ def __getattr__(self, method):
+ """
+ Allows the usage of attributes as *method* names.
+ """
+ return self._Method(client_instance=self, method=method)
+
+
+if __name__ == '__main__':
+ pass
diff --git a/pyproject.toml b/pyproject.toml
deleted file mode 100644
index 7f9f425..0000000
--- a/pyproject.toml
+++ /dev/null
@@ -1,173 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-[build-system]
-requires = ["hatchling", "hatch-fancy-pypi-readme"]
-build-backend = "hatchling.build"
-
-[project]
-name="apache-dubbo"
-requires-python = ">=3.9"
-authors = [
- {name = "Apache Dubbo Community", email = "dev@dubbo.apache.org"}
-]
-maintainers = [
- {name = "Apache Dubbo Community", email = "dev@dubbo.apache.org"}
-]
-description = "Python Implementation For Apache Dubbo."
-license = "Apache-2.0"
-license-files = ["LICEN[CS]E.*"]
-keywords=["dubbo", "rpc","grpc", "dubbo-python", "http2", "network"]
-classifiers=[
- "Development Status :: 4 - Beta",
- "Intended Audience :: Developers",
- "License :: OSI Approved :: Apache Software License",
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: 3.13",
- "Framework :: AsyncIO",
- "Topic :: Internet",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
- "Topic :: Software Development :: Libraries",
- "Topic :: Software Development :: Libraries :: Python Modules",
- "Topic :: System :: Networking",
-]
-
-dependencies = [
- "h2>=4.1.0",
- "uvloop>=0.19.0; platform_system!='Windows'",
- "psutil>=6.0.0",
-]
-dynamic = ["version", "readme"]
-
-
-[project.urls]
-Homepage = "https://cn.dubbo.apache.org"
-Documentation = "https://cn.dubbo.apache.org/en/overview/mannual/python-sdk/"
-Repository = "https://github.com/apache/dubbo-python"
-Issues = "https://github.com/apache/dubbo/issues"
-
-[project.optional-dependencies]
-zookeeper = [
- "kazoo>=2.10.0",
-]
-
-### Hatch settings ###
-[tool.hatch.version]
-path = "src/dubbo/__about__.py"
-
-[tool.hatch.build.targets.sdist]
-include = [
- "/src",
- "/tests",
- "/README.md",
-]
-
-[tool.hatch.build.targets.wheel]
-packages = ["src/dubbo"]
-
-
-[tool.hatch.metadata.hooks.fancy-pypi-readme]
-content-type = "text/markdown"
-
-[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
-path = "README.md"
-
-
-
-### Ruff settings ###
-
-# Top-level
-[tool.ruff]
-target-version = "py39"
-line-length = 120
-extend-exclude = ["samples/proto"]
-
-# Format
-[tool.ruff.format]
-docstring-code-format = true
-
-# Lint
-[tool.ruff.lint]
-select = [
- "E", # pycodestyle errors
- "W", # pycodestyle warnings
- "F", # pyflakes
- "I", # Check for missing imports (auto-fixable)
- "UP", # pyupgrade
- "ASYNC", # flake8-async
- "ISC", # Checks for implicit literal string concatenation (auto-fixable)
- "LOG", # Checking the use of logging objects
- "G", # Check for logging format issues (auto-fixable)
-]
-ignore = [
- "ISC001" # may casue conflict with ruff
-]
-
-
-[tool.ruff.lint.isort]
-combine-as-imports = true
-
-section-order = [
- "future",
- "standard-library",
- "third-party",
- "first-party",
- "local-folder"
-]
-
-### Coverage settings ###
-[tool.coverage.run]
-branch = true
-relative_files = true
-include = ["src/dubbo/*"]
-
-[tool.coverage.report]
-# Skip coverage report for 100% covered files
-skip_covered = true
-exclude_also = [
- "def __repr__",
- "raise AssertionError",
- "raise NotImplementedError",
- "if __name__ == .__main__.:",
- "@(abc\\.)?abstractmethod",
- "@(typing(_extensions)?\\.)?overload",
- "if (typing(_extensions)?\\.)?TYPE_CHECKING:"
-]
-
-
-### Mypy settings ###
-[tool.mypy]
-ignore_missing_imports = true
-
-[[tool.mypy.overrides]]
-module = "tests.*"
-disallow_untyped_defs = false
-check_untyped_defs = true
-
-### Pytest settings ###
-[tool.pytest]
-addopts = "-rxXs"
-testpaths = ["tests"]
-python_files = [
- "test_*.py"
-]
\ No newline at end of file
diff --git a/requirements-dev.txt b/requirements-dev.txt
deleted file mode 100644
index d43d7e5..0000000
--- a/requirements-dev.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-# Here, we do not specify the dependencies required by the project in the requirements.txt file,
-# as they have all been moved to the pyproject.toml file.
-# In this file, we only specify the dependencies needed for the development environment,
-# such as testing, packaging, linting, etc.
-
--e .[zoopkeeper]
-
-
-# Packaging
-hatch>=1.14.0
-
-
-# Formatting & Linting
-ruff>=0.9.1
-mypy>=1.14.1
-
-# Testing
-pytest>=8.3.4
-coverage[toml]>=7.6.10
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..11d5b61
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+bunch==1.0.1
+kazoo==2.0
+py==1.4.26
+pytest==2.7.0
+python-jsonrpc==0.7.3
+wsgiref==0.1.2
diff --git a/samples/README.md b/samples/README.md
deleted file mode 100644
index 0b21213..0000000
--- a/samples/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Dubbo-python Samples
-
-## What It Contains
-
-1. [**helloworld**](./helloworld): The simplest usage example for quick start.
-2. [**serialization**](./serialization): Writing and using custom serialization functions, including protobuf, JSON, and more.
-3. [**stream**](./stream): Using streaming calls, including `ClientStream`, `ServerStream`, and `BidirectionalStream`.
-4. [**registry**](./registry): Using service registration and discovery features.
-5. [**LLM Integration**](./llm): Easily integrating LLMs with Dubbo Python, providing RPC services using models like DeepSeek R1.
-
diff --git a/samples/__init__.py b/samples/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/helloworld/__init__.py b/samples/helloworld/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/helloworld/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/helloworld/client.py b/samples/helloworld/client.py
deleted file mode 100644
index 8881547..0000000
--- a/samples/helloworld/client.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ReferenceConfig
-
-
-class UnaryServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(method_name="unary")
-
- def say_hello(self, message: bytes) -> bytes:
- return self.unary(message)
-
-
-if __name__ == "__main__":
- # Create a client
- reference_config = ReferenceConfig.from_url("tri://127.0.0.1:50051/org.apache.dubbo.samples.HelloWorld")
- dubbo_client = dubbo.Client(reference_config)
- unary_service_stub = UnaryServiceStub(dubbo_client)
-
- # Call the remote method
- result = unary_service_stub.say_hello(b"Hello from client")
- print(result)
diff --git a/samples/helloworld/server.py b/samples/helloworld/server.py
deleted file mode 100644
index 6ace94c..0000000
--- a/samples/helloworld/server.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-
-
-class UnaryServiceServicer:
- def say_hello(self, message: bytes) -> bytes:
- print(f"Received message from client: {message}")
- return b"Hello from server"
-
-
-def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(method=UnaryServiceServicer().say_hello, method_name="unary")
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.HelloWorld",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build service config
- service_handler = build_service_handler()
- service_config = ServiceConfig(service_handler=service_handler, host="127.0.0.1", port=50051)
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/samples/llm/README.md b/samples/llm/README.md
deleted file mode 100644
index 36326b2..0000000
--- a/samples/llm/README.md
+++ /dev/null
@@ -1,127 +0,0 @@
-## Integrating LLM
-
-Dubbo Python can easily integrate with LLMs and provide RPC services.
-
-- **Model**: DeepSeek-R1-Distill-Qwen-7B
-- **Model Deployment Framework**: LMDeploy
-- **GPU**: NVIDIA Corporation GA102GL [A10] (rev a1)
-
-**Description**: This example demonstrates the use of [DeepSeek R1](https://github.com/deepseek-ai/DeepSeek-R1) and [LMDeploy](https://github.com/InternLM/lmdeploy) for deployment, but the overall process is applicable to other models and inference frameworks. If you wish to deploy using Docker or other containerization methods, refer to the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html) documentation for relevant configuration steps.
-
-### Basic Environment
-
-```sh
-----------
-Operating System: Ubuntu 22.04.5
-Python Version: 3.11.10
-PyTorch Version: 2.5.1
-----------
-```
-
-### Model Download
-
-Use the `snapshot_download` function provided by modelscope to download the model. The first parameter is the model name, and the `cache_dir` parameter specifies the download path for the model.
-
-```python
-from modelscope import snapshot_download
-
-model_dir = snapshot_download('deepseek-ai/DeepSeek-R1-Distill-Qwen-7B', cache_dir='/home/dubbo/model', revision='master')
-```
-
-### Core code
-
-```python
-from time import sleep
-
-from lmdeploy import GenerationConfig, TurbomindEngineConfig, pipeline
-
-from dubbo import Dubbo
-from dubbo.configs import RegistryConfig, ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-import chat_pb2
-
-# the path of a model. It could be one of the following options:
-# 1. A local directory path of a turbomind model
-# 2. The model_id of a lmdeploy-quantized model
-# 3. The model_id of a model hosted inside a model repository
-model = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
-
-backend_config = TurbomindEngineConfig(cache_max_entry_count=0.2, max_context_token_num=20544, session_len=20544)
-
-gen_config = GenerationConfig(
- top_p=0.95,
- temperature=0.6,
- max_new_tokens=8192,
- stop_token_ids=[151329, 151336, 151338],
- do_sample=True, # enable sampling
-)
-
-
-class DeepSeekAiServicer:
- def __init__(self, model: str, backend_config: TurbomindEngineConfig, gen_config: GenerationConfig):
- self.llm = pipeline(model, backend_config=backend_config)
- self.gen_config = gen_config
-
- def chat(self, stream):
- # read request from stream
- request = stream.read()
- print(f"Received request: {request}")
- # prepare prompts
- prompts = [{"role": request.role, "content": request.content + "\n"}]
-
- is_think = False
-
- # perform streaming inference
- for item in self.llm.stream_infer(prompts, gen_config=gen_config):
- # update think status
- if item.text == "":
- is_think = True
- continue
- elif item.text == "":
- is_think = False
- continue
- # According to the state of thought, decide the content of the reply.
- if is_think:
- # send thought
- stream.write(chat_pb2.ChatReply(think=item.text, answer=""))
- else:
- # send answer
- stream.write(chat_pb2.ChatReply(think="", answer=item.text))
-
- stream.done_writing()
-
-
-def build_server_handler():
- # build a method handler
- deepseek_ai_servicer = DeepSeekAiServicer(model, backend_config, gen_config)
- method_handler = RpcMethodHandler.server_stream(
- deepseek_ai_servicer.chat,
- method_name="chat",
- request_deserializer=chat_pb2.ChatRequest.FromString,
- response_serializer=chat_pb2.ChatReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.llm.api.DeepSeekAiService",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build a service handler
- service_handler = build_server_handler()
- service_config = ServiceConfig(service_handler=service_handler)
-
- # Configure the Zookeeper registry
- registry_config = RegistryConfig.from_url("zookeeper://zookeeper:2181")
- bootstrap = Dubbo(registry_config=registry_config)
-
- # Create and start the server
- bootstrap.create_server(service_config).start()
-
- # 30days
- sleep(30 * 24 * 60 * 60)
-
-```
-
diff --git a/samples/llm/__init__.py b/samples/llm/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/llm/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/llm/chat.proto b/samples/llm/chat.proto
deleted file mode 100644
index e041636..0000000
--- a/samples/llm/chat.proto
+++ /dev/null
@@ -1,21 +0,0 @@
-syntax = "proto3";
-
-option java_multiple_files = true;
-option java_outer_classname = "ChatProto";
-
-package org.apache.dubbo.samples.llm.api;
-
-message ChatRequest {
- string role = 1;
- string content = 2;
-}
-
-message ChatReply {
- string think = 1;
- string answer = 2;
-}
-
-service DeepSeekAiService {
- // chat
- rpc chat(ChatRequest) returns (stream ChatReply);
-}
diff --git a/samples/llm/chat_pb2.py b/samples/llm/chat_pb2.py
deleted file mode 100644
index de9488e..0000000
--- a/samples/llm/chat_pb2.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# source: chat.proto
-# Protobuf Python Version: 4.25.3
-"""Generated protocol buffer code."""
-
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import descriptor_pool as _descriptor_pool
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf.internal import builder as _builder
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
- b'\n\nchat.proto\x12 org.apache.dubbo.samples.llm.api",\n\x0b\x43hatRequest\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t"*\n\tChatReply\x12\r\n\x05think\x18\x01 \x01(\t\x12\x0e\n\x06\x61nswer\x18\x02 \x01(\t2y\n\x11\x44\x65\x65pSeekAiService\x12\x64\n\x04\x63hat\x12-.org.apache.dubbo.samples.llm.api.ChatRequest\x1a+.org.apache.dubbo.samples.llm.api.ChatReply0\x01\x42\rB\tChatProtoP\x01\x62\x06proto3'
-)
-
-_globals = globals()
-_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
-_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "chat_pb2", _globals)
-if _descriptor._USE_C_DESCRIPTORS == False:
- _globals["DESCRIPTOR"]._options = None
- _globals["DESCRIPTOR"]._serialized_options = b"B\tChatProtoP\001"
- _globals["_CHATREQUEST"]._serialized_start = 48
- _globals["_CHATREQUEST"]._serialized_end = 92
- _globals["_CHATREPLY"]._serialized_start = 94
- _globals["_CHATREPLY"]._serialized_end = 136
- _globals["_DEEPSEEKAISERVICE"]._serialized_start = 138
- _globals["_DEEPSEEKAISERVICE"]._serialized_end = 259
-# @@protoc_insertion_point(module_scope)
diff --git a/samples/llm/main.py b/samples/llm/main.py
deleted file mode 100644
index e315716..0000000
--- a/samples/llm/main.py
+++ /dev/null
@@ -1,106 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-from time import sleep
-
-from lmdeploy import GenerationConfig, TurbomindEngineConfig, pipeline
-
-from dubbo import Dubbo
-from dubbo.configs import RegistryConfig, ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-import chat_pb2
-
-# the path of a model. It could be one of the following options:
-# 1. A local directory path of a turbomind model
-# 2. The model_id of a lmdeploy-quantized model
-# 3. The model_id of a model hosted inside a model repository
-model = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
-
-backend_config = TurbomindEngineConfig(cache_max_entry_count=0.2, max_context_token_num=20544, session_len=20544)
-
-gen_config = GenerationConfig(
- top_p=0.95,
- temperature=0.6,
- max_new_tokens=8192,
- stop_token_ids=[151329, 151336, 151338],
- do_sample=True, # enable sampling
-)
-
-
-class DeepSeekAiServicer:
- def __init__(self, model: str, backend_config: TurbomindEngineConfig, gen_config: GenerationConfig):
- self.llm = pipeline(model, backend_config=backend_config)
- self.gen_config = gen_config
-
- def chat(self, stream):
- # read request from stream
- request = stream.read()
- print(f"Received request: {request}")
- # prepare prompts
- prompts = [{"role": request.role, "content": request.content + "\n"}]
-
- is_think = False
-
- # perform streaming inference
- for item in self.llm.stream_infer(prompts, gen_config=gen_config):
- # update think status
- if item.text == "":
- is_think = True
- continue
- elif item.text == "":
- is_think = False
- continue
- # According to the state of thought, decide the content of the reply.
- if is_think:
- # send thought
- stream.write(chat_pb2.ChatReply(think=item.text, answer=""))
- else:
- # send answer
- stream.write(chat_pb2.ChatReply(think="", answer=item.text))
-
- stream.done_writing()
-
-
-def build_server_handler():
- # build a method handler
- deepseek_ai_servicer = DeepSeekAiServicer(model, backend_config, gen_config)
- method_handler = RpcMethodHandler.server_stream(
- deepseek_ai_servicer.chat,
- method_name="chat",
- request_deserializer=chat_pb2.ChatRequest.FromString,
- response_serializer=chat_pb2.ChatReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.llm.api.DeepSeekAiService",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build a service handler
- service_handler = build_server_handler()
- service_config = ServiceConfig(service_handler=service_handler)
-
- # Configure the Zookeeper registry
- registry_config = RegistryConfig.from_url("zookeeper://zookeeper:2181")
- bootstrap = Dubbo(registry_config=registry_config)
-
- # Create and start the server
- bootstrap.create_server(service_config).start()
-
- # 30days
- sleep(30 * 24 * 60 * 60)
diff --git a/samples/proto/__init__.py b/samples/proto/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/proto/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/proto/greeter.proto b/samples/proto/greeter.proto
deleted file mode 100644
index 491520a..0000000
--- a/samples/proto/greeter.proto
+++ /dev/null
@@ -1,28 +0,0 @@
-syntax = "proto3";
-
-package org.apache.dubbo.samples.proto;
-
-message GreeterRequest {
- string name = 1;
-}
-
-message GreeterReply {
- string message = 1;
-}
-
-service Greeter{
-
- // unary
- rpc sayHello(GreeterRequest) returns (GreeterReply);
-
- // client stream
- rpc clientStream(stream GreeterRequest) returns (GreeterReply);
-
- // server stream
- rpc serverStream(GreeterRequest) returns (stream GreeterReply);
-
- // bi-directional stream
- rpc biStream(stream GreeterRequest) returns (stream GreeterReply);
-
-
-}
diff --git a/samples/proto/greeter_pb2.py b/samples/proto/greeter_pb2.py
deleted file mode 100644
index b8f5020..0000000
--- a/samples/proto/greeter_pb2.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by the protocol buffer compiler. DO NOT EDIT!
-# NO CHECKED-IN PROTOBUF GENCODE
-# source: greeter.proto
-# Protobuf Python Version: 5.27.0
-"""Generated protocol buffer code."""
-from google.protobuf import descriptor as _descriptor
-from google.protobuf import descriptor_pool as _descriptor_pool
-from google.protobuf import runtime_version as _runtime_version
-from google.protobuf import symbol_database as _symbol_database
-from google.protobuf.internal import builder as _builder
-_runtime_version.ValidateProtobufRuntimeVersion(
- _runtime_version.Domain.PUBLIC,
- 5,
- 27,
- 0,
- '',
- 'greeter.proto'
-)
-# @@protoc_insertion_point(imports)
-
-_sym_db = _symbol_database.Default()
-
-
-
-
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rgreeter.proto\x12\x1eorg.apache.dubbo.samples.proto\"\x1e\n\x0eGreeterRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1f\n\x0cGreeterReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\xc1\x03\n\x07Greeter\x12h\n\x08sayHello\x12..org.apache.dubbo.samples.proto.GreeterRequest\x1a,.org.apache.dubbo.samples.proto.GreeterReply\x12n\n\x0c\x63lientStream\x12..org.apache.dubbo.samples.proto.GreeterRequest\x1a,.org.apache.dubbo.samples.proto.GreeterReply(\x01\x12n\n\x0cserverStream\x12..org.apache.dubbo.samples.proto.GreeterRequest\x1a,.org.apache.dubbo.samples.proto.GreeterReply0\x01\x12l\n\x08\x62iStream\x12..org.apache.dubbo.samples.proto.GreeterRequest\x1a,.org.apache.dubbo.samples.proto.GreeterReply(\x01\x30\x01\x62\x06proto3')
-
-_globals = globals()
-_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
-_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'greeter_pb2', _globals)
-if not _descriptor._USE_C_DESCRIPTORS:
- DESCRIPTOR._loaded_options = None
- _globals['_GREETERREQUEST']._serialized_start=49
- _globals['_GREETERREQUEST']._serialized_end=79
- _globals['_GREETERREPLY']._serialized_start=81
- _globals['_GREETERREPLY']._serialized_end=112
- _globals['_GREETER']._serialized_start=115
- _globals['_GREETER']._serialized_end=564
-# @@protoc_insertion_point(module_scope)
diff --git a/samples/registry/README.md b/samples/registry/README.md
deleted file mode 100644
index dc8a068..0000000
--- a/samples/registry/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-## Service Registration and Discovery
-
-Using service registration and discovery is very simple. In fact, it only requires two additional lines of code compared to point-to-point calls. Before using this feature, we need to install the relevant registry client. Currently, Dubbo-python only supports `Zookeeper`, so the following demonstration will use `Zookeeper`.
-
-Similar to before, we need to clone the Dubbo-python source code and install it. However, in this case, we also need to install the `Zookeeper` client. The commands are:
-
-```shell
-git clone https://github.com/apache/dubbo-python.git
-cd dubbo-python && pip install .[zookeeper]
-```
-
-After that, simply start `Zookeeper` and insert the following code into your existing example:
-
-```python
-# Configure the Zookeeper registry
-registry_config = RegistryConfig.from_url("zookeeper://127.0.0.1:2181")
-bootstrap = Dubbo(registry_config=registry_config)
-
-# Create the client
-client = bootstrap.create_client(reference_config)
-
-# Create and start the server
-bootstrap.create_server(service_config).start()
-```
-
-This enables service registration and discovery within your Dubbo-python project.
diff --git a/samples/registry/__init__.py b/samples/registry/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/registry/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/registry/zookeeper/__init__.py b/samples/registry/zookeeper/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/registry/zookeeper/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/registry/zookeeper/client.py b/samples/registry/zookeeper/client.py
deleted file mode 100644
index b9035d6..0000000
--- a/samples/registry/zookeeper/client.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import dubbo
-from dubbo.configs import ReferenceConfig, RegistryConfig
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(
- method_name="sayHello",
- request_serializer=greeter_pb2.GreeterRequest.SerializeToString,
- response_deserializer=greeter_pb2.GreeterReply.FromString,
- )
-
- def say_hello(self, request):
- return self.unary(request)
-
-
-if __name__ == "__main__":
- registry_config = RegistryConfig.from_url("zookeeper://127.0.0.1:2181")
- bootstrap = dubbo.Dubbo(registry_config=registry_config)
-
- reference_config = ReferenceConfig(protocol="tri", service="org.apache.dubbo.samples.data.Greeter")
- dubbo_client = bootstrap.create_client(reference_config)
-
- stub = GreeterServiceStub(dubbo_client)
-
- result = stub.say_hello(greeter_pb2.GreeterRequest(name="dubbo-python"))
-
- print(result.message)
diff --git a/samples/registry/zookeeper/server.py b/samples/registry/zookeeper/server.py
deleted file mode 100644
index 8e9b463..0000000
--- a/samples/registry/zookeeper/server.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import RegistryConfig, ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceServicer:
- def say_hello(self, request):
- print(f"Received request: {request.name}")
- return greeter_pb2.GreeterReply(message=f"Hello, {request.name}!")
-
-
-def build_server_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(
- GreeterServiceServicer().say_hello,
- method_name="sayHello",
- request_deserializer=greeter_pb2.GreeterRequest.FromString,
- response_serializer=greeter_pb2.GreeterReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.data.Greeter",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- registry_config = RegistryConfig.from_url("zookeeper://127.0.0.1:2181")
- bootstrap = dubbo.Dubbo(registry_config=registry_config)
-
- # build a service config
- service_handler = build_server_handler()
- service_config = ServiceConfig(service_handler)
-
- # start the server
- server = bootstrap.create_server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/samples/serialization/README.md b/samples/serialization/README.md
deleted file mode 100644
index d711d4f..0000000
--- a/samples/serialization/README.md
+++ /dev/null
@@ -1,190 +0,0 @@
-## Custom Serialization
-
-Python is a dynamic language, and its flexibility makes it challenging to design a universal serialization layer for other languages. Therefore, we removed the framework-level serialization layer and instead exposed interfaces, allowing users to implement their own (as they know best the data format they will be passing).
-
-Serialization typically involves two parts: serialization and deserialization. We have defined the types for these functions, and custom serialization/deserialization functions must follow these "formats."
-
-For serialization functions, we specify:
-
-```python
-# A function that takes any number of arguments and returns data of type bytes
-SerializingFunction = Callable[..., bytes]
-```
-
-For deserialization functions, we specify:
-
-```python
-# A function that takes an argument of type bytes and returns data of any type
-DeserializingFunction = Callable[[bytes], Any]
-```
-
-Below, I'll demonstrate how to use `protobuf` and `json` for serialization.
-
-### [protobuf](./protobuf)
-
-1. For defining and compiling `protobuf` files, please refer to the [protobuf tutorial](https://protobuf.dev/getting-started/pythontutorial/) for detailed instructions.
-
-2. Set `xxx_serializer` and `xxx_deserializer` in the client and server.
-
- client
-
- ```python
- class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(
- method_name="sayHello",
- request_serializer=greeter_pb2.GreeterRequest.SerializeToString,
- response_deserializer=greeter_pb2.GreeterReply.FromString,
- )
-
- def say_hello(self, request):
- return self.unary(request)
-
-
- if __name__ == "__main__":
- reference_config = ReferenceConfig.from_url(
- "tri://127.0.0.1:50051/org.apache.dubbo.samples.data.Greeter"
- )
- dubbo_client = dubbo.Client(reference_config)
-
- stub = GreeterServiceStub(dubbo_client)
- result = stub.say_hello(greeter_pb2.GreeterRequest(name="Dubbo-python"))
- print(f"Received reply: {result.message}")
- ```
-
- server
-
- ```python
- class GreeterServiceServicer:
- def say_hello(self, request):
- print(f"Received request: {request}")
- return greeter_pb2.GreeterReply(message=f"Hello, {request.name}")
-
-
- def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(
- GreeterServiceServicer().say_hello,
- method_name="sayHello",
- request_deserializer=greeter_pb2.GreeterRequest.FromString,
- response_serializer=greeter_pb2.GreeterReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.data.Greeter",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
- if __name__ == "__main__":
- # build a service handler
- service_handler = build_service_handler()
- service_config = ServiceConfig(
- service_handler=service_handler, host="127.0.0.1", port=50051
- )
-
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
- ```
-
-
-
-### [Json](./json)
-
-We have already implemented single-parameter serialization and deserialization using `protobuf`. Now, I will demonstrate how to write a multi-parameter `Json` serialization and deserialization function to enable remote calls for methods with multiple parameters.
-
-1. Install `orjson`:
-
- ```shell
- pip install orjson
- ```
-
-2. Define serialization and deserialization functions:
-
- client
-
- ```python
- def request_serializer(name: str, age: int) -> bytes:
- return orjson.dumps({"name": name, "age": age})
-
-
- def response_deserializer(data: bytes) -> str:
- json_dict = orjson.loads(data)
- return json_dict["message"]
-
-
- class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(
- method_name="unary",
- request_serializer=request_serializer,
- response_deserializer=response_deserializer,
- )
-
- def say_hello(self, name: str, age: int):
- return self.unary(name, age)
-
-
- if __name__ == "__main__":
- reference_config = ReferenceConfig.from_url(
- "tri://127.0.0.1:50051/org.apache.dubbo.samples.serialization.json"
- )
- dubbo_client = dubbo.Client(reference_config)
-
- stub = GreeterServiceStub(dubbo_client)
- result = stub.say_hello("dubbo-python", 18)
- print(result)
- ```
-
- server
-
- ```python
- def request_deserializer(data: bytes) -> Tuple[str, int]:
- json_dict = orjson.loads(data)
- return json_dict["name"], json_dict["age"]
-
-
- def response_serializer(message: str) -> bytes:
- return orjson.dumps({"message": message})
-
-
- class GreeterServiceServicer:
- def say_hello(self, request):
- name, age = request
- print(f"Received request: {name}, {age}")
- return f"Hello, {name}, you are {age} years old."
-
-
- def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(
- GreeterServiceServicer().say_hello,
- method_name="unary",
- request_deserializer=request_deserializer,
- response_serializer=response_serializer,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.serialization.json",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
- if __name__ == "__main__":
- # build server config
- service_handler = build_service_handler()
- service_config = ServiceConfig(
- service_handler=service_handler, host="127.0.0.1", port=50051
- )
-
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
- ```
-
-
\ No newline at end of file
diff --git a/samples/serialization/__init__.py b/samples/serialization/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/serialization/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/serialization/json/__init__.py b/samples/serialization/json/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/serialization/json/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/serialization/json/client.py b/samples/serialization/json/client.py
deleted file mode 100644
index 9cd13c8..0000000
--- a/samples/serialization/json/client.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import orjson
-
-import dubbo
-from dubbo.configs import ReferenceConfig
-
-
-def request_serializer(name: str, age: int) -> bytes:
- return orjson.dumps({"name": name, "age": age})
-
-
-def response_deserializer(data: bytes) -> str:
- json_dict = orjson.loads(data)
- return json_dict["message"]
-
-
-class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(
- method_name="unary",
- request_serializer=request_serializer,
- response_deserializer=response_deserializer,
- )
-
- def say_hello(self, name: str, age: int):
- return self.unary(name, age)
-
-
-if __name__ == "__main__":
- reference_config = ReferenceConfig.from_url("tri://127.0.0.1:50051/org.apache.dubbo.samples.serialization.json")
- dubbo_client = dubbo.Client(reference_config)
-
- stub = GreeterServiceStub(dubbo_client)
- result = stub.say_hello("dubbo-python", 18)
- print(result)
diff --git a/samples/serialization/json/server.py b/samples/serialization/json/server.py
deleted file mode 100644
index f2b7160..0000000
--- a/samples/serialization/json/server.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import orjson
-
-import dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-
-
-def request_deserializer(data: bytes) -> tuple[str, int]:
- json_dict = orjson.loads(data)
- return json_dict["name"], json_dict["age"]
-
-
-def response_serializer(message: str) -> bytes:
- return orjson.dumps({"message": message})
-
-
-class GreeterServiceServicer:
- def say_hello(self, request):
- name, age = request
- print(f"Received request: {name}, {age}")
- return f"Hello, {name}, you are {age} years old."
-
-
-def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(
- GreeterServiceServicer().say_hello,
- method_name="unary",
- request_deserializer=request_deserializer,
- response_serializer=response_serializer,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.serialization.json",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build server config
- service_handler = build_service_handler()
- service_config = ServiceConfig(service_handler=service_handler, host="127.0.0.1", port=50051)
-
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/samples/serialization/protobuf/__init__.py b/samples/serialization/protobuf/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/serialization/protobuf/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/serialization/protobuf/client.py b/samples/serialization/protobuf/client.py
deleted file mode 100644
index 0f33137..0000000
--- a/samples/serialization/protobuf/client.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ReferenceConfig
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary = client.unary(
- method_name="sayHello",
- request_serializer=greeter_pb2.GreeterRequest.SerializeToString,
- response_deserializer=greeter_pb2.GreeterReply.FromString,
- )
-
- def say_hello(self, request):
- return self.unary(request)
-
-
-if __name__ == "__main__":
- reference_config = ReferenceConfig.from_url("tri://127.0.0.1:50051/org.apache.dubbo.samples.data.Greeter")
- dubbo_client = dubbo.Client(reference_config)
-
- stub = GreeterServiceStub(dubbo_client)
- result = stub.say_hello(greeter_pb2.GreeterRequest(name="Dubbo-python"))
- print(f"Received reply: {result.message}")
diff --git a/samples/serialization/protobuf/server.py b/samples/serialization/protobuf/server.py
deleted file mode 100644
index 7381179..0000000
--- a/samples/serialization/protobuf/server.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceServicer:
- def say_hello(self, request):
- print(f"Received request: {request}")
- return greeter_pb2.GreeterReply(message=f"Hello, {request.name}")
-
-
-def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.unary(
- GreeterServiceServicer().say_hello,
- method_name="sayHello",
- request_deserializer=greeter_pb2.GreeterRequest.FromString,
- response_serializer=greeter_pb2.GreeterReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.data.Greeter",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build a service handler
- service_handler = build_service_handler()
- service_config = ServiceConfig(service_handler=service_handler, host="127.0.0.1", port=50051)
-
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/samples/stream/README.md b/samples/stream/README.md
deleted file mode 100644
index 51100e0..0000000
--- a/samples/stream/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-## Streaming Calls
-
-Dubbo-Python supports streaming calls, including `ClientStream`, `ServerStream`, and `BidirectionalStream` modes.
-
-Streaming calls can be divided into write-streams and read-streams. For `ClientStream`, it’s multiple writes with a single read; for `ServerStream`, a single write with multiple reads; and `BidirectionalStream` allows multiple writes and reads.
-
-### Write-Stream
-
-Write operations in streaming calls can be divided into single write (`ServerStream`) and multiple writes (`ClientStream` and `BidirectionalStream`).
-
-#### Single Write
-
-Single write calls are similar to unary mode. For example:
-
-```python
-stub.server_stream(greeter_pb2.GreeterRequest(name="hello world from dubbo-python"))
-```
-
-#### Multiple Writes
-
-For multiple writes, users can write data using either an iterator or `writeStream` (only one of these options should be used).
-
-1. **Iterator-based Write**: Writing via iterator is similar to unary mode, with the main difference being the use of an iterator for multiple writes. For example:
-
- ```python
- # Use an iterator to send multiple requests
- def request_generator():
- for i in ["hello", "world", "from", "dubbo-python"]:
- yield greeter_pb2.GreeterRequest(name=str(i))
-
- # Call the remote method and return a read_stream
- stream = stub.client_stream(request_generator())
- ```
-
-2. **Using `writeStream`**: This method requires an empty argument, after which data is written incrementally using `write`, and `done_writing` is called to end the write-stream. For example:
-
- ```python
- stream = stub.bi_stream()
- # Use the write method to send messages
- stream.write(greeter_pb2.GreeterRequest(name="jock"))
- stream.write(greeter_pb2.GreeterRequest(name="jane"))
- stream.write(greeter_pb2.GreeterRequest(name="alice"))
- stream.write(greeter_pb2.GreeterRequest(name="dave"))
- # Call done_writing to notify the server that the client has finished writing
- stream.done_writing()
- ```
-
-### Read-Stream
-
-Read operations for streaming calls can be single read (`ClientStream`) or multiple reads (`ServerStream` and `BidirectionalStream`). A `ReadStream` is returned in all cases, and data can be read using the `read` method or an iterator. When using `read`, please note:
-
-1. The `read` method supports a `timeout` parameter (in seconds).
-2. The `read` method can return one of three values: the expected data, `None` (timeout exceeded), or `EOF` (end of the read-stream).
-
-#### Single Read
-
-A single call to the `read` method will retrieve the data, for example:
-
-```python
-result = stream.read()
-print(f"Received response: {result.message}")
-```
-
-#### Multiple Reads
-
-Multiple reads can be done by repeatedly calling `read`, with handling for `None` and `EOF` values. Since `ReadStream` implements `__iter__` and `__next__`, an iterator-based approach can also be used, which automatically handles these values but doesn’t support a timeout.
-
-1. **Using Iterator (Recommended)**:
-
- ```python
- def client_stream(self, request_iterator):
- response = ""
- for request in request_iterator:
- print(f"Received request: {request.name}")
- response += f"{request.name} "
- return greeter_pb2.GreeterReply(message=response)
- ```
-
-2. **Multiple Calls to `read` Method**:
-
- ```python
- # Use read method to receive messages
- # If no message arrives within the specified time, returns None
- # If the server has finished sending messages, returns EOF
- while True:
- i = stream.read(timeout=0.5)
- if i is dubbo.classes.EOF:
- break
- elif i is None:
- print("No message received")
- continue
- print(f"Received response: {i.message}")
- ```
-
diff --git a/samples/stream/__init__.py b/samples/stream/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/stream/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/stream/bidi_stream/__init__.py b/samples/stream/bidi_stream/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/stream/bidi_stream/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/stream/bidi_stream/client.py b/samples/stream/bidi_stream/client.py
deleted file mode 100644
index 4c387dc..0000000
--- a/samples/stream/bidi_stream/client.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import dubbo
-from dubbo.classes import EOF
-from dubbo.configs import ReferenceConfig
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.bidi_stream = client.bi_stream(
- method_name="biStream",
- request_serializer=greeter_pb2.GreeterRequest.SerializeToString,
- response_deserializer=greeter_pb2.GreeterReply.FromString,
- )
-
- def bi_stream(self, *args):
- return self.bidi_stream(args)
-
-
-if __name__ == "__main__":
- # Create a reference config
- reference_config = ReferenceConfig.from_url("tri://127.0.0.1:50051/org.apache.dubbo.samples.data.Greeter")
- dubbo_client = dubbo.Client(reference_config)
- stub = GreeterServiceStub(dubbo_client)
-
- stream = stub.bi_stream()
- # use write method to send message
- stream.write(greeter_pb2.GreeterRequest(name="jock"))
-
- # use read method to receive message
- print(f"Received response: {stream.read().message}")
-
- # continue to send message
- stream.write(greeter_pb2.GreeterRequest(name="jane"))
- stream.write(greeter_pb2.GreeterRequest(name="alice"))
- stream.write(greeter_pb2.GreeterRequest(name="dave"))
- # done_writing method must be called to notify the server that the client has finished writing
- stream.done_writing()
-
- # use read method to receive message
- # If no message arrives within the specified time, returns None
- # If the server has finished sending messages and the client has received all messages, returns EOF
- while True:
- i = stream.read(timeout=0.5)
- if i is EOF:
- break
- elif i is None:
- print("No message received")
- continue
- print(f"Received response: {i.message}")
diff --git a/samples/stream/bidi_stream/server.py b/samples/stream/bidi_stream/server.py
deleted file mode 100644
index a01246a..0000000
--- a/samples/stream/bidi_stream/server.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import time
-
-import dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceServicer:
- def bi_stream(self, stream):
- counter = 0
- for request in stream:
- print(f"Received request: {request.name}")
- # simulate a long time to process
- if counter == 1:
- time.sleep(1)
- counter += 1
-
- stream.write(greeter_pb2.GreeterReply(message=f"Hello, {request.name}!"))
-
- stream.done_writing()
-
-
-def build_server_handler():
- method_handler = RpcMethodHandler.bi_stream(
- GreeterServiceServicer().bi_stream,
- method_name="biStream",
- request_deserializer=greeter_pb2.GreeterRequest.FromString,
- response_serializer=greeter_pb2.GreeterReply.SerializeToString,
- )
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.data.Greeter",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build a service config
- service_handler = build_server_handler()
- service_config = ServiceConfig(service_handler, host="127.0.0.1", port=50051)
-
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/samples/stream/client_stream/__init__.py b/samples/stream/client_stream/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/stream/client_stream/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/stream/client_stream/client.py b/samples/stream/client_stream/client.py
deleted file mode 100644
index b9c1bbe..0000000
--- a/samples/stream/client_stream/client.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ReferenceConfig
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.unary_stream = client.client_stream(
- method_name="clientStream",
- request_serializer=greeter_pb2.GreeterRequest.SerializeToString,
- response_deserializer=greeter_pb2.GreeterReply.FromString,
- )
-
- def client_stream(self, request_iterator):
- return self.unary_stream(request_iterator)
-
-
-if __name__ == "__main__":
- reference_config = ReferenceConfig.from_url("tri://127.0.0.1:50051/org.apache.dubbo.samples.data.Greeter")
- dubbo_client = dubbo.Client(reference_config)
- stub = GreeterServiceStub(dubbo_client)
-
- # use iterator to send multiple requests
- def request_generator():
- for i in ["hello", "world", "from", "dubbo-python"]:
- yield greeter_pb2.GreeterRequest(name=str(i))
-
- # call the remote method and return a read_stream
- stream = stub.client_stream(request_generator())
- # use read method to get the response
- print(stream.read())
diff --git a/samples/stream/client_stream/server.py b/samples/stream/client_stream/server.py
deleted file mode 100644
index 2ab0a28..0000000
--- a/samples/stream/client_stream/server.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceServicer:
- def client_stream(self, request_iterator):
- response = ""
- for request in request_iterator:
- print(f"Received request: {request.name}")
- response += f"{request.name} "
- return greeter_pb2.GreeterReply(message=response)
-
-
-def build_service_handler():
- # build a method handler
- method_handler = RpcMethodHandler.client_stream(
- GreeterServiceServicer().client_stream,
- method_name="clientStream",
- request_deserializer=greeter_pb2.GreeterRequest.FromString,
- response_serializer=greeter_pb2.GreeterReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.data.Greeter",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build server config
- service_handler = build_service_handler()
- service_config = ServiceConfig(service_handler=service_handler, host="127.0.0.1", port=50051)
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/samples/stream/server_stream/__init__.py b/samples/stream/server_stream/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/samples/stream/server_stream/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/samples/stream/server_stream/client.py b/samples/stream/server_stream/client.py
deleted file mode 100644
index 383b226..0000000
--- a/samples/stream/server_stream/client.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ReferenceConfig
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceStub:
- def __init__(self, client: dubbo.Client):
- self.stream_unary = client.server_stream(
- method_name="serverStream",
- request_serializer=greeter_pb2.GreeterRequest.SerializeToString,
- response_deserializer=greeter_pb2.GreeterReply.FromString,
- )
-
- def server_stream(self, values):
- return self.stream_unary(values)
-
-
-if __name__ == "__main__":
- reference_config = ReferenceConfig.from_url("tri://127.0.0.1:50051/org.apache.dubbo.samples.data.Greeter")
- dubbo_client = dubbo.Client(reference_config)
- stub = GreeterServiceStub(dubbo_client)
-
- stream = stub.server_stream(greeter_pb2.GreeterRequest(name="hello world from dubbo-python"))
-
- for i in stream:
- print(f"Received response: {i.message}")
diff --git a/samples/stream/server_stream/server.py b/samples/stream/server_stream/server.py
deleted file mode 100644
index bce6aca..0000000
--- a/samples/stream/server_stream/server.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from samples.proto import greeter_pb2
-
-
-class GreeterServiceServicer:
- def server_stream(self, stream):
- request = stream.read()
- print(f"Received request: {request.name}")
- response = request.name.split(" ")
- for i in response:
- yield greeter_pb2.GreeterReply(message=i)
-
-
-def build_server_handler():
- # build a method handler
- method_handler = RpcMethodHandler.server_stream(
- GreeterServiceServicer().server_stream,
- method_name="serverStream",
- request_deserializer=greeter_pb2.GreeterRequest.FromString,
- response_serializer=greeter_pb2.GreeterReply.SerializeToString,
- )
- # build a service handler
- service_handler = RpcServiceHandler(
- service_name="org.apache.dubbo.samples.data.Greeter",
- method_handlers=[method_handler],
- )
- return service_handler
-
-
-if __name__ == "__main__":
- # build a service config
- service_handler = build_server_handler()
- service_config = ServiceConfig(service_handler, host="127.0.0.1", port=50051)
-
- # start the server
- server = dubbo.Server(service_config).start()
-
- input("Press Enter to stop the server...\n")
diff --git a/scripts/build.sh b/scripts/build.sh
deleted file mode 100755
index 51d538d..0000000
--- a/scripts/build.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-echo "Building the project..."
-
-hatch build
-
-echo "Finished building the project."
\ No newline at end of file
diff --git a/scripts/check.sh b/scripts/check.sh
deleted file mode 100755
index 015db37..0000000
--- a/scripts/check.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-export SOURCE_FILES="src/dubbo tests"
-
-# check code style
-echo "Checking code style..."
-ruff format $SOURCE_FILES --diff
-ruff check --output-format=github $SOURCE_FILES --config=pyproject.toml
-
-# TODO:Temporarily disable mypy check, because it's too strict for now
-# mypy $SOURCE_FILES
-
-
-echo "Finished code style check."
diff --git a/scripts/install-dev.sh b/scripts/install-dev.sh
deleted file mode 100755
index 9bc2377..0000000
--- a/scripts/install-dev.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/sh
-
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Install dependencies for development
-REQUIREMENTS_DEV="requirements-dev.txt"
-
-# Upgrade pip
-pip install -U pip
-
-# Install dependencies
-pip install -r $REQUIREMENTS_DEV
-
diff --git a/scripts/rat.sh b/scripts/rat.sh
deleted file mode 100755
index d76d692..0000000
--- a/scripts/rat.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/sh
-
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-### Apache RAT license check script ###
-# This script downloads Apache RAT and runs it to check the license headers of the source files.
-
-set -e
-
-ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
-TMP_DIR="$(mktemp -d)"
-
-RAT_VERSION="0.16.1"
-RAT_JAR="${TMP_DIR}/apache-rat-${RAT_VERSION}.jar"
-
-
-cd "${ROOT_DIR}"
-
-# Set Java command
-if [ -x "${JAVA_HOME}/bin/java" ]; then
- java_cmd="${JAVA_HOME}/bin/java"
-else
- java_cmd="java"
-fi
-
-
-# Download Apache RAT jar
-echo "Downloading Apache RAT ${RAT_VERSION}..."
-
-RAT_URL="https://repo1.maven.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar"
-JAR_PART="${RAT_JAR}.part"
-
-if command -v curl &> /dev/null; then
- curl -L --silent "${RAT_URL}" -o "${JAR_PART}" && mv "${JAR_PART}" "${RAT_JAR}"
-elif command -v wget &> /dev/null; then
- wget --quiet "${RAT_URL}" -O "${JAR_PART}" && mv "${JAR_PART}" "${RAT_JAR}"
-else
- echo "Neither curl nor wget found."
- exit 1
-fi
-
-unzip -tq "${RAT_JAR}" > /dev/null
-if [ $? -ne 0 ]; then
- echo "Downloaded Apache RAT jar is invalid"
- exit 1
-fi
-
-echo "Downloaded Apache RAT ${RAT_VERSION} successfully."
-
-
-# Run Apache RAT
-echo "Running Apache license check, this may take a while..."
-${java_cmd} -jar ${RAT_JAR} -d ${ROOT_DIR} -E "${ROOT_DIR}/.license-ignore" > "${TMP_DIR}/rat-report.txt"
-
-
-# Check the result
-if [ $? -ne 0 ]; then
- echo "RAT exited abnormally"
- exit 1
-elif grep -q "??" "${TMP_DIR}/rat-report.txt"; then
- echo >&2 "Could not find Apache license headers in the following files:"
- grep "??" "${TMP_DIR}/rat-report.txt" >&2
- exit 1
-else
- echo "Apache license check passed."
-fi
diff --git a/scripts/test.sh b/scripts/test.sh
deleted file mode 100755
index bded3cf..0000000
--- a/scripts/test.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-# Run tests
-coverage run -m pytest
-
-# Generate coverage report
-# TODO: shut down the coverage report for now, because the current code coverage is too low
-# coverage report --show-missing --skip-covered --fail-under=100
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..c29c017
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+# coding: utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+
+
+"""
+Python Dubbo Library Client Server - Setup
+
+Created
+ 2015-4-10 by Joe - https://github.com/JoeCao
+"""
+
+
+import os
+
+from setuptools import setup, find_packages
+
+THISDIR = os.path.dirname(os.path.abspath(__file__))
+os.chdir(THISDIR)
+
+VERSION = open("version.txt").readline().strip()
+HOMEPAGE = "https://github.com/ofpay/dubbo-client-py"
+DOWNLOAD_BASEURL = "https://github.com/ofpay/dubbo-client-py/raw/master/dist/"
+DOWNLOAD_URL = DOWNLOAD_BASEURL + "dubbo-client-%s-py2.7.egg" % VERSION
+
+setup(
+ name="dubbo-client",
+ version=VERSION,
+ description=(
+ "Python Dubbo Client"
+ ),
+ long_description=open("README.md", encoding='utf-8').read(),
+ keywords=(
+ "Dubbo, JSON-RPC, JSON, RPC, Client,"
+ "HTTP-Client, Remote Procedure Call, JavaScript Object Notation, "
+ ),
+ author="Joe Cao",
+ author_email="chinalibra@gmail.com",
+ url=HOMEPAGE,
+ download_url=DOWNLOAD_URL,
+ packages=find_packages(),
+ classifiers=[
+ # "Development Status :: 1 - Planning",
+ # "Development Status :: 2 - Pre-Alpha",
+ # "Development Status :: 3 - Alpha",
+ "Development Status :: 4 - Beta",
+ # "Development Status :: 5 - Production/Stable",
+ "Environment :: Web Environment",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Communications",
+ "Topic :: System :: Networking",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ install_requires=["kazoo>=2.0", "python-jsonrpc>=0.7.3"],
+)
diff --git a/src/dubbo/__about__.py b/src/dubbo/__about__.py
deleted file mode 100644
index 7302839..0000000
--- a/src/dubbo/__about__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__version__ = "3.0.0b1"
diff --git a/src/dubbo/__init__.py b/src/dubbo/__init__.py
deleted file mode 100644
index ded7e25..0000000
--- a/src/dubbo/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from .bootstrap import Dubbo
-from .client import Client
-from .server import Server
-
-__all__ = ["Dubbo", "Client", "Server"]
diff --git a/src/dubbo/bootstrap.py b/src/dubbo/bootstrap.py
deleted file mode 100644
index 5792195..0000000
--- a/src/dubbo/bootstrap.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import threading
-from typing import Optional
-
-from dubbo.classes import SingletonBase
-from dubbo.configs import (
- ApplicationConfig,
- LoggerConfig,
- ReferenceConfig,
- RegistryConfig,
-)
-from dubbo.constants import common_constants
-from dubbo.loggers import loggerFactory
-
-
-class Dubbo(SingletonBase):
- """
- Dubbo class. This class is used to initialize the Dubbo framework.
- """
-
- def __init__(
- self,
- application_config: Optional[ApplicationConfig] = None,
- registry_config: Optional[RegistryConfig] = None,
- logger_config: Optional[LoggerConfig] = None,
- ):
- """
- Initialize a new Dubbo bootstrap.
- :param application_config: The application configuration.
- :type application_config: Optional[ApplicationConfig]
- :param registry_config: The registry configuration.
- :type registry_config: Optional[RegistryConfig]
- :param logger_config: The logger configuration.
- :type logger_config: Optional[LoggerConfig]
- """
- self._initialized = False
- self._global_lock = threading.Lock()
-
- self._application_config = application_config
- self._registry_config = registry_config
- self._logger_config = logger_config
-
- # check and set the default configuration
- self._check_default()
-
- # initialize the Dubbo framework
- self._initialize()
-
- @property
- def application_config(self) -> Optional[ApplicationConfig]:
- """
- Get the application configuration.
- :return: The application configuration.
- :rtype: Optional[ApplicationConfig]
- """
- return self._application_config
-
- @property
- def registry_config(self) -> Optional[RegistryConfig]:
- """
- Get the registry configuration.
- :return: The registry configuration.
- :rtype: Optional[RegistryConfig]
- """
- return self._registry_config
-
- @property
- def logger_config(self) -> Optional[LoggerConfig]:
- """
- Get the logger configuration.
- :return: The logger configuration.
- :rtype: Optional[LoggerConfig]
- """
- return self._logger_config
-
- def _check_default(self):
- """
- Check and set the default configuration.
- """
- # set default application configuration
- if not self._application_config:
- self._application_config = ApplicationConfig(common_constants.DUBBO_VALUE)
-
- if self._registry_config:
- if not self._registry_config.version and self.application_config.version:
- self._registry_config.version = self.application_config.version
-
- def _initialize(self):
- """
- Initialize the Dubbo framework.
- """
- with self._global_lock:
- if self._initialized:
- return
-
- # set logger configuration
- if self._logger_config:
- loggerFactory.set_config(self._logger_config)
-
- self._initialized = True
-
- def create_client(self, reference_config: ReferenceConfig):
- """
- Create a new Dubbo client.
- :param reference_config: The reference configuration.
- :type reference_config: ReferenceConfig
- """
- from dubbo import Client
-
- return Client(reference_config, self)
-
- def create_server(self, config):
- """
- Create a new Dubbo server.
- :param config: The service configuration.
- :type config: ServiceConfig
- """
- from dubbo import Server
-
- return Server(config, self)
diff --git a/src/dubbo/classes.py b/src/dubbo/classes.py
deleted file mode 100644
index 8d87299..0000000
--- a/src/dubbo/classes.py
+++ /dev/null
@@ -1,246 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import abc
-import threading
-from typing import Any, Callable, Optional, Union
-
-from dubbo.types import DeserializingFunction, RpcType, RpcTypes, SerializingFunction
-
-__all__ = [
- "EOF",
- "SingletonBase",
- "MethodDescriptor",
- "ReadStream",
- "WriteStream",
- "ReadWriteStream",
-]
-
-
-class _EOF:
- """
- EOF is a class representing the end flag.
- """
-
- _repr_str = ""
-
- def __bool__(self):
- return False
-
- def __len__(self):
- return 0
-
- def __repr__(self) -> str:
- return self._repr_str
-
- def __str__(self) -> str:
- return self._repr_str
-
-
-# The EOF object -> global constant
-EOF = _EOF()
-
-
-class SingletonBase:
- """
- Singleton base class. This class ensures that only one instance of a derived class exists.
-
- This implementation is thread-safe.
- """
-
- _instance = None
- _instance_lock = threading.Lock()
-
- def __new__(cls, *args, **kwargs):
- """
- Create a new instance of the class if it does not exist.
- """
- if cls._instance is None:
- with cls._instance_lock:
- # double check
- if cls._instance is None:
- cls._instance = super().__new__(cls)
- return cls._instance
-
-
-class MethodDescriptor:
- """
- MethodDescriptor is a descriptor for a method.
- It contains the method name, the method, and the method's serialization and deserialization methods.
- """
-
- __slots__ = [
- "_callable_method",
- "_method_name",
- "_rpc_type",
- "_arg_serialization",
- "_return_serialization",
- ]
-
- def __init__(
- self,
- method_name: str,
- arg_serialization: tuple[Optional[SerializingFunction], Optional[DeserializingFunction]],
- return_serialization: tuple[Optional[SerializingFunction], Optional[DeserializingFunction]],
- rpc_type: Union[RpcType, RpcTypes, str] = RpcTypes.UNARY.value,
- callable_method: Optional[Callable] = None,
- ):
- """
- Initialize the method model.
-
- :param method_name:
- The name of the method.
- :type method_name: str
-
- :param arg_serialization:
- A tuple containing serialization and deserialization methods for the function's arguments.
- :type arg_serialization: Optional[Tuple[SerializingFunction, DeserializingFunction]]
-
- :param return_serialization:
- A tuple containing serialization and deserialization methods for the function's return values.
- :type return_serialization: Optional[Tuple[SerializingFunction, DeserializingFunction]]
-
- :param rpc_type:
- The RPC type. default is RpcTypes.UNARY.
- :type rpc_type: RpcType
-
- :param callable_method:
- The main callable method to be executed.
- :type callable_method: Optional[Callable]
- """
- self._method_name = method_name
- self._arg_serialization = arg_serialization
- self._return_serialization = return_serialization
- self._callable_method = callable_method
-
- if isinstance(rpc_type, str):
- rpc_type = RpcTypes.from_name(rpc_type)
- elif isinstance(rpc_type, RpcTypes):
- rpc_type = rpc_type.value
- elif not isinstance(rpc_type, RpcType):
- raise TypeError(f"rpc_type must be of type RpcType, RpcTypes, or str, not {type(rpc_type)}")
- self._rpc_type = rpc_type
-
- def get_method(self) -> Callable:
- """
- Get the callable method.
- :return: The callable method.
- :rtype: Callable
- """
- return self._callable_method
-
- def get_method_name(self) -> str:
- """
- Get the method name.
- :return: The method name.
- :rtype: str
- """
- return self._method_name
-
- def get_rpc_type(self) -> RpcType:
- """
- Get the RPC type.
- :return: The RPC type.
- :rtype: RpcType
- """
- return self._rpc_type
-
- def get_arg_serializer(self) -> Optional[SerializingFunction]:
- """
- Get the argument serializer.
- :return: The argument serializer. If not set, return None.
- :rtype: Optional[SerializingFunction]
- """
- return self._arg_serialization[0] if self._arg_serialization else None
-
- def get_arg_deserializer(self) -> Optional[DeserializingFunction]:
- """
- Get the argument deserializer.
- :return: The argument deserializer. If not set, return None.
- :rtype: Optional[DeserializingFunction]
- """
- return self._arg_serialization[1] if self._arg_serialization else None
-
- def get_return_serializer(self) -> Optional[SerializingFunction]:
- """
- Get the return value serializer.
- :return: The return value serializer. If not set, return None.
- :rtype: Optional[SerializingFunction]
- """
- return self._return_serialization[0] if self._return_serialization else None
-
- def get_return_deserializer(self) -> Optional[DeserializingFunction]:
- """
- Get the return value deserializer.
- :return: The return value deserializer. If not set, return None.
- :rtype: Optional[DeserializingFunction]
- """
- return self._return_serialization[1] if self._return_serialization else None
-
-
-class ReadStream(abc.ABC):
- """
- ReadStream is an abstract class for reading streams.
- """
-
- @abc.abstractmethod
- def read(self, *args, **kwargs) -> Any:
- """
- Read the stream.
- :param args: The arguments to pass to the read method.
- :param kwargs: The keyword arguments to pass to the read method.
- :return: The read value.
- """
- raise NotImplementedError()
-
-
-class WriteStream(abc.ABC):
- """
- WriteStream is an abstract class for writing streams.
- """
-
- @abc.abstractmethod
- def can_write_more(self) -> bool:
- """
- Check if the stream can write more data.
- :return: True if the stream can write more data, False otherwise.
- :rtype: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def write(self, *args, **kwargs) -> None:
- """
- Write to the stream.
- :param args: The arguments to pass to the write method.
- :param kwargs: The keyword arguments to pass to the write method.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def done_writing(self, **kwargs) -> None:
- """
- Done writing to the stream.
- :param kwargs: The keyword arguments to pass to the done
- """
- raise NotImplementedError()
-
-
-class ReadWriteStream(ReadStream, WriteStream, abc.ABC):
- """
- ReadWriteStream is an abstract class for reading and writing streams.
- """
-
- pass
diff --git a/src/dubbo/client.py b/src/dubbo/client.py
deleted file mode 100644
index 33e6264..0000000
--- a/src/dubbo/client.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import threading
-from typing import Optional
-
-from dubbo.bootstrap import Dubbo
-from dubbo.classes import MethodDescriptor
-from dubbo.configs import ReferenceConfig
-from dubbo.constants import common_constants
-from dubbo.extension import extensionLoader
-from dubbo.protocol import Invoker, Protocol
-from dubbo.proxy import RpcCallable, RpcCallableFactory
-from dubbo.proxy.callables import DefaultRpcCallableFactory
-from dubbo.registry.protocol import RegistryProtocol
-from dubbo.types import (
- DeserializingFunction,
- RpcTypes,
- SerializingFunction,
-)
-from dubbo.url import URL
-
-__all__ = ["Client"]
-
-
-class Client:
- def __init__(self, reference: ReferenceConfig, dubbo: Optional[Dubbo] = None):
- self._initialized = False
- self._global_lock = threading.RLock()
-
- self._dubbo = dubbo or Dubbo()
- self._reference = reference
-
- self._url: Optional[URL] = None
- self._protocol: Optional[Protocol] = None
- self._invoker: Optional[Invoker] = None
-
- self._callable_factory: RpcCallableFactory = DefaultRpcCallableFactory()
-
- # initialize the invoker
- self._initialize()
-
- def _initialize(self):
- """
- Initialize the invoker.
- """
- with self._global_lock:
- if self._initialized:
- return
-
- # get the protocol
- protocol = extensionLoader.get_extension(Protocol, self._reference.protocol)()
-
- registry_config = self._dubbo.registry_config
-
- self._protocol = RegistryProtocol(registry_config, protocol) if self._dubbo.registry_config else protocol
-
- # build url
- reference_url = self._reference.to_url()
- if registry_config:
- self._url = registry_config.to_url().copy()
- self._url.path = reference_url.path
- for k, v in reference_url.parameters.items():
- self._url.parameters[k] = v
- else:
- self._url = reference_url
-
- # create invoker
- self._invoker = self._protocol.refer(self._url)
-
- self._initialized = True
-
- def unary(
- self,
- method_name: str,
- request_serializer: Optional[SerializingFunction] = None,
- response_deserializer: Optional[DeserializingFunction] = None,
- ) -> RpcCallable:
- return self._callable(
- MethodDescriptor(
- method_name=method_name,
- arg_serialization=(request_serializer, None),
- return_serialization=(None, response_deserializer),
- rpc_type=RpcTypes.UNARY.value,
- )
- )
-
- def client_stream(
- self,
- method_name: str,
- request_serializer: Optional[SerializingFunction] = None,
- response_deserializer: Optional[DeserializingFunction] = None,
- ) -> RpcCallable:
- return self._callable(
- MethodDescriptor(
- method_name=method_name,
- arg_serialization=(request_serializer, None),
- return_serialization=(None, response_deserializer),
- rpc_type=RpcTypes.CLIENT_STREAM.value,
- )
- )
-
- def server_stream(
- self,
- method_name: str,
- request_serializer: Optional[SerializingFunction] = None,
- response_deserializer: Optional[DeserializingFunction] = None,
- ) -> RpcCallable:
- return self._callable(
- MethodDescriptor(
- method_name=method_name,
- arg_serialization=(request_serializer, None),
- return_serialization=(None, response_deserializer),
- rpc_type=RpcTypes.SERVER_STREAM.value,
- )
- )
-
- def bi_stream(
- self,
- method_name: str,
- request_serializer: Optional[SerializingFunction] = None,
- response_deserializer: Optional[DeserializingFunction] = None,
- ) -> RpcCallable:
- # create method descriptor
- return self._callable(
- MethodDescriptor(
- method_name=method_name,
- arg_serialization=(request_serializer, None),
- return_serialization=(None, response_deserializer),
- rpc_type=RpcTypes.BI_STREAM.value,
- )
- )
-
- def _callable(self, method_descriptor: MethodDescriptor) -> RpcCallable:
- """
- Generate a proxy for the given method
- :param method_descriptor: The method descriptor.
- :return: The proxy.
- :rtype: RpcCallable
- """
- # get invoker
- url = self._invoker.get_url()
-
- # clone url
- url = url.copy()
- url.parameters[common_constants.METHOD_KEY] = method_descriptor.get_method_name()
- # set method descriptor
- url.attributes[common_constants.METHOD_DESCRIPTOR_KEY] = method_descriptor
-
- # create proxy
- return self._callable_factory.get_callable(self._invoker, url)
diff --git a/src/dubbo/cluster/__init__.py b/src/dubbo/cluster/__init__.py
deleted file mode 100644
index 0260f69..0000000
--- a/src/dubbo/cluster/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import Cluster, Directory, LoadBalance
-
-__all__ = ["Cluster", "Directory", "LoadBalance"]
diff --git a/src/dubbo/cluster/_interfaces.py b/src/dubbo/cluster/_interfaces.py
deleted file mode 100644
index f6c999b..0000000
--- a/src/dubbo/cluster/_interfaces.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import abc
-from typing import Optional
-
-from dubbo.node import Node
-from dubbo.protocol import Invocation, Invoker
-
-__all__ = ["Directory", "LoadBalance", "Cluster"]
-
-
-class Directory(Node, abc.ABC):
- """
- Directory interface.
- """
-
- @abc.abstractmethod
- def get_list(self, invocation: Invocation) -> list[Invoker]:
- """
- List the directory.
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The list of invokers.
- :rtype: List
- """
- raise NotImplementedError()
-
-
-class LoadBalance(abc.ABC):
- """
- The load balance interface.
- """
-
- @abc.abstractmethod
- def select(self, invokers: list[Invoker], invocation: Invocation) -> Optional[Invoker]:
- """
- Select an invoker from the list.
- :param invokers: The invokers.
- :type invokers: List[Invoker]
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The selected invoker. If no invoker is selected, return None.
- :rtype: Optional[Invoker]
- """
- raise NotImplementedError()
-
-
-class Cluster(abc.ABC):
- """
- Cluster interface.
- """
-
- @abc.abstractmethod
- def join(self, directory: Directory) -> Invoker:
- """
- Join the cluster.
- :param directory: The directory.
- :type directory: Directory
- :return: The cluster invoker.
- :rtype: Invoker
- """
- raise NotImplementedError()
diff --git a/src/dubbo/cluster/directories.py b/src/dubbo/cluster/directories.py
deleted file mode 100644
index cb13a33..0000000
--- a/src/dubbo/cluster/directories.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.cluster import Directory
-from dubbo.protocol import Invoker, Protocol
-from dubbo.registry import NotifyListener, Registry
-from dubbo.url import URL
-
-
-class RegistryDirectory(Directory, NotifyListener):
- """
- The registry directory.
- """
-
- def __init__(self, registry: Registry, protocol: Protocol, url: URL):
- self._registry = registry
- self._protocol = protocol
-
- self._url = url
-
- self._invokers: dict[str, Invoker] = {}
-
- # subscribe
- self._registry.subscribe(url, self)
-
- def get_list(self, invocation) -> list[Invoker]:
- return list(self._invokers.values())
-
- def notify(self, urls: list[URL]) -> None:
- old_invokers = self._invokers
- self._invokers = {}
-
- # create new invokers
- for url in urls:
- k = str(url)
- if k in old_invokers:
- self._invokers[k] = old_invokers[k]
- del old_invokers[k]
- else:
- self._invokers[k] = self._protocol.refer(url)
-
- # destroy old invokers
- for invoker in old_invokers.values():
- invoker.destroy()
-
- def get_url(self) -> URL:
- return self._url
-
- def is_available(self) -> bool:
- return self._registry.is_available()
-
- def destroy(self) -> None:
- self._registry.destroy()
diff --git a/src/dubbo/cluster/failfast_cluster.py b/src/dubbo/cluster/failfast_cluster.py
deleted file mode 100644
index 85ed01e..0000000
--- a/src/dubbo/cluster/failfast_cluster.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.cluster import Cluster, Directory, LoadBalance
-from dubbo.cluster.loadbalances import CpuLoadBalance
-from dubbo.constants import common_constants
-from dubbo.extension import extensionLoader
-from dubbo.protocol import Invoker, Result
-from dubbo.protocol.triple.exceptions import RpcError
-from dubbo.url import URL
-
-
-class FailfastInvoker(Invoker):
- """
- FailfastInvoker
- """
-
- def __init__(self, directory, url: URL):
- self._directory = directory
-
- self._load_balance = extensionLoader.get_extension(
- LoadBalance, url.parameters.get(common_constants.LOADBALANCE_KEY, "random")
- )()
-
- if isinstance(self._load_balance, CpuLoadBalance):
- self._load_balance.set_monitor(directory)
-
- def invoke(self, invocation) -> Result:
- # get the invokers
- invokers = self._directory.get_list(invocation)
- if not invokers:
- raise RpcError("No provider available for the service")
-
- # select the invoker
- invoker = self._load_balance.select(invokers, invocation)
-
- # invoke the invoker
- return invoker.invoke(invocation)
-
- def get_url(self) -> URL:
- return self._directory.get_url()
-
- def is_available(self) -> bool:
- return self._directory.is_available()
-
- def destroy(self):
- self._directory.destroy()
-
-
-class FailfastCluster(Cluster):
- """
- Execute exactly once, which means this policy will throw an exception immediately in case of an invocation error.
- Usually used for non-idempotent write operations
- """
-
- def join(self, directory: Directory) -> Invoker:
- return FailfastInvoker(directory, directory.get_url())
diff --git a/src/dubbo/cluster/loadbalances.py b/src/dubbo/cluster/loadbalances.py
deleted file mode 100644
index 850d227..0000000
--- a/src/dubbo/cluster/loadbalances.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import abc
-import random
-from typing import Optional
-
-from dubbo.cluster import LoadBalance
-from dubbo.cluster.monitor.cpu import CpuMonitor
-from dubbo.protocol import Invocation, Invoker
-
-
-class AbstractLoadBalance(LoadBalance, abc.ABC):
- """
- The abstract load balance.
- """
-
- def select(self, invokers: list[Invoker], invocation: Invocation) -> Optional[Invoker]:
- if not invokers:
- return None
-
- if len(invokers) == 1:
- return invokers[0]
-
- return self.do_select(invokers, invocation)
-
- @abc.abstractmethod
- def do_select(self, invokers: list[Invoker], invocation: Invocation) -> Optional[Invoker]:
- """
- Do select an invoker from the list.
- :param invokers: The invokers.
- :type invokers: List[Invoker]
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The selected invoker. If no invoker is selected, return None.
- :rtype: Optional[Invoker]
- """
- raise NotImplementedError()
-
-
-class RandomLoadBalance(AbstractLoadBalance):
- """
- Random load balance.
- """
-
- def do_select(self, invokers: list[Invoker], invocation: Invocation) -> Optional[Invoker]:
- randint = random.randint(0, len(invokers) - 1)
- return invokers[randint]
-
-
-class CpuLoadBalance(AbstractLoadBalance):
- """
- CPU load balance.
- """
-
- def __init__(self):
- self._monitor: Optional[CpuMonitor] = None
-
- def set_monitor(self, monitor: CpuMonitor) -> None:
- """
- Set the CPU monitor.
- :param monitor: The CPU monitor.
- :type monitor: CpuMonitor
- """
- self._monitor = monitor
-
- def do_select(self, invokers: list[Invoker], invocation: Invocation) -> Optional[Invoker]:
- # get the CPU usage
- cpu_usages = self._monitor.get_cpu_usage(invokers)
- # Select the caller with the lowest CPU usage, 0 means CPU usage is unknown.
- available_invokers = []
- unknown_invokers = []
-
- for invoker, cpu_usage in cpu_usages.items():
- if cpu_usage == 0:
- unknown_invokers.append((cpu_usage, invoker))
- else:
- available_invokers.append((cpu_usage, invoker))
-
- if available_invokers:
- # sort and select the invoker with the lowest CPU usage
- available_invokers.sort(key=lambda x: x[0])
- return available_invokers[0][1]
- elif unknown_invokers:
- # get the invoker with unknown CPU usage randomly
- randint = random.randint(0, len(unknown_invokers) - 1)
- return unknown_invokers[randint][1]
- else:
- return None
diff --git a/src/dubbo/cluster/monitor/__init__.py b/src/dubbo/cluster/monitor/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/src/dubbo/cluster/monitor/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/src/dubbo/cluster/monitor/cpu.py b/src/dubbo/cluster/monitor/cpu.py
deleted file mode 100644
index 4df5d51..0000000
--- a/src/dubbo/cluster/monitor/cpu.py
+++ /dev/null
@@ -1,165 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import threading
-
-from dubbo.cluster.directories import RegistryDirectory
-from dubbo.loggers import loggerFactory
-from dubbo.protocol import Invoker, Protocol
-from dubbo.protocol.invocation import RpcInvocation
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from dubbo.registry import Registry
-from dubbo.url import URL
-from dubbo.utils import CpuUtils
-
-_LOGGER = loggerFactory.get_logger()
-
-_cpu_invocation = RpcInvocation(
- "org.apache.dubbo.MetricsService",
- "cpu",
- str(1).encode("utf-8"),
-)
-
-
-class CpuMonitor(RegistryDirectory):
- """
- The CPU monitor.
- """
-
- def __init__(self, registry: Registry, protocol: Protocol, url: URL):
- super().__init__(registry, protocol, url)
-
- # interval
- self._interval = 5
-
- # about CPU usage
- self._usages_lock = threading.Lock()
- self._cpu_usages: dict[Invoker, float] = {}
-
- # running invokers
- self._running_invokers: dict[str, Invoker] = {}
-
- # thread
- self._started = False
- self._thread: threading.Thread = threading.Thread(target=self._monitor_cpu, daemon=True)
- self._stop_event: threading.Event = threading.Event()
-
- # start the monitor
- self.start()
-
- def start(self) -> None:
- """
- Start the monitor.
- """
- if self._stop_event.is_set():
- raise RuntimeError("The monitor has been stopped.")
- elif self._started:
- return
-
- self._started = True
- self._thread.start()
- _LOGGER.info("The CPU monitor has been started.")
-
- def stop(self) -> None:
- """
- Stop the monitor.
- """
- if self._stop_event.is_set():
- return
- # notify the thread to stop
- self._stop_event.set()
-
- def _monitor_cpu(self) -> None:
- """
- Monitor the CPU usage.
- """
- while True:
- # get available invokers
- available_invokers = {url: invoker for url, invoker in self._invokers.items() if invoker.is_available()}
-
- # update the running invokers
- self._running_invokers = available_invokers
-
- # update the CPU usage
- with self._usages_lock:
- self._cpu_usages = {
- invoker: usage
- for invoker, usage in self._cpu_usages.items()
- if invoker in available_invokers.values()
- }
-
- # get the CPU usage for each invoker
- for url, invoker in self._running_invokers.items():
- if invoker.is_available():
- try:
- result = invoker.invoke(_cpu_invocation)
- cpu_usage = float(result.value().decode("utf-8"))
- self._cpu_usages[invoker] = cpu_usage
- except Exception:
- _LOGGER.error("Failed to get the CPU usage for invoker %s: %s", url, str)
- # remove the cpu usage
- self._remove_cpu_usage(invoker)
-
- # wait for the interval or stop
- if self._stop_event.wait(self._interval):
- _LOGGER.info("The CPU monitor has been stopped.")
- break
-
- def get_cpu_usage(self, invokers: list[Invoker]) -> dict[Invoker, float]:
- """
- Get the CPU usage for the invoker.
- :param invokers: The invokers.
- :type invokers: List[Invoker]
- :return: The CPU usage.
- :rtype: Dict[Invoker, float]
- """
- with self._usages_lock:
- return {invoker: self._cpu_usages.get(invoker, 0) for invoker in invokers}
-
- def _remove_cpu_usage(self, invoker: Invoker) -> None:
- with self._usages_lock:
- self._cpu_usages.pop(invoker)
-
-
-class CpuInnerRpcHandler:
- """
- The CPU inner RPC handler.
- """
-
- @staticmethod
- def get_service_handler() -> RpcServiceHandler:
- """
- Get the service handler.
- :return: The service handler.
- :rtype: RpcServiceHandler
- """
- return RpcServiceHandler(
- "org.apache.dubbo.MetricsService",
- [
- RpcMethodHandler.unary(CpuInnerRpcHandler.get_cpu_usage, method_name="cpu"),
- ],
- )
-
- @staticmethod
- def get_cpu_usage(interval) -> bytes:
- """
- Get the CPU usage.
- :param interval: The interval.
- :type interval: bytes
- :return: The CPU usage.
- :rtype: bytes
- """
- float_value = CpuUtils.get_total_cpu_usage(interval=int(interval.decode("utf-8")))
- return str(float_value).encode("utf-8")
diff --git a/src/dubbo/compression/__init__.py b/src/dubbo/compression/__init__.py
deleted file mode 100644
index eb01689..0000000
--- a/src/dubbo/compression/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import Compressor, Decompressor
-from .bzip2s import Bzip2
-from .gzips import Gzip
-from .identities import Identity
-
-__all__ = ["Compressor", "Decompressor", "Identity", "Gzip", "Bzip2"]
diff --git a/src/dubbo/compression/_interfaces.py b/src/dubbo/compression/_interfaces.py
deleted file mode 100644
index d7a8513..0000000
--- a/src/dubbo/compression/_interfaces.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-
-__all__ = ["MessageEncoding", "Compressor", "Decompressor"]
-
-
-class MessageEncoding(abc.ABC):
- """
- The message encoding interface.
- """
-
- @classmethod
- @abc.abstractmethod
- def get_message_encoding(cls) -> str:
- """
- Get message encoding of current compression
- :return: The message encoding.
- :rtype: str
- """
- raise NotImplementedError()
-
-
-class Compressor(MessageEncoding, abc.ABC):
- """
- The compression interface.
- """
-
- @abc.abstractmethod
- def compress(self, data: bytes) -> bytes:
- """
- Compress the data.
- :param data: The data to compress.
- :type data: bytes
- :return: The compressed data.
- :rtype: bytes
- """
- raise NotImplementedError()
-
-
-class Decompressor(MessageEncoding, abc.ABC):
- """
- The decompressor interface.
- """
-
- @abc.abstractmethod
- def decompress(self, data: bytes) -> bytes:
- """
- Decompress the data.
- :param data: The data to decompress.
- :type data: bytes
- :return: The decompressed data.
- :rtype: bytes
- """
- raise NotImplementedError()
diff --git a/src/dubbo/compression/bzip2s.py b/src/dubbo/compression/bzip2s.py
deleted file mode 100644
index 92b2bf0..0000000
--- a/src/dubbo/compression/bzip2s.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import bz2
-
-from dubbo.compression import Compressor, Decompressor
-
-
-class Bzip2(Compressor, Decompressor):
- """
- The BZIP2 compression and decompressor.
- """
-
- _MESSAGE_ENCODING = "bzip2"
-
- @classmethod
- def get_message_encoding(cls) -> str:
- """
- Get message encoding of current compression
- :return: The message encoding.
- :rtype: str
- """
- return cls._MESSAGE_ENCODING
-
- def compress(self, data: bytes) -> bytes:
- """
- Compress the data.
- :param data: The data to compress.
- :type data: bytes
- :return: The compressed data.
- :rtype: bytes
- """
- return bz2.compress(data)
-
- def decompress(self, data: bytes) -> bytes:
- """
- Decompress the data.
- :param data: The data to decompress.
- :type data: bytes
- :return: The decompressed data.
- :rtype: bytes
- """
- return bz2.decompress(data)
diff --git a/src/dubbo/compression/gzips.py b/src/dubbo/compression/gzips.py
deleted file mode 100644
index 4b9ac59..0000000
--- a/src/dubbo/compression/gzips.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import gzip
-
-from dubbo.compression import Compressor, Decompressor
-
-__all__ = ["Gzip"]
-
-
-class Gzip(Compressor, Decompressor):
- """
- The GZIP compression and decompressor.
- """
-
- _MESSAGE_ENCODING = "gzip"
-
- @classmethod
- def get_message_encoding(cls) -> str:
- """
- Get message encoding of current compression
- :return: The message encoding.
- :rtype: str
- """
- return cls._MESSAGE_ENCODING
-
- def compress(self, data: bytes) -> bytes:
- """
- Compress the data.
- :param data: The data to compress.
- :type data: bytes
- :return: The compressed data.
- :rtype: bytes
- """
- return gzip.compress(data)
-
- def decompress(self, data: bytes) -> bytes:
- """
- Decompress the data.
- :param data: The data to decompress.
- :type data: bytes
- :return: The decompressed data.
- :rtype: bytes
- """
- return gzip.decompress(data)
diff --git a/src/dubbo/compression/identities.py b/src/dubbo/compression/identities.py
deleted file mode 100644
index 4f8d085..0000000
--- a/src/dubbo/compression/identities.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.classes import SingletonBase
-from dubbo.compression import Compressor, Decompressor
-
-__all__ = ["Identity"]
-
-
-class Identity(Compressor, Decompressor, SingletonBase):
- """
- The identity compression and decompressor.It does not compress or decompress the data.
- """
-
- _MESSAGE_ENCODING = "identity"
-
- @classmethod
- def get_message_encoding(cls) -> str:
- """
- Get message encoding of current compression
- :return: The message encoding.
- :rtype: str
- """
- return cls._MESSAGE_ENCODING
-
- def compress(self, data: bytes) -> bytes:
- """
- Compress the data.
- :param data: The data to compress.
- :type data: bytes
- :return: The compressed data.
- :rtype: bytes
- """
- return data
-
- def decompress(self, data: bytes) -> bytes:
- """
- Decompress the data.
- :param data: The data to decompress.
- :type data: bytes
- :return: The decompressed data.
- :rtype: bytes
- """
- return data
diff --git a/src/dubbo/configs.py b/src/dubbo/configs.py
deleted file mode 100644
index f7a85d7..0000000
--- a/src/dubbo/configs.py
+++ /dev/null
@@ -1,911 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import abc
-from dataclasses import dataclass
-from typing import Optional, Union
-
-from dubbo.constants import (
- common_constants,
- config_constants,
- logger_constants,
- registry_constants,
-)
-from dubbo.proxy.handlers import RpcServiceHandler
-from dubbo.url import URL, create_url
-from dubbo.utils import NetworkUtils
-
-__all__ = [
- "ApplicationConfig",
- "ReferenceConfig",
- "ServiceConfig",
- "RegistryConfig",
- "LoggerConfig",
-]
-
-
-class AbstractConfig(abc.ABC):
- """
- Abstract configuration class.
- """
-
- __slots__ = ["id"]
-
- def __init__(self):
- # Identifier for this configuration.
- self.id: Optional[str] = None
-
-
-class ApplicationConfig(AbstractConfig):
- """
- Configuration for the dubbo application.
- """
-
- __slots__ = [
- "_name",
- "_version",
- "_owner",
- "_organization",
- "_architecture",
- "_environment",
- ]
-
- def __init__(
- self,
- name: str,
- version: Optional[str] = None,
- owner: Optional[str] = None,
- organization: Optional[str] = None,
- architecture: Optional[str] = None,
- environment: Optional[str] = None,
- ):
- """
- Initialize the application configuration.
- :param name: The name of the application.
- :type name: str
- :param version: The version of the application.
- :type version: Optional[str]
- :param owner: The owner of the application.
- :type owner: Optional[str]
- :param organization: The organization(BU) of the application.
- :type organization: Optional[str]
- :param architecture: The architecture of the application.
- :type architecture: Optional[str]
- :param environment: The environment of the application. e.g. dev, test, prod.
- :type environment: Optional[str]
- """
- super().__init__()
-
- self._name = name
- self._version = version
- self._owner = owner
- self._organization = organization
- self._architecture = architecture
-
- self._environment = self._ensure_environment(environment)
-
- @property
- def name(self) -> str:
- """
- Get the name of the application.
- :return: The name of the application.
- :rtype: str
- """
- return self._name
-
- @name.setter
- def name(self, name: str) -> None:
- """
- Set the name of the application.
- :param name: The name of the application.
- :type name: str
- """
- self._name = name
-
- @property
- def version(self) -> Optional[str]:
- """
- Get the version of the application.
- :return: The version of the application.
- :rtype: Optional[str]
- """
- return self._version
-
- @version.setter
- def version(self, version: str) -> None:
- """
- Set the version of the application.
- :param version: The version of the application.
- :type version: str
- """
- self._version = version
-
- @property
- def owner(self) -> Optional[str]:
- """
- Get the owner of the application.
- :return: The owner of the application.
- :rtype: Optional[str]
- """
- return self._owner
-
- @owner.setter
- def owner(self, owner: str) -> None:
- """
- Set the owner of the application.
- :param owner: The owner of the application.
- :type owner: str
- """
- self._owner = owner
-
- @property
- def organization(self) -> Optional[str]:
- """
- Get the organization(BU) of the application.
- :return: The organization(BU) of the application.
- :rtype: Optional[str]
- """
- return self._organization
-
- @organization.setter
- def organization(self, organization: str) -> None:
- """
- Set the organization(BU) of the application.
- :param organization: The organization(BU) of the application.
- :type organization: str
- """
- self._organization = organization
-
- @property
- def architecture(self) -> Optional[str]:
- """
- Get the architecture of the application.
- :return: The architecture of the application.
- :rtype: Optional[str]
- """
- return self._architecture
-
- @architecture.setter
- def architecture(self, architecture: str) -> None:
- """
- Set the architecture of the application.
- :param architecture: The architecture of the application.
- :type architecture: str
- """
- self._architecture = architecture
-
- @property
- def environment(self) -> str:
- """
- Get the environment of the application.
- :return: The environment of the application.
- :rtype: str
- """
- return self._environment
-
- @environment.setter
- def environment(self, environment: str) -> None:
- """
- Set the environment of the application.
- :param environment: The environment of the application.
- :type environment: str
- """
- self._environment = self._ensure_environment(environment)
-
- @staticmethod
- def _ensure_environment(environment: Optional[str]) -> str:
- """
- Ensure the environment is valid.
- :param environment: The environment.
- :type environment: Optional[str]
- :return: The environment. If the environment is None, return the default environment.
- :rtype: str
- """
- if not environment:
- return config_constants.PRODUCTION_ENVIRONMENT
-
- # ignore case
- environment = environment.lower()
-
- allowed_environments = [
- config_constants.TEST_ENVIRONMENT,
- config_constants.DEVELOPMENT_ENVIRONMENT,
- config_constants.PRODUCTION_ENVIRONMENT,
- ]
-
- if environment not in allowed_environments:
- raise ValueError(
- f"Unsupported environment: {environment}, "
- f"only support {allowed_environments}, "
- f"default is {config_constants.PRODUCTION_ENVIRONMENT}."
- )
-
- return environment
-
-
-class ReferenceConfig(AbstractConfig):
- """
- Configuration for the dubbo reference.
- """
-
- __slots__ = ["_protocol", "_service", "_host", "_port"]
-
- def __init__(
- self,
- protocol: str,
- service: str,
- host: Optional[str] = None,
- port: Optional[int] = None,
- ):
- """
- Initialize the reference configuration.
- :param protocol: The protocol of the server.
- :type protocol: str
- :param service: The name of the server.
- :type service: str
- :param host: The host of the server.
- :type host: Optional[str]
- :param port: The port of the server.
- :type port: Optional[int]
- """
- super().__init__()
- self._protocol = protocol
- self._service = service
- self._host = host
- self._port = port
-
- @property
- def protocol(self) -> str:
- """
- Get the protocol of the server.
- :return: The protocol of the server.
- :rtype: str
- """
- return self._protocol
-
- @protocol.setter
- def protocol(self, protocol: str) -> None:
- """
- Set the protocol of the server.
- :param protocol: The protocol of the server.
- :type protocol: str
- """
- self._protocol = protocol
-
- @property
- def service(self) -> str:
- """
- Get the name of the service.
- :return: The name of the service.
- :rtype: str
- """
- return self._service
-
- @service.setter
- def service(self, service: str) -> None:
- """
- Set the name of the service.
- :param service: The name of the service.
- :type service: str
- """
- self._service = service
-
- @property
- def host(self) -> Optional[str]:
- """
- Get the host of the server.
- :return: The host of the server.
- :rtype: Optional[str]
- """
- return self._host
-
- @host.setter
- def host(self, host: str) -> None:
- """
- Set the host of the server.
- :param host: The host of the server.
- :type host: str
- """
- self._host = host
-
- @property
- def port(self) -> Optional[int]:
- """
- Get the port of the server.
- :return: The port of the server.
- :rtype: Optional[int]
- """
- return self._port
-
- @port.setter
- def port(self, port: int) -> None:
- """
- Set the port of the server.
- :param port: The port of the server.
- :type port: int
- """
- self._port = port
-
- def to_url(self) -> URL:
- """
- Convert the reference configuration to a URL.
- :return: The URL.
- :rtype: URL
- """
- return URL(
- scheme=self.protocol,
- host=self.host,
- port=self.port,
- path=self.service,
- parameters={common_constants.SERVICE_KEY: self.service},
- )
-
- @classmethod
- def from_url(cls, url: Union[str, URL]) -> "ReferenceConfig":
- """
- Create a reference configuration from a URL.
- :param url: The URL.
- :type url: Union[str,URL]
- :return: The reference configuration.
- :rtype: ReferenceConfig
- """
- if isinstance(url, str):
- url = create_url(url)
- return cls(
- protocol=url.scheme,
- service=url.parameters.get(common_constants.SERVICE_KEY, url.path),
- host=url.host,
- port=url.port,
- )
-
-
-class ServiceConfig(AbstractConfig):
- """
- Configuration for the dubbo service.
- """
-
- def __init__(
- self,
- service_handler: RpcServiceHandler,
- host: Optional[str] = None,
- port: Optional[int] = None,
- protocol: Optional[str] = None,
- ):
- super().__init__()
-
- self._service_handler = service_handler
- self._host = host or NetworkUtils.get_local_address() or common_constants.LOCAL_HOST_VALUE
- self._port = port or common_constants.DEFAULT_PORT
- self._protocol = protocol or common_constants.TRIPLE_SHORT
-
- @property
- def service_handler(self) -> RpcServiceHandler:
- """
- Get the service handler.
- :return: The service handler.
- :rtype: RpcServiceHandler
- """
- return self._service_handler
-
- @service_handler.setter
- def service_handler(self, service_handler: RpcServiceHandler) -> None:
- """
- Set the service handler.
- :param service_handler: The service handler.
- :type service_handler: RpcServiceHandler
- """
- self._service_handler = service_handler
-
- @property
- def host(self) -> str:
- """
- Get the host of the service.
- :return: The host of the service.
- :rtype: str
- """
- return self._host
-
- @host.setter
- def host(self, host: str) -> None:
- """
- Set the host of the service.
- :param host: The host of the service.
- :type host: str
- """
- self._host = host
-
- @property
- def port(self) -> int:
- """
- Get the port of the service.
- :return: The port of the service.
- :rtype: int
- """
- return self._port
-
- @port.setter
- def port(self, port: int) -> None:
- """
- Set the port of the service.
- :param port: The port of the service.
- :type port: int
- """
- self._port = port
-
- @property
- def protocol(self) -> str:
- """
- Get the protocol of the service.
- :return: The protocol of the service.
- :rtype: str
- """
- return self._protocol
-
- @protocol.setter
- def protocol(self, protocol: str) -> None:
- """
- Set the protocol of the service.
- :param protocol: The protocol of the service.
- :type protocol: str
- """
- self._protocol = protocol
-
- def to_url(self) -> URL:
- """
- Convert the service configuration to a URL.
- :return: The URL.
- :rtype: URL
- """
- return URL(
- scheme=self.protocol,
- host=self.host,
- port=self.port,
- parameters={common_constants.SERVICE_KEY: self.service_handler.service_name},
- attributes={common_constants.SERVICE_HANDLER_KEY: self.service_handler},
- )
-
-
-class RegistryConfig(AbstractConfig):
- """
- Configuration for the registry.
- """
-
- __slots__ = [
- "_protocol",
- "_host",
- "_port",
- "_username",
- "_password",
- "_load_balance",
- "_group",
- "_version",
- ]
-
- def __init__(
- self,
- protocol: str,
- host: str,
- port: int,
- username: Optional[str] = None,
- password: Optional[str] = None,
- load_balance: Optional[str] = None,
- group: Optional[str] = None,
- version: Optional[str] = None,
- ):
- """
- Initialize the registry configuration.
- :param protocol: The protocol of the registry.
- :type protocol: str
- :param host: The host of the registry.
- :type host: str
- :param port: The port of the registry.
- :type port: int
- :param username: The username of the registry.
- :type username: Optional[str]
- :param password: The password of the registry.
- :type password: Optional[str]
- :param load_balance: The load balance of the registry.
- :type load_balance: Optional[str]
- :param group: The group of the registry.
- :type group: Optional[str]
- :param version: The version of the registry.
- :type version: Optional[str]
- """
- super().__init__()
-
- self._protocol = protocol
- self._host = host
- self._port = port
- self._username = username
- self._password = password
- self._load_balance = load_balance
- self._group = group
- self._version = version
-
- @property
- def protocol(self) -> str:
- """
- Get the protocol of the registry.
- :return: The protocol of the registry.
- :rtype: str
- """
- return self._protocol
-
- @protocol.setter
- def protocol(self, protocol: str) -> None:
- """
- Set the protocol of the registry.
- :param protocol: The protocol of the registry.
- :type protocol: str
- """
- self._protocol = protocol
-
- @property
- def host(self) -> str:
- """
- Get the host of the registry.
- :return: The host of the registry.
- :rtype: str
- """
- return self._host
-
- @host.setter
- def host(self, host: str) -> None:
- """
- Set the host of the registry.
- :param host: The host of the registry.
- :type host: str
- """
- self._host = host
-
- @property
- def port(self) -> int:
- """
- Get the port of the registry.
- :return: The port of the registry.
- :rtype: int
- """
- return self._port
-
- @port.setter
- def port(self, port: int) -> None:
- """
- Set the port of the registry.
- :param port: The port of the registry.
- :type port: int
- """
- self._port = port
-
- @property
- def username(self) -> Optional[str]:
- """
- Get the username of the registry.
- :return: The username of the registry.
- :rtype: Optional[str]
- """
- return self._username
-
- @username.setter
- def username(self, username: str) -> None:
- """
- Set the username of the registry.
- :param username: The username of the registry.
- :type username: str
- """
- self._username = username
-
- @property
- def password(self) -> Optional[str]:
- """
- Get the password of the registry.
- :return: The password of the registry.
- :rtype: Optional[str]
- """
- return self._password
-
- @password.setter
- def password(self, password: str) -> None:
- """
- Set the password of the registry.
- :param password: The password of the registry.
- :type password: str
- """
- self._password = password
-
- @property
- def load_balance(self) -> Optional[str]:
- """
- Get the load balance of the registry.
- :return: The load balance of the registry.
- :rtype: Optional[str]
- """
- return self._load_balance
-
- @load_balance.setter
- def load_balance(self, load_balance: str) -> None:
- """
- Set the load balance of the registry.
- :param load_balance: The load balance of the registry.
- :type load_balance: str
- """
- self._load_balance = load_balance
-
- @property
- def group(self) -> Optional[str]:
- """
- Get the group of the registry.
- :return: The group of the registry.
- :rtype: Optional[str]
- """
- return self._group
-
- @group.setter
- def group(self, group: str) -> None:
- """
- Set the group of the registry.
- :param group: The group of the registry.
- :type group: str
- """
- self._group = group
-
- @property
- def version(self) -> Optional[str]:
- """
- Get the version of the registry.
- :return: The version of the registry.
- :rtype: Optional[str]
- """
- return self._version
-
- @version.setter
- def version(self, version: str) -> None:
- """
- Set the version of the registry.
- :param version: The version of the registry.
- :type version: str
- """
- self._version = version
-
- def to_url(self) -> URL:
- """
- Convert the registry configuration to a URL.
- :return: The URL.
- :rtype: URL
- """
- parameters = {}
- if self.load_balance:
- parameters[registry_constants.LOAD_BALANCE_KEY] = self.load_balance
- if self.group:
- parameters[config_constants.GROUP] = self.group
- if self.version:
- parameters[config_constants.VERSION] = self.version
-
- return URL(
- scheme=self.protocol,
- host=self.host,
- port=self.port,
- username=self.username,
- password=self.password,
- parameters=parameters,
- )
-
- @classmethod
- def from_url(cls, url: Union[str, URL]) -> "RegistryConfig":
- """
- Create a registry configuration from a URL.
- :param url: The URL.
- :type url: Union[str,URL]
- :return: The registry configuration.
- :rtype: RegistryConfig
- """
- if isinstance(url, str):
- url = create_url(url)
- return cls(
- protocol=url.scheme,
- host=url.host,
- port=url.port,
- username=url.username,
- password=url.password,
- load_balance=url.parameters.get(registry_constants.LOAD_BALANCE_KEY),
- group=url.parameters.get(config_constants.GROUP),
- version=url.parameters.get(config_constants.VERSION),
- )
-
-
-class LoggerConfig(AbstractConfig):
- """
- Logger Configuration.
- """
-
- @dataclass
- class ConsoleConfig:
- """
- Console logger configuration.
-
- :param formatter: Console formatter.
- :type formatter: Optional[str]
- """
-
- formatter: Optional[str] = None
-
- @dataclass
- class FileConfig:
- """
- File logger configuration.
-
- :param file_formatter: File formatter.
- :type file_formatter: Optional[str]
- :param file_dir: File directory.
- :type file_dir: str
- :param file_name: File name.
- :type file_name: str
- :param rotate: File rotate type.
- :type rotate: logger_constants.FileRotateType
- :param backup_count: Backup count.
- :type backup_count: int
- :param max_bytes: Max bytes.
- :type max_bytes: int
- :param interval: Interval.
- :type interval: int
- """
-
- file_formatter: Optional[str] = None
- file_dir: str = logger_constants.DEFAULT_FILE_DIR_VALUE
- file_name: str = logger_constants.DEFAULT_FILE_NAME_VALUE
- rotate: logger_constants.FileRotateType = logger_constants.FileRotateType.NONE
- backup_count: int = logger_constants.DEFAULT_FILE_BACKUP_COUNT_VALUE
- max_bytes: int = logger_constants.DEFAULT_FILE_MAX_BYTES_VALUE
- interval: int = logger_constants.DEFAULT_FILE_INTERVAL_VALUE
-
- __slots__ = [
- "_level",
- "_global_formatter",
- "_console_enabled",
- "_console_config",
- "_file_enabled",
- "_file_config",
- ]
-
- def __init__(
- self,
- level: str = logger_constants.DEFAULT_LEVEL_VALUE,
- formatter: Optional[str] = None,
- console_enabled: bool = logger_constants.DEFAULT_CONSOLE_ENABLED_VALUE,
- file_enabled: bool = logger_constants.DEFAULT_FILE_ENABLED_VALUE,
- ):
- """
- Initialize the logger configuration.
- :param level: The logger level.
- :type level: str, default is "INFO".
- :param console_enabled: Whether to enable console logger.
- :type console_enabled: bool, default is True.
- :param file_enabled: Whether to enable file logger.
- """
- super().__init__()
- # logger level
- self._level = level.upper()
-
- # global formatter
- self._global_formatter = formatter
-
- # console logger
- self._console_enabled = console_enabled
- self._console_config = LoggerConfig.ConsoleConfig()
-
- # file logger
- self._file_enabled = file_enabled
- self._file_config = LoggerConfig.FileConfig()
-
- @property
- def level(self) -> str:
- """
- Get logger level.
- :return: The logger level.
- :rtype: str
- """
- return self._level
-
- @level.setter
- def level(self, level: str) -> None:
- """
- Set logger level.
- :param level: The logger level.
- :type level: str
- """
- if self._level != level.upper():
- self._level = level.upper()
-
- @property
- def global_formatter(self) -> Optional[str]:
- """
- Get global formatter.
- :return: The global formatter.
- :rtype: Optional[str]
- """
- return self._global_formatter
-
- def is_console_enabled(self) -> bool:
- """
- Check if console logger is enabled.
- :return: True if console logger is enabled, otherwise False.
- :rtype: bool
- """
- return self._console_enabled
-
- def enable_console(self) -> None:
- """
- Enable console logger.
- """
- self._console_enabled = True
-
- def disable_console(self) -> None:
- """
- Disable console logger.
- """
- self._console_enabled = False
-
- @property
- def console_config(self) -> ConsoleConfig:
- """
- Get console logger configuration.
- :return: Console logger configuration.
- :rtype: ConsoleConfig
- """
- return self._console_config
-
- def set_console(self, console_config: ConsoleConfig):
- """
- Set console logger configuration.
- :param console_config: Console logger configuration.
- :type console_config: ConsoleConfig
- """
- self._console_config = console_config
-
- def is_file_enabled(self) -> bool:
- """
- Check if file logger is enabled.
- :return: True if file logger is enabled, otherwise False.
- :rtype: bool
- """
- return self._file_enabled
-
- def enable_file(self) -> None:
- """
- Enable file logger.
- """
- self._file_enabled = True
-
- def disable_file(self) -> None:
- """
- Disable file logger.
- """
- self._file_enabled = False
-
- @property
- def file_config(self) -> FileConfig:
- """
- Get file logger configuration.
- :return: File logger configuration.
- :rtype: FileConfig
- """
- return self._file_config
-
- def set_file(self, file_config: FileConfig) -> None:
- """
- Set file logger configuration.
- :param file_config: File logger configuration.
- :type file_config: FileConfig
- """
- self._file_config = file_config
diff --git a/src/dubbo/constants/__init__.py b/src/dubbo/constants/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/src/dubbo/constants/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/src/dubbo/constants/common_constants.py b/src/dubbo/constants/common_constants.py
deleted file mode 100644
index c997ac8..0000000
--- a/src/dubbo/constants/common_constants.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-DUBBO_VALUE = "dubbo"
-
-REFER_KEY = "refer"
-EXPORT_KEY = "export"
-
-PROTOCOL_KEY = "protocol"
-TRIPLE = "triple"
-TRIPLE_SHORT = "tri"
-
-SIDE_KEY = "side"
-SERVER_VALUE = "server"
-CLIENT_VALUE = "client"
-
-METHOD_KEY = "method"
-SERVICE_KEY = "service"
-
-SERVICE_HANDLER_KEY = "service-handler"
-
-GROUP_KEY = "group"
-
-LOCAL_HOST_KEY = "localhost"
-LOCAL_HOST_VALUE = "127.0.0.1"
-DEFAULT_PORT = 50051
-
-SSL_ENABLED_KEY = "ssl-enabled"
-
-SERIALIZATION_KEY = "serialization"
-SERIALIZER_KEY = "serializer"
-DESERIALIZER_KEY = "deserializer"
-
-
-COMPRESSION_KEY = "compression"
-COMPRESSOR_KEY = "compressor"
-DECOMPRESSOR_KEY = "decompressor"
-
-
-TRANSPORTER_KEY = "transporter"
-TRANSPORTER_DEFAULT_VALUE = "aio"
-
-TRUE_VALUE = "true"
-FALSE_VALUE = "false"
-
-RPC_TYPE_KEY = "rpc-type"
-
-METHOD_DESCRIPTOR_KEY = "method-descriptor"
-
-LOADBALANCE_KEY = "loadbalance"
-
-PATH_SEPARATOR = "/"
-PROTOCOL_SEPARATOR = "://"
-ANY_VALUE = "*"
-COMMA_SEPARATOR = ","
diff --git a/src/dubbo/constants/config_constants.py b/src/dubbo/constants/config_constants.py
deleted file mode 100644
index aa8830c..0000000
--- a/src/dubbo/constants/config_constants.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-ENVIRONMENT = "environment"
-TEST_ENVIRONMENT = "test"
-DEVELOPMENT_ENVIRONMENT = "develop"
-PRODUCTION_ENVIRONMENT = "product"
-
-VERSION = "version"
-GROUP = "group"
-
-TRANSPORT = "transport"
-AIO_TRANSPORT = "aio"
diff --git a/src/dubbo/constants/logger_constants.py b/src/dubbo/constants/logger_constants.py
deleted file mode 100644
index 8d8e802..0000000
--- a/src/dubbo/constants/logger_constants.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import enum
-import os
-
-__all__ = [
- "FileRotateType",
- "LEVEL_KEY",
- "CONSOLE_ENABLED_KEY",
- "FILE_ENABLED_KEY",
- "FILE_DIR_KEY",
- "FILE_NAME_KEY",
- "FILE_ROTATE_KEY",
- "FILE_MAX_BYTES_KEY",
- "FILE_INTERVAL_KEY",
- "FILE_BACKUP_COUNT_KEY",
- "DEFAULT_LEVEL_VALUE",
- "DEFAULT_CONSOLE_ENABLED_VALUE",
- "DEFAULT_FILE_ENABLED_VALUE",
- "DEFAULT_FILE_DIR_VALUE",
- "DEFAULT_FILE_NAME_VALUE",
- "DEFAULT_FILE_MAX_BYTES_VALUE",
- "DEFAULT_FILE_INTERVAL_VALUE",
- "DEFAULT_FILE_BACKUP_COUNT_VALUE",
-]
-
-
-@enum.unique
-class FileRotateType(enum.Enum):
- """
- The file rotating type enum.
-
- :cvar NONE: No rotating.
- :cvar SIZE: Rotate the file by size.
- :cvar TIME: Rotate the file by time.
- """
-
- NONE = "NONE"
- SIZE = "SIZE"
- TIME = "TIME"
-
-
-"""logger config keys"""
-# global config
-LEVEL_KEY = "logger.level"
-
-# console config
-CONSOLE_ENABLED_KEY = "logger.console.enable"
-
-# file logger
-FILE_ENABLED_KEY = "logger.file.enable"
-FILE_DIR_KEY = "logger.file.dir"
-FILE_NAME_KEY = "logger.file.name"
-FILE_ROTATE_KEY = "logger.file.rotate"
-FILE_MAX_BYTES_KEY = "logger.file.maxbytes"
-FILE_INTERVAL_KEY = "logger.file.interval"
-FILE_BACKUP_COUNT_KEY = "logger.file.backupcount"
-
-"""some logger default value"""
-DEFAULT_LEVEL_VALUE = "INFO"
-# console
-DEFAULT_CONSOLE_ENABLED_VALUE = True
-# file
-DEFAULT_FILE_ENABLED_VALUE = False
-DEFAULT_FILE_DIR_VALUE = os.path.expanduser("~")
-DEFAULT_FILE_NAME_VALUE = "dubbo.log"
-DEFAULT_FILE_MAX_BYTES_VALUE = 10 * 1024 * 1024
-DEFAULT_FILE_INTERVAL_VALUE = 1
-DEFAULT_FILE_BACKUP_COUNT_VALUE = 10
diff --git a/src/dubbo/constants/registry_constants.py b/src/dubbo/constants/registry_constants.py
deleted file mode 100644
index 6ac69a4..0000000
--- a/src/dubbo/constants/registry_constants.py
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-REGISTRY_KEY = "registry"
-DYNAMIC_KEY = "dynamic"
-CATEGORY_KEY = "category"
-PROVIDERS_CATEGORY = "providers"
-CONSUMERS_CATEGORY = "consumers"
-
-
-LOAD_BALANCE_KEY = "loadbalance"
diff --git a/src/dubbo/extension/__init__.py b/src/dubbo/extension/__init__.py
deleted file mode 100644
index 6af90b2..0000000
--- a/src/dubbo/extension/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.extension.extension_loader import ExtensionError, ExtensionLoader as _ExtensionLoader
-
-__all__ = ["ExtensionError", "extensionLoader"]
-
-extensionLoader = _ExtensionLoader()
diff --git a/src/dubbo/extension/extension_loader.py b/src/dubbo/extension/extension_loader.py
deleted file mode 100644
index 1f9e814..0000000
--- a/src/dubbo/extension/extension_loader.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import importlib
-from typing import Any
-
-from dubbo.classes import SingletonBase
-from dubbo.extension import registries as registries_module
-
-
-class ExtensionError(Exception):
- """
- Extension error.
- """
-
- def __init__(self, message: str):
- """
- Initialize the extension error.
- :param message: The error message.
- :type message: str
- """
- super().__init__(message)
-
-
-class ExtensionLoader(SingletonBase):
- """
- Singleton class for loading extension implementations.
- """
-
- def __init__(self):
- """
- Initialize the extension loader.
-
- Load all the registries from the registries module.
- """
- if not hasattr(self, "_initialized"): # Ensure __init__ runs only once
- self._registries = {}
- for name in registries_module.registries:
- registry = getattr(registries_module, name)
- self._registries[registry.interface] = registry.impls
- self._initialized = True
-
- def get_extension(self, interface: Any, impl_name: str) -> Any:
- """
- Get the extension implementation for the interface.
-
- :param interface: Interface class.
- :type interface: Any
- :param impl_name: Implementation name.
- :type impl_name: str
- :return: Extension implementation class.
- :rtype: Any
- :raises ExtensionError: If the interface or implementation is not found.
- """
- # Get the registry for the interface
- impls = self._registries.get(interface)
- if not impls:
- raise ExtensionError(f"Interface '{interface.__name__}' is not supported.")
-
- # Get the full name of the implementation
- full_name = impls.get(impl_name)
- if not full_name:
- raise ExtensionError(f"Implementation '{impl_name}' for interface '{interface.__name__}' is not exist.")
-
- try:
- # Split the full name into module and class
- module_name, class_name = full_name.rsplit(".", 1)
-
- # Load the module and get the class
- module = importlib.import_module(module_name)
- subclass = getattr(module, class_name)
-
- # Return the subclass
- return subclass
- except Exception as e:
- raise ExtensionError(
- f"Failed to load extension '{impl_name}' for interface '{interface.__name__}'. \nDetail: {e}"
- )
diff --git a/src/dubbo/extension/registries.py b/src/dubbo/extension/registries.py
deleted file mode 100644
index cf23ae7..0000000
--- a/src/dubbo/extension/registries.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dataclasses import dataclass
-from typing import Any
-
-from dubbo.cluster import LoadBalance
-from dubbo.compression import Compressor, Decompressor
-from dubbo.protocol import Protocol
-from dubbo.registry import RegistryFactory
-from dubbo.remoting import Transporter
-
-
-@dataclass
-class ExtendedRegistry:
- """
- A dataclass to represent an extended registry.
-
- :param interface: The interface of the registry.
- :type interface: Any
- :param impls: The implementations of the registry.
- :type impls: Dict[str, Any]
- """
-
- interface: Any
- impls: dict[str, Any]
-
-
-# All Extension Registries
-registries = [
- "registryFactoryRegistry",
- "loadBalanceRegistry",
- "protocolRegistry",
- "compressorRegistry",
- "decompressorRegistry",
- "transporterRegistry",
-]
-
-# RegistryFactory registry
-registryFactoryRegistry = ExtendedRegistry(
- interface=RegistryFactory,
- impls={
- "zookeeper": "dubbo.registry.zookeeper.zk_registry.ZookeeperRegistryFactory",
- },
-)
-
-# LoadBalance registry
-loadBalanceRegistry = ExtendedRegistry(
- interface=LoadBalance,
- impls={
- "random": "dubbo.cluster.loadbalances.RandomLoadBalance",
- "cpu": "dubbo.cluster.loadbalances.CpuLoadBalance",
- },
-)
-
-# Protocol registry
-protocolRegistry = ExtendedRegistry(
- interface=Protocol,
- impls={
- "tri": "dubbo.protocol.triple.protocol.TripleProtocol",
- },
-)
-
-# Compressor registry
-compressorRegistry = ExtendedRegistry(
- interface=Compressor,
- impls={
- "identity": "dubbo.compression.Identity",
- "gzip": "dubbo.compression.Gzip",
- "bzip2": "dubbo.compression.Bzip2",
- },
-)
-
-
-# Decompressor registry
-decompressorRegistry = ExtendedRegistry(
- interface=Decompressor,
- impls={
- "identity": "dubbo.compression.Identity",
- "gzip": "dubbo.compression.Gzip",
- "bzip2": "dubbo.compression.Bzip2",
- },
-)
-
-
-# Transporter registry
-transporterRegistry = ExtendedRegistry(
- interface=Transporter,
- impls={
- "aio": "dubbo.remoting.aio.aio_transporter.AioTransporter",
- },
-)
diff --git a/src/dubbo/loadbalance/__init__.py b/src/dubbo/loadbalance/__init__.py
deleted file mode 100644
index b1b2965..0000000
--- a/src/dubbo/loadbalance/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import AbstractLoadBalance, LoadBalance
-
-__all__ = ["AbstractLoadBalance", "LoadBalance"]
diff --git a/src/dubbo/loadbalance/_interfaces.py b/src/dubbo/loadbalance/_interfaces.py
deleted file mode 100644
index b2d7e43..0000000
--- a/src/dubbo/loadbalance/_interfaces.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from typing import Optional
-
-from dubbo.common import URL
-from dubbo.protocol import Invocation, Invoker
-
-
-class LoadBalance(abc.ABC):
- """
- The load balance interface.
- """
-
- @abc.abstractmethod
- def select(self, invokers: list[Invoker], url: URL, invocation: Invocation) -> Optional[Invoker]:
- """
- Select an invoker from the list.
- :param invokers: The invokers.
- :type invokers: List[Invoker]
- :param url: The URL.
- :type url: URL
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The selected invoker. If no invoker is selected, return None.
- :rtype: Optional[Invoker]
- """
- raise NotImplementedError()
-
-
-class AbstractLoadBalance(LoadBalance, abc.ABC):
- """
- The abstract load balance.
- """
-
- def select(self, invokers: list[Invoker], url: URL, invocation: Invocation) -> Optional[Invoker]:
- if not invokers:
- return None
-
- if len(invokers) == 1:
- return invokers[0]
-
- return self.do_select(invokers, url, invocation)
-
- @abc.abstractmethod
- def do_select(self, invokers: list[Invoker], url: URL, invocation: Invocation) -> Optional[Invoker]:
- """
- Do select an invoker from the list.
- :param invokers: The invokers.
- :type invokers: List[Invoker]
- :param url: The URL.
- :type url: URL
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The selected invoker. If no invoker is selected, return None.
- :rtype: Optional[Invoker]
- """
- raise NotImplementedError()
diff --git a/src/dubbo/loggers.py b/src/dubbo/loggers.py
deleted file mode 100644
index 6b9d91c..0000000
--- a/src/dubbo/loggers.py
+++ /dev/null
@@ -1,234 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import enum
-import logging
-import re
-import threading
-
-from dubbo.configs import LoggerConfig
-
-__all__ = ["loggerFactory"]
-
-
-class ColorFormatter(logging.Formatter):
- """
- A formatter with color.
- It will format the log message like this:
- 2024-06-24 16:39:57 | DEBUG | test_logger_factory:test_with_config:44 - [Dubbo] debug log
- """
-
- @enum.unique
- class Colors(enum.Enum):
- """
- Colors for log messages.
- """
-
- END = "\033[0m"
- BOLD = "\033[1m"
- BLUE = "\033[34m"
- GREEN = "\033[32m"
- PURPLE = "\033[35m"
- CYAN = "\033[36m"
- RED = "\033[31m"
- YELLOW = "\033[33m"
- GREY = "\033[38;5;240m"
-
- COLOR_LEVEL_MAP = {
- "DEBUG": Colors.BLUE.value,
- "INFO": Colors.GREEN.value,
- "WARNING": Colors.YELLOW.value,
- "ERROR": Colors.RED.value,
- "CRITICAL": Colors.RED.value + Colors.BOLD.value,
- }
-
- DATE_FORMAT: str = "%Y-%m-%d %H:%M:%S"
-
- LOG_FORMAT: str = (
- f"{Colors.GREEN.value}%(asctime)s{Colors.END.value}"
- " | "
- f"%(level_color)s%(levelname)s{Colors.END.value}"
- " | "
- f"{Colors.CYAN.value}%(module)s:%(funcName)s:%(lineno)d{Colors.END.value}"
- " - "
- f"{Colors.PURPLE.value}[Dubbo]{Colors.END.value} "
- f"%(suffix)s"
- f"%(msg_color)s%(message)s{Colors.END.value}"
- )
-
- def __init__(self, suffix: str = ""):
- super().__init__(self.LOG_FORMAT, self.DATE_FORMAT)
- self.suffix = f"{self.Colors.PURPLE.value}[{suffix}]{self.Colors.END.value} " if suffix else ""
-
- def format(self, record) -> str:
- levelname = record.levelname
- record.level_color = record.msg_color = self.COLOR_LEVEL_MAP.get(levelname)
- record.suffix = self.suffix
- return super().format(record)
-
-
-class NoColorFormatter(logging.Formatter):
- """
- A formatter without color.
- It will format the log message like this:
- 2024-06-24 16:39:57 | DEBUG | test_logger_factory:test_with_config:44 - [Dubbo] debug log
- """
-
- def __init__(self, suffix: str = ""):
- color_re = re.compile(r"\033\[[0-9;]*\w|%\((msg_color|level_color)\)s")
- self.log_format = color_re.sub("", ColorFormatter.LOG_FORMAT)
- self.suffix = f"[{suffix}] " if suffix else ""
- super().__init__(self.log_format, ColorFormatter.DATE_FORMAT)
-
- def format(self, record) -> str:
- record.message = self.suffix + record.getMessage()
- return super().format(record)
-
-
-class _LoggerFactory:
- """
- The logger factory.
- """
-
- DEFAULT_LOGGER_NAME = "dubbo"
-
- _logger_lock = threading.RLock()
- _config: LoggerConfig = LoggerConfig()
- _loggers = {}
-
- @classmethod
- def set_config(cls, config):
- if not isinstance(config, LoggerConfig):
- raise TypeError("config must be an instance of LoggerConfig")
-
- cls._config = config
- cls._refresh_config()
-
- @classmethod
- def _refresh_config(cls) -> None:
- """
- Refresh the logger configuration.
-
- """
- with cls._logger_lock:
- # create logger if not exists
- if not cls._loggers:
- cls._loggers[cls.DEFAULT_LOGGER_NAME] = logging.getLogger(cls.DEFAULT_LOGGER_NAME)
-
- # update all loggers
- for name, logger in cls._loggers.items():
- cls._update_logger(logger, name)
-
- @classmethod
- def _update_logger(cls, logger: logging.Logger, name: str) -> logging.Logger:
- """
- Update the logger with the current configuration.
- :param logger: The logger to update.
- :type logger: logging.Logger
- :param name: The logger name.
- :type name: str
- :return: The updated logger.
- :rtype: logging.Logger
- """
- # clean up handlers
- logger.handlers.clear()
-
- config = cls._config
-
- # set logger level
- logger.setLevel(config.level)
-
- # add console handler if enabled
- if config.is_console_enabled():
- logger.addHandler(cls._get_console_handler(name))
-
- # add file handler if enabled
- if config.is_file_enabled():
- logger.addHandler(cls._get_file_handler(name))
-
- return logger
-
- @classmethod
- def _get_console_handler(cls, name: str) -> logging.StreamHandler:
- """
- Get the console handler
-
- :param name: The logger name.
- :type name: str
- :return: The console handler.
- :rtype: logging.StreamHandler
- """
- console_handler = logging.StreamHandler()
- if not cls._config.console_config.formatter or cls._config.global_formatter:
- # set default color formatter
- console_handler.setFormatter(ColorFormatter(name if name != cls.DEFAULT_LOGGER_NAME else ""))
- else:
- console_handler.setFormatter(
- logging.Formatter(cls._config.console_config.formatter or cls._config.global_formatter)
- )
-
- return console_handler
-
- @classmethod
- def _get_file_handler(cls, name: str) -> logging.FileHandler:
- """
- Get the file handler
-
- :param name: The logger name.
- :type name: str
- :return: The file handler.
- :rtype: logging.FileHandler
- """
- file_handler = logging.FileHandler(
- filename=cls._config.file_config.file_name,
- mode="a",
- encoding="utf-8",
- )
- if not cls._config.file_config.file_formatter or cls._config.global_formatter:
- # set default no color formatter
- file_handler.setFormatter(NoColorFormatter(name if name != cls.DEFAULT_LOGGER_NAME else ""))
- else:
- file_handler.setFormatter(
- logging.Formatter(cls._config.file_config.file_formatter or cls._config.global_formatter)
- )
-
- return file_handler
-
- @classmethod
- def get_logger(cls, name=DEFAULT_LOGGER_NAME) -> logging.Logger:
- """
- Get the logger. class method.
-
- :return: The logger.
- :rtype: logging.Logger
- """
- logger = cls._loggers.get(name)
- if logger is not None:
- return logger
-
- with cls._logger_lock:
- logger = cls._loggers.get(name)
- # double check
- if logger is not None:
- return logger
-
- logger = cls._update_logger(logging.getLogger(name), name)
- cls._loggers[name] = logger
-
- return logger
-
-
-# expose loggerFactory
-loggerFactory = _LoggerFactory
diff --git a/src/dubbo/node.py b/src/dubbo/node.py
deleted file mode 100644
index f847b11..0000000
--- a/src/dubbo/node.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-
-from dubbo.url import URL
-
-__all__ = ["Node"]
-
-
-class Node(abc.ABC):
- """
- Abstract base class for a Node.
- """
-
- @abc.abstractmethod
- def get_url(self) -> URL:
- """
- Get the URL of the node.
-
- :return: The URL of the node.
- :rtype: URL
- :raises NotImplementedError: If the method is not implemented.
- """
- raise NotImplementedError("get_url() is not implemented.")
-
- @abc.abstractmethod
- def is_available(self) -> bool:
- """
- Check if the node is available.
-
- :return: True if the node is available, False otherwise.
- :rtype: bool
- :raises NotImplementedError: If the method is not implemented.
- """
- raise NotImplementedError("is_available() is not implemented.")
-
- @abc.abstractmethod
- def destroy(self) -> None:
- """
- Destroy the node.
-
- :raises NotImplementedError: If the method is not implemented.
- """
- raise NotImplementedError("destroy() is not implemented.")
diff --git a/src/dubbo/protocol/__init__.py b/src/dubbo/protocol/__init__.py
deleted file mode 100644
index 965b73f..0000000
--- a/src/dubbo/protocol/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import Invocation, Invoker, Protocol, Result
-
-__all__ = ["Invocation", "Result", "Invoker", "Protocol"]
diff --git a/src/dubbo/protocol/_interfaces.py b/src/dubbo/protocol/_interfaces.py
deleted file mode 100644
index 22f6fe3..0000000
--- a/src/dubbo/protocol/_interfaces.py
+++ /dev/null
@@ -1,123 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from typing import Any
-
-from dubbo.node import Node
-from dubbo.url import URL
-
-__all__ = ["Invocation", "Result", "Invoker", "Protocol"]
-
-
-class Invocation(abc.ABC):
- @abc.abstractmethod
- def get_service_name(self) -> str:
- """
- Get the service name.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def get_method_name(self) -> str:
- """
- Get the method name.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def get_argument(self) -> Any:
- """
- Get the method argument.
- """
- raise NotImplementedError()
-
-
-class Result(abc.ABC):
- """
- Result of a call
- """
-
- @abc.abstractmethod
- def set_value(self, value: Any) -> None:
- """
- Set the value of the result
- :param value: The value to set
- :type value: Any
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def value(self) -> Any:
- """
- Get the value of the result
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def set_exception(self, exception: Exception) -> None:
- """
- Set the exception to the result
- :param exception: The exception to set
- :type exception: Exception
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def exception(self) -> Exception:
- """
- Get the exception to the result
- """
- raise NotImplementedError()
-
-
-class Invoker(Node, abc.ABC):
- """
- Invoker
- """
-
- @abc.abstractmethod
- def invoke(self, invocation: Invocation) -> Result:
- """
- Invoke the service.
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The result.
- :rtype: Result
- """
- raise NotImplementedError()
-
-
-class Protocol(abc.ABC):
- @abc.abstractmethod
- def export(self, url: URL):
- """
- Export a remote service.
- :param url: The URL.
- :type url: URL
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def refer(self, url: URL) -> Invoker:
- """
- Refer a remote service.
- :param url: The URL.
- :type url: URL
- :return: The invoker.
- :rtype: Invoker
- """
- raise NotImplementedError()
diff --git a/src/dubbo/protocol/invocation.py b/src/dubbo/protocol/invocation.py
deleted file mode 100644
index 400361d..0000000
--- a/src/dubbo/protocol/invocation.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any, Optional
-
-from ._interfaces import Invocation
-
-
-class RpcInvocation(Invocation):
- """
- The RpcInvocation class is an implementation of the Invocation interface.
- """
-
- __slots__ = [
- "_service_name",
- "_method_name",
- "_argument",
- "_attachments",
- "_attributes",
- ]
-
- def __init__(
- self,
- service_name: str,
- method_name: str,
- argument: Any,
- attachments: Optional[dict[str, str]] = None,
- attributes: Optional[dict[str, Any]] = None,
- ):
- """
- Initialize a new RpcInvocation instance.
- :param service_name: The service name.
- :type service_name: str
- :param method_name: The method name.
- :type method_name: str
- :param argument: The argument.
- :type argument: Any
- :param attachments: The attachments.
- :type attachments: Optional[Dict[str, str]]
- :param attributes: The attributes.
- :type attributes: Optional[Dict[str, Any]]
- """
- self._service_name = service_name
- self._method_name = method_name
- self._argument = argument
- self._attachments = attachments or {}
- self._attributes = attributes or {}
-
- def add_attachment(self, key: str, value: str) -> None:
- self._attachments[key] = value
-
- def get_attachment(self, key: str) -> Optional[str]:
- return self._attachments.get(key, None)
-
- def add_attribute(self, key: str, value: Any) -> None:
- self._attributes[key] = value
-
- def get_attribute(self, key: str) -> Optional[Any]:
- return self._attributes.get(key, None)
-
- def get_service_name(self) -> str:
- return self._service_name
-
- def get_method_name(self) -> str:
- return self._method_name
-
- def get_argument(self) -> Any:
- return self._argument
diff --git a/src/dubbo/protocol/triple/__init__.py b/src/dubbo/protocol/triple/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/src/dubbo/protocol/triple/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/src/dubbo/protocol/triple/call/__init__.py b/src/dubbo/protocol/triple/call/__init__.py
deleted file mode 100644
index d274978..0000000
--- a/src/dubbo/protocol/triple/call/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import ClientCall, ServerCall
-from .client_call import TripleClientCall
-
-__all__ = ["ClientCall", "ServerCall", "TripleClientCall"]
diff --git a/src/dubbo/protocol/triple/call/_interfaces.py b/src/dubbo/protocol/triple/call/_interfaces.py
deleted file mode 100644
index 65c84bf..0000000
--- a/src/dubbo/protocol/triple/call/_interfaces.py
+++ /dev/null
@@ -1,143 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from typing import Any
-
-from dubbo.protocol.triple.metadata import RequestMetadata
-from dubbo.protocol.triple.status import TriRpcStatus
-
-__all__ = ["ClientCall", "ServerCall"]
-
-
-class ClientCall(abc.ABC):
- """
- Interface for client call.
- """
-
- @abc.abstractmethod
- def start(self, metadata: RequestMetadata) -> None:
- """
- Start this call.
-
- :param metadata: call metadata
- :type metadata: RequestMetadata
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def send_message(self, message: Any, last: bool) -> None:
- """
- Send message to server.
-
- :param message: message to send
- :type message: Any
- :param last: whether this message is the last one
- :type last: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def cancel_by_local(self, e: Exception) -> None:
- """
- Cancel this call by local.
-
- :param e: The exception that caused the call to be canceled
- :type e: Exception
- """
- raise NotImplementedError()
-
- class Listener(abc.ABC):
- """
- Interface for client call listener.
- """
-
- @abc.abstractmethod
- def on_message(self, message: Any) -> None:
- """
- Called when a message is received from server.
-
- :param message: received message
- :type message: Any
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def on_close(self, status: TriRpcStatus, trailers: dict[str, Any]) -> None:
- """
- Called when the call is closed.
-
- :param status: call status
- :type status: TriRpcStatus
- :param trailers: trailers
- :type trailers: Dict[str, Any]
- """
- raise NotImplementedError()
-
-
-class ServerCall(abc.ABC):
- """
- Interface for server call.
- """
-
- @abc.abstractmethod
- def send_message(self, message: Any) -> None:
- """
- Send message to client.
-
- :param message: message to send
- :type message: Any
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def complete(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- """
- Complete this call.
-
- :param status: call status
- :type status: TriRpcStatus
- :param attachments: attachments
- :type attachments: Dict[str, Any]
- """
- raise NotImplementedError()
-
- class Listener(abc.ABC):
- """
- Interface for server call listener.
- """
-
- @abc.abstractmethod
- def on_message(self, message: Any, last: bool) -> None:
- """
- Called when a message is received from client.
-
- :param message: received message
- :type message: Any
- :param last: whether this message is the last one
- :type last: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def on_close(self, status: TriRpcStatus) -> None:
- """
- Called when the call is closed.
-
- :param status: call status
- :type status: TriRpcStatus
- """
- raise NotImplementedError()
diff --git a/src/dubbo/protocol/triple/call/client_call.py b/src/dubbo/protocol/triple/call/client_call.py
deleted file mode 100644
index 676826f..0000000
--- a/src/dubbo/protocol/triple/call/client_call.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any, Optional
-
-from dubbo.compression import Compressor, Identity
-from dubbo.loggers import loggerFactory
-from dubbo.protocol.triple.call import ClientCall
-from dubbo.protocol.triple.constants import GRpcCode
-from dubbo.protocol.triple.metadata import RequestMetadata
-from dubbo.protocol.triple.status import TriRpcStatus
-from dubbo.protocol.triple.stream import ClientStream
-from dubbo.protocol.triple.stream.client_stream import TriClientStream
-from dubbo.remoting.aio.http2.stream_handler import StreamClientMultiplexHandler
-from dubbo.serialization import Deserializer, SerializationError, Serializer
-
-__all__ = [
- "TripleClientCall",
- "FutureToClientCallListenerAdapter",
- "ReadStreamToClientCallListenerAdapter",
-]
-
-from dubbo.utils import FunctionHelper
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class TripleClientCall(ClientCall, ClientStream.Listener):
- """
- Triple client call.
- """
-
- def __init__(
- self,
- stream_factory: StreamClientMultiplexHandler,
- listener: ClientCall.Listener,
- serializer: Serializer,
- deserializer: Deserializer,
- ):
- self._stream_factory = stream_factory
- self._client_stream: Optional[ClientStream] = None
- self._listener = listener
- self._serializer = serializer
- self._deserializer = deserializer
- self._compressor: Optional[Compressor] = None
-
- self._headers_sent = False
- self._done = False
- self._request_metadata: Optional[RequestMetadata] = None
-
- def start(self, metadata: RequestMetadata) -> None:
- self._request_metadata = metadata
-
- # get compression from metadata
- self._compressor = metadata.compressor
-
- # create a new stream
- client_stream = TriClientStream(self, self._compressor)
- h2_stream = self._stream_factory.create(client_stream.transport_listener)
- client_stream.bind(h2_stream)
- self._client_stream = client_stream
-
- def send_message(self, message: Any, last: bool) -> None:
- if self._done:
- _LOGGER.warning("Call is done, cannot send message")
- return
-
- # check if headers are sent
- if not self._headers_sent:
- # send headers
- self._headers_sent = True
- self._client_stream.send_headers(self._request_metadata.to_headers())
-
- # send message
- try:
- data = FunctionHelper.call_func(self._serializer.serialize, message) if message else b""
- compress_flag = 0 if self._compressor.get_message_encoding() == Identity.get_message_encoding() else 1
- self._client_stream.send_message(data, compress_flag, last)
- except SerializationError as e:
- _LOGGER.error("Failed to serialize message: %s", e)
- # close the stream
- self.cancel_by_local(e)
- # close the listener
- status = TriRpcStatus(
- code=GRpcCode.INTERNAL,
- description="Failed to serialize message",
- )
- self._listener.on_close(status, {})
-
- def cancel_by_local(self, e: Exception) -> None:
- if self._done:
- return
- self._done = True
-
- if not self._client_stream or not self._headers_sent:
- return
-
- status = TriRpcStatus(
- code=GRpcCode.CANCELLED,
- description=f"Call cancelled by client: {e}",
- )
- self._client_stream.cancel_by_local(status)
-
- def on_message(self, data: bytes) -> None:
- """
- Called when a message is received from server.
- :param data: The message data
- :type data: bytes
- """
- if self._done:
- _LOGGER.warning("Call is done, cannot receive message")
- return
-
- try:
- # Deserialize the message
- message = self._deserializer.deserialize(data)
- self._listener.on_message(message)
- except SerializationError as e:
- _LOGGER.error("Failed to deserialize message: %s", e)
- # close the stream
- self.cancel_by_local(e)
- # close the listener
- status = TriRpcStatus(
- code=GRpcCode.INTERNAL,
- description="Failed to deserialize message",
- )
- self._listener.on_close(status, {})
-
- def on_complete(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- """
- Called when the call is completed.
- :param status: The status
- :type status: TriRpcStatus
- :param attachments: The attachments
- :type attachments: Dict[str, Any]
- """
- if not self._done:
- self._done = True
- self._listener.on_close(status, attachments)
-
- def on_cancel_by_remote(self, status: TriRpcStatus) -> None:
- """
- Called when the call is cancelled by remote.
- :param status: The status
- :type status: TriRpcStatus
- """
- self.on_complete(status, {})
-
-
-class FutureToClientCallListenerAdapter(ClientCall.Listener):
- """
- The future call listener.
- """
-
- def __init__(self, future):
- self._future = future
- self._message = None
-
- def on_message(self, message: Any) -> None:
- self._message = message
-
- def on_close(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- if status.code != GRpcCode.OK:
- self._future.set_exception(status.as_exception())
- else:
- self._future.set_result(self._message)
-
-
-class ReadStreamToClientCallListenerAdapter(ClientCall.Listener):
- """
- Adapter from stream to client call listener.
- """
-
- def __init__(self, read_stream):
- self._read_stream = read_stream
-
- def on_message(self, message: Any) -> None:
- self._read_stream.put(message)
-
- def on_close(self, status: TriRpcStatus, trailers: dict[str, Any]) -> None:
- if status.code != GRpcCode.OK:
- self._read_stream.put_exception(status.as_exception())
- else:
- self._read_stream.put_eof()
diff --git a/src/dubbo/protocol/triple/call/server_call.py b/src/dubbo/protocol/triple/call/server_call.py
deleted file mode 100644
index d46947d..0000000
--- a/src/dubbo/protocol/triple/call/server_call.py
+++ /dev/null
@@ -1,232 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from concurrent.futures import ThreadPoolExecutor
-from typing import Any, Callable
-
-from dubbo.classes import ReadWriteStream
-from dubbo.loggers import loggerFactory
-from dubbo.protocol.triple.call import ServerCall
-from dubbo.protocol.triple.constants import (
- GRpcCode,
- TripleHeaderName,
- TripleHeaderValue,
-)
-from dubbo.protocol.triple.status import TriRpcStatus
-from dubbo.protocol.triple.stream import ServerStream
-from dubbo.protocol.triple.streams import (
- TriReadStream,
- TriReadWriteStream,
- TriServerWriteStream,
-)
-from dubbo.proxy.handlers import RpcMethodHandler
-from dubbo.remoting.aio.http2.headers import Http2Headers
-from dubbo.remoting.aio.http2.registries import HttpStatus
-from dubbo.serialization import (
- CustomDeserializer,
- CustomSerializer,
- DirectDeserializer,
- DirectSerializer,
-)
-from dubbo.types import RpcType, RpcTypes
-from dubbo.utils import FunctionHelper
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class TripleServerCall(ServerCall, ServerStream.Listener):
- def __init__(
- self,
- server_stream: ServerStream,
- method_handler: RpcMethodHandler,
- executor: ThreadPoolExecutor,
- ):
- self._server_stream = server_stream
- self._executor = executor
-
- # create read stream
- self._read_stream = TriReadStream()
-
- # create write stream
- write_stream = TriServerWriteStream(self)
- read_write_stream = TriReadWriteStream(write_stream, self._read_stream)
-
- self._method_runner: MethodRunner = MethodRunnerFactory.create(method_handler, read_write_stream)
-
- # get method descriptor
- method_descriptor = method_handler.method_descriptor
-
- # get arguments deserializer
- arg_deserializer = method_descriptor.get_arg_deserializer()
- self._deserializer = CustomDeserializer(arg_deserializer) if arg_deserializer else DirectDeserializer()
-
- # get return serializer
- return_serializer = method_descriptor.get_return_serializer()
- self._serializer = CustomSerializer(return_serializer) if return_serializer else DirectSerializer()
-
- self._headers_sent = False
-
- def send_message(self, message: Any) -> None:
- if not self._headers_sent:
- headers = Http2Headers()
- headers.status = HttpStatus.OK.value
- headers.add(
- TripleHeaderName.CONTENT_TYPE.value,
- TripleHeaderValue.APPLICATION_GRPC_PROTO.value,
- )
- self._server_stream.send_headers(headers)
-
- serialized_data = FunctionHelper.call_func(self._serializer.serialize, message)
- # TODO support compression
- self._server_stream.send_message(serialized_data, False)
-
- def complete(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- if not attachments.get(TripleHeaderName.CONTENT_TYPE.value):
- attachments[TripleHeaderName.CONTENT_TYPE.value] = TripleHeaderValue.APPLICATION_GRPC_PROTO.value
- self._server_stream.complete(status, attachments)
-
- def on_headers(self, headers: dict[str, Any]) -> None:
- # start a new thread to run the method
- self._executor.submit(self._method_runner.run)
-
- def on_message(self, data: bytes) -> None:
- if data == b"":
- return
- deserialized_data = self._deserializer.deserialize(data)
- self._read_stream.put(deserialized_data)
-
- def on_complete(self) -> None:
- self._read_stream.put_eof()
-
- def on_cancel_by_remote(self, status: TriRpcStatus) -> None:
- # cancel the method runner.
- self._read_stream.put_exception(status.as_exception())
-
-
-class MethodRunner(abc.ABC):
- """
- Interface for method runner.
- """
-
- @abc.abstractmethod
- def run(self) -> None:
- """
- Run the method.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def handle_result(self, result: Any) -> None:
- """
- Handle the result.
- :param result: result
- :type result: Any
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def handle_exception(self, e: Exception) -> None:
- """
- Handle the exception.
- :param e: exception.
- :type e: Exception
- """
- raise NotImplementedError()
-
-
-class DefaultMethodRunner(MethodRunner):
- """
- Abstract method runner.
- """
-
- def __init__(
- self,
- func: Callable,
- read_write_stream: ReadWriteStream,
- rpc_type: RpcType,
- ):
- self._read_write_stream = read_write_stream
- self._func = func
-
- self._rpc_type = rpc_type
-
- def run(self) -> None:
- try:
- if self._rpc_type == RpcTypes.UNARY.value:
- result = self._func(self._read_write_stream.read())
- else:
- result = self._func(self._read_write_stream)
- # handle the result
- self.handle_result(result)
- except Exception as e:
- # handle the exception
- self.handle_exception(e)
-
- def handle_result(self, result: Any) -> None:
- try:
- # check if the stream is completed
- if not self._read_write_stream.can_write_more():
- return
-
- if not self._rpc_type.server_stream:
- # get single result
- self._read_write_stream.write(result)
- else:
- # get multi results
- for message in result:
- self._read_write_stream.write(message)
-
- self._read_write_stream.done_writing()
- except Exception as e:
- self.handle_exception(e)
-
- def handle_exception(self, e: Exception) -> None:
- if self._read_write_stream.can_write_more():
- _LOGGER.exception("Invoke method failed: %s", e)
- status = TriRpcStatus(
- GRpcCode.INTERNAL,
- description=f"Invoke method failed: {str(e)}",
- cause=e,
- )
- self._read_write_stream.done_writing(tri_rpc_status=status)
-
-
-class MethodRunnerFactory:
- """
- Factory for method runner.
- """
-
- @staticmethod
- def create(method_handler: RpcMethodHandler, read_write_stream: ReadWriteStream) -> MethodRunner:
- """
- Create a method runner.
-
- :param method_handler: method handler
- :type method_handler: RpcMethodHandler
- :param read_write_stream: read write stream
- :type read_write_stream: ReadWriteStream
- :return: method runner
- :rtype: MethodRunner
- """
-
- method_descriptor = method_handler.method_descriptor
-
- return DefaultMethodRunner(
- method_descriptor.get_method(),
- read_write_stream,
- method_descriptor.get_rpc_type(),
- )
diff --git a/src/dubbo/protocol/triple/coders.py b/src/dubbo/protocol/triple/coders.py
deleted file mode 100644
index 8eb67b8..0000000
--- a/src/dubbo/protocol/triple/coders.py
+++ /dev/null
@@ -1,256 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import struct
-from typing import Optional
-
-from dubbo.compression import Compressor, Decompressor
-from dubbo.protocol.triple.exceptions import RpcError
-
-"""
- gRPC Message Format Diagram (HTTP/2 Data Frame):
- +----------------------+-------------------------+------------------+
- | HTTP Header | gRPC Header | Business Data |
- +----------------------+-------------------------+------------------+
- | (variable length) | compressed-flag (1 byte)| data (variable) |
- | | message length (4 byte) | |
- +----------------------+-------------------------+------------------+
-"""
-
-__all__ = ["TriEncoder", "TriDecoder"]
-
-HEADER: str = "HEADER"
-PAYLOAD: str = "PAYLOAD"
-
-# About HEADER
-HEADER_LENGTH: int = 5
-COMPRESSED_FLAG_MASK: int = 1
-RESERVED_MASK = 0xFE
-DEFAULT_MAX_MESSAGE_SIZE: int = 4194304 # 4MB
-
-
-class TriEncoder:
- """
- This class is responsible for encoding the gRPC message format, which is composed of a header and payload.
- """
-
- __slots__ = ["_compressor"]
-
- def __init__(self, compressor: Optional[Compressor]):
- """
- Initialize the encoder.
- :param compressor: The compression to use for compressing the payload.
- :type compressor: Optional[Compressor]
- """
- self._compressor = compressor
-
- @property
- def compressor(self) -> Optional[Compressor]:
- """
- Get the compressor.
- :return: The compressor.
- :rtype: Optional[Compressor]
- """
- return self._compressor
-
- @compressor.setter
- def compressor(self, value: Compressor) -> None:
- """
- Set the compressor.
- :param value: The compressor.
- :type value: Compressor
- """
- self._compressor = value
-
- def encode(self, message: bytes, compress_flag: int) -> bytes:
- """
- Encode the message into the gRPC message format.
-
- :param message: The message to encode.
- :type message: bytes
- :param compress_flag: The compress flag. 0 for no compression, 1 for compression.
- :type compress_flag: int
- :return: The encoded message.
- :rtype: bytes
- """
-
- # check compress_flag
- if compress_flag not in [0, 1]:
- raise RpcError(f"compress_flag must be 0 or 1, but got {compress_flag}")
-
- # check message size
- if len(message) > DEFAULT_MAX_MESSAGE_SIZE:
- raise RpcError(f"Message too large. Allowed maximum size is 4194304 bytes, but got {len(message)} bytes.")
-
- # check compress_flag and compress the payload
- if compress_flag == 1:
- if not self._compressor:
- raise RpcError("compression is required when compress_flag is 1")
- message = self._compressor.compress(message)
-
- # Create the gRPC header
- # >: big-endian
- # B: unsigned char(1 byte) -> compressed_flag
- # I: unsigned int(4 bytes) -> message_length
- header = struct.pack(">BI", compress_flag, len(message))
-
- return header + message
-
-
-class TriDecoder:
- """
- This class is responsible for decoding the gRPC message format, which is composed of a header and payload.
- """
-
- __slots__ = [
- "_accumulate",
- "_listener",
- "_decompressor",
- "_state",
- "_required_length",
- "_decoding",
- "_compressed",
- "_closing",
- "_closed",
- ]
-
- def __init__(
- self,
- listener: "TriDecoder.Listener",
- decompressor: Optional[Decompressor],
- ):
- """
- Initialize the decoder.
- :param decompressor: The decompressor to use for decompressing the payload.
- :type decompressor: Optional[Decompressor]
- :param listener: The listener to deliver the decoded payload to when a message is received.
- :type listener: TriDecoder.Listener
- """
-
- self._listener = listener
- # store data for decoding
- self._accumulate = bytearray()
- self._decompressor = decompressor
-
- self._state = HEADER
- self._required_length = HEADER_LENGTH
-
- # decode state, if True, the decoder is currently processing a message
- self._decoding = False
-
- # whether the message is compressed
- self._compressed = False
-
- self._closing = False
- self._closed = False
-
- def decode(self, data: bytes) -> None:
- """
- Process the incoming bytes, decoding the gRPC message and delivering the payload to the listener.
- :param data: The data to decode.
- :type data: bytes
- """
- self._accumulate.extend(data)
- self._do_decode()
-
- def close(self) -> None:
- """
- Close the decoder and listener.
- """
- self._closing = True
- self._do_decode()
-
- def _do_decode(self) -> None:
- """
- Deliver the accumulated bytes to the listener, processing the header and payload as necessary.
- """
- if self._decoding:
- return
-
- self._decoding = True
- try:
- while self._has_enough_bytes():
- if self._state == HEADER:
- self._process_header()
- elif self._state == PAYLOAD:
- self._process_payload()
- if self._closing:
- if not self._closed:
- self._closed = True
- self._accumulate = None
- self._listener.close()
- finally:
- self._decoding = False
-
- def _has_enough_bytes(self) -> bool:
- """
- Check if the accumulated bytes are enough to process the header or payload
- :return: True if there are enough bytes, False otherwise.
- :rtype: bool
- """
- return len(self._accumulate) >= self._required_length
-
- def _process_header(self) -> None:
- """
- Processes the GRPC compression header which is composed of the compression flag and the outer frame length.
- """
- header_bytes = self._accumulate[: self._required_length]
- self._accumulate = self._accumulate[self._required_length :]
-
- # Parse the header
- compressed_flag = int(header_bytes[0])
- if (compressed_flag & RESERVED_MASK) != 0:
- raise RpcError("gRPC frame header malformed: reserved bits not zero")
- else:
- self._compressed = bool(compressed_flag & COMPRESSED_FLAG_MASK)
- self._required_length = int.from_bytes(header_bytes[1:], byteorder="big")
- # Continue to process the payload
- self._state = PAYLOAD
-
- def _process_payload(self) -> None:
- """
- Processes the GRPC message body, which depending on frame header flags may be compressed.
- """
- payload_bytes = self._accumulate[: self._required_length]
- self._accumulate = self._accumulate[self._required_length :]
-
- if self._compressed:
- # Decompress the payload
- payload_bytes = self._decompressor.decompress(payload_bytes)
-
- self._listener.on_message(bytes(payload_bytes))
-
- # Done with this frame, begin processing the next header.
- self._required_length = HEADER_LENGTH
- self._state = HEADER
-
- class Listener(abc.ABC):
- @abc.abstractmethod
- def on_message(self, message: bytes):
- """
- Called when a message is received.
- :param message: The message received.
- :type message: bytes
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def close(self):
- """
- Called when the listener is closed.
- """
- raise NotImplementedError()
diff --git a/src/dubbo/protocol/triple/constants.py b/src/dubbo/protocol/triple/constants.py
deleted file mode 100644
index d0684a6..0000000
--- a/src/dubbo/protocol/triple/constants.py
+++ /dev/null
@@ -1,125 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import enum
-
-
-class GRpcCode(enum.Enum):
- """
- RPC status codes.
- See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
- """
-
- # Not an error; returned on success.
- OK = 0
-
- # The operation was cancelled, typically by the caller.
- CANCELLED = 1
-
- # Unknown error.
- UNKNOWN = 2
-
- # The client specified an invalid argument.
- INVALID_ARGUMENT = 3
-
- # The deadline expired before the operation could complete.
- DEADLINE_EXCEEDED = 4
-
- # Some requested entity (e.g., file or directory) was not found
- NOT_FOUND = 5
-
- # The entity that a client attempted to create (e.g., file or directory) already exists.
- ALREADY_EXISTS = 6
-
- # The caller does not have permission to execute the specified operation.
- PERMISSION_DENIED = 7
-
- # Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.
- RESOURCE_EXHAUSTED = 8
-
- # The operation was rejected because the system is not in a state required for the operation's execution.
- FAILED_PRECONDITION = 9
-
- # The operation was aborted, typically due to a concurrency issue
- # such as a sequencer check failure or transaction abort.
- ABORTED = 10
-
- # The operation was attempted past the valid range.
- OUT_OF_RANGE = 11
-
- # The operation is not implemented or is not supported/enabled in this service.
- UNIMPLEMENTED = 12
-
- # Internal errors.
- INTERNAL = 13
-
- # The service is currently unavailable.
- UNAVAILABLE = 14
-
- # Unrecoverable data loss or corruption.
- DATA_LOSS = 15
-
- # The request does not have valid authentication credentials for the operation.
- UNAUTHENTICATED = 16
-
- @classmethod
- def from_code(cls, code: int) -> "GRpcCode":
- """
- Get the RPC status code from the given code.
- :param code: The RPC status code.
- :type code: int
- :return: The RPC status code.
- :rtype: GRpcCode
- """
- for rpc_code in cls:
- if rpc_code.value == code:
- return rpc_code
- return cls.UNKNOWN
-
-
-class TripleHeaderName(enum.Enum):
- """
- Header names used in triple protocol.
- """
-
- CONTENT_TYPE = "content-type"
-
- TE = "te"
- GRPC_STATUS = "grpc-status"
- GRPC_MESSAGE = "grpc-message"
- GRPC_STATUS_DETAILS_BIN = "grpc-status-details-bin"
- GRPC_TIMEOUT = "grpc-timeout"
- GRPC_ENCODING = "grpc-encoding"
- GRPC_ACCEPT_ENCODING = "grpc-accept-encoding"
-
- SERVICE_VERSION = "tri-service-version"
- SERVICE_GROUP = "tri-service-group"
-
- CONSUMER_APP_NAME = "tri-consumer-appname"
-
-
-class TripleHeaderValue(enum.Enum):
- """
- Header values used in triple protocol.
- """
-
- TRAILERS = "trailers"
- HTTP = "http"
- HTTPS = "https"
- APPLICATION_GRPC_PROTO = "application/grpc+data"
- APPLICATION_GRPC = "application/grpc"
-
- TEXT_PLAIN_UTF8 = "text/plain; encoding=utf-8"
diff --git a/src/dubbo/protocol/triple/exceptions.py b/src/dubbo/protocol/triple/exceptions.py
deleted file mode 100644
index 6dbfcb9..0000000
--- a/src/dubbo/protocol/triple/exceptions.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__all__ = ["RpcError", "StatusRpcError"]
-
-
-class RpcError(Exception):
- """
- The RPC exception.
- """
-
- def __init__(self, message: str):
- self.message = f"RPC Invocation failed: {message}"
- super().__init__(self.message)
-
- def __str__(self):
- return self.message
-
-
-class StatusRpcError(Exception):
- """
- The status RPC exception.
- """
-
- def __init__(self, status):
- self.status = status
- self.message = f"RPC Invocation failed: {status.code} {status.description}"
- super().__init__(status, self.message)
-
- def __str__(self):
- return self.message
diff --git a/src/dubbo/protocol/triple/invoker.py b/src/dubbo/protocol/triple/invoker.py
deleted file mode 100644
index 31a7c2d..0000000
--- a/src/dubbo/protocol/triple/invoker.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import concurrent
-from collections.abc import Iterator
-
-from dubbo.classes import MethodDescriptor
-from dubbo.compression import Compressor, Identity
-from dubbo.constants import common_constants
-from dubbo.extension import ExtensionError, extensionLoader
-from dubbo.loggers import loggerFactory
-from dubbo.protocol import Invoker, Result
-from dubbo.protocol.invocation import Invocation, RpcInvocation
-from dubbo.protocol.triple.call import TripleClientCall
-from dubbo.protocol.triple.call.client_call import (
- FutureToClientCallListenerAdapter,
- ReadStreamToClientCallListenerAdapter,
-)
-from dubbo.protocol.triple.constants import TripleHeaderName, TripleHeaderValue
-from dubbo.protocol.triple.metadata import RequestMetadata
-from dubbo.protocol.triple.results import TriResult
-from dubbo.protocol.triple.streams import (
- TriClientWriteStream,
- TriReadStream,
- TriReadWriteStream,
-)
-from dubbo.remoting import Client
-from dubbo.remoting.aio.exceptions import RemotingError
-from dubbo.remoting.aio.http2.stream_handler import StreamClientMultiplexHandler
-from dubbo.serialization import (
- CustomDeserializer,
- CustomSerializer,
- DirectDeserializer,
- DirectSerializer,
-)
-from dubbo.url import URL
-from dubbo.utils import FunctionHelper
-
-__all__ = ["TripleInvoker"]
-
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class TripleInvoker(Invoker):
- """
- Triple invoker.
- """
-
- __slots__ = ["_url", "_client", "_stream_multiplexer", "_compression"]
-
- def __init__(self, url: URL, client: Client, stream_multiplexer: StreamClientMultiplexHandler):
- self._url = url
- self._client = client
- self._stream_multiplexer = stream_multiplexer
-
- def invoke(self, invocation: RpcInvocation) -> Result:
- """
- Invoke the invocation.
- :param invocation: The invocation to invoke.
- :type invocation: RpcInvocation
- :return: The result of the invocation.
- :rtype: Result
- """
- future = concurrent.futures.Future()
- result = TriResult(future)
- if not self._client.is_connected():
- result.set_exception(RemotingError("The client is not connected to the server."))
- return result
-
- # get method descriptor
- method_descriptor: MethodDescriptor = invocation.get_attribute(common_constants.METHOD_DESCRIPTOR_KEY)
-
- # get arg_serializer
- arg_serializing_function = method_descriptor.get_arg_serializer()
- arg_serializer = CustomSerializer(arg_serializing_function) if arg_serializing_function else DirectSerializer()
-
- # get return_deserializer
- return_deserializing_function = method_descriptor.get_return_deserializer()
- return_deserializer = (
- CustomDeserializer(return_deserializing_function) if return_deserializing_function else DirectDeserializer()
- )
-
- write_stream = TriClientWriteStream()
- read_stream = TriReadStream()
-
- # create listener
- rpc_type = method_descriptor.get_rpc_type()
- is_unary = not rpc_type.client_stream and not rpc_type.server_stream
- if is_unary:
- listener = FutureToClientCallListenerAdapter(future)
- else:
- read_stream = TriReadStream()
- listener = ReadStreamToClientCallListenerAdapter(read_stream)
-
- # Create a new TriClientCall
- tri_client_call = TripleClientCall(
- self._stream_multiplexer,
- listener,
- arg_serializer,
- return_deserializer,
- )
- write_stream.set_call(tri_client_call)
-
- if not is_unary:
- write_stream = TriReadWriteStream(write_stream, read_stream)
-
- # start the call
- try:
- metadata = self._create_metadata(invocation)
- tri_client_call.start(metadata)
- except ExtensionError as e:
- result.set_exception(e)
- return result
-
- # write the message
- if not rpc_type.client_stream:
- # if the client call is not a stream, we send the message directly
- FunctionHelper.call_func(write_stream.write, invocation.get_argument())
- write_stream.done_writing()
- else:
- # try to get first argument and check if it is an iterable
- args, _ = invocation.get_argument()
- if args and isinstance(args[0], Iterator):
- # if the argument is an iterator, we need to write the stream
- for arg in args[0]:
- write_stream.write(arg)
- write_stream.done_writing()
-
- # If the call is not unary, we need to return the stream
- # server_stream -> return read_stream
- # client_stream or bidirectional_stream -> return write_read_stream
- if not is_unary:
- if rpc_type.server_stream and not rpc_type.client_stream:
- result.set_value(read_stream)
- else:
- result.set_value(write_stream)
-
- return result
-
- def _create_metadata(self, invocation: Invocation) -> RequestMetadata:
- """
- Create the metadata.
- :param invocation: The invocation.
- :type invocation: Invocation
- :return: The metadata.
- :rtype: RequestMetadata
- :raise ExtensionError: If the compressor is not supported.
- """
- metadata = RequestMetadata()
- # set service and method
- metadata.service = invocation.get_service_name()
- metadata.method = invocation.get_method_name()
-
- # get scheme
- metadata.scheme = (
- TripleHeaderValue.HTTPS.value
- if self._url.parameters.get(common_constants.SSL_ENABLED_KEY, False)
- else TripleHeaderValue.HTTP.value
- )
-
- # get compressor
- compression = self._url.parameters.get(common_constants.COMPRESSION_KEY, Identity.get_message_encoding())
- if metadata.compressor.get_message_encoding() != compression:
- try:
- metadata.compressor = extensionLoader.get_extension(Compressor, compression)()
- except ExtensionError as e:
- _LOGGER.error("Unsupported compressor: %s", compression)
- raise e
-
- # get address
- metadata.address = self._url.location
-
- # TODO add more metadata
- metadata.attachments[TripleHeaderName.TE.value] = TripleHeaderValue.TRAILERS.value
-
- return metadata
-
- def get_url(self) -> URL:
- return self._url
-
- def is_available(self) -> bool:
- return self._client.is_connected()
-
- def destroy(self) -> None:
- self._client.close()
- self._client = None
- self._stream_multiplexer = None
- self._url = None
diff --git a/src/dubbo/protocol/triple/metadata.py b/src/dubbo/protocol/triple/metadata.py
deleted file mode 100644
index 40692c9..0000000
--- a/src/dubbo/protocol/triple/metadata.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any, Optional
-
-from dubbo.compression import Compressor, Identity
-from dubbo.protocol.triple.constants import TripleHeaderName, TripleHeaderValue
-from dubbo.remoting.aio.http2.headers import Http2Headers, HttpMethod
-
-
-class RequestMetadata:
- """
- The request metadata.
- """
-
- def __init__(self):
- self.scheme: Optional[str] = None
- self.application: Optional[str] = None
- self.service: Optional[str] = None
- self.version: Optional[str] = None
- self.group: Optional[str] = None
- self.address: Optional[str] = None
- self.acceptEncoding: Optional[str] = None
- self.timeout: Optional[str] = None
- self.compressor: Compressor = Identity()
- self.method: Optional[str] = None
- self.attachments: dict[str, Any] = {}
-
- def to_headers(self) -> Http2Headers:
- """
- Convert to HTTP/2 headers.
- :return: The HTTP/2 headers.
- :rtype: Http2Headers
- """
- headers = Http2Headers()
- headers.scheme = self.scheme
- headers.authority = self.address
- headers.method = HttpMethod.POST.value
- headers.path = f"/{self.service}/{self.method}"
- headers.add(
- TripleHeaderName.CONTENT_TYPE.value,
- TripleHeaderValue.APPLICATION_GRPC_PROTO.value,
- )
-
- if self.version != "1.0.0":
- set_if_not_none(headers, TripleHeaderName.SERVICE_VERSION.value, self.version)
-
- set_if_not_none(headers, TripleHeaderName.GRPC_TIMEOUT.value, self.timeout)
- set_if_not_none(headers, TripleHeaderName.SERVICE_GROUP.value, self.group)
- set_if_not_none(headers, TripleHeaderName.CONSUMER_APP_NAME.value, self.application)
- set_if_not_none(headers, TripleHeaderName.GRPC_ENCODING.value, self.acceptEncoding)
-
- if self.compressor.get_message_encoding() != Identity.get_message_encoding():
- set_if_not_none(
- headers,
- TripleHeaderName.GRPC_ENCODING.value,
- self.compressor.get_message_encoding(),
- )
-
- [headers.add(k, str(v)) for k, v in self.attachments.items()]
-
- return headers
-
-
-def set_if_not_none(headers: Http2Headers, key: str, value: Optional[str]) -> None:
- """
- Set the header if the value is not None.
- :param headers: The headers.
- :type headers: Http2Headers
- :param key: The key.
- :type key: str
- :param value: The value.
- :type value: Optional[str]
- """
- if value:
- headers.add(key, str(value))
diff --git a/src/dubbo/protocol/triple/protocol.py b/src/dubbo/protocol/triple/protocol.py
deleted file mode 100644
index f4aa4d3..0000000
--- a/src/dubbo/protocol/triple/protocol.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import functools
-import uuid
-from collections.abc import Iterable
-from concurrent.futures import ThreadPoolExecutor
-from typing import Optional
-
-from dubbo.constants import common_constants
-from dubbo.extension import extensionLoader
-from dubbo.loggers import loggerFactory
-from dubbo.protocol import Invoker, Protocol
-from dubbo.protocol.triple.invoker import TripleInvoker
-from dubbo.protocol.triple.stream.server_stream import ServerTransportListener
-from dubbo.proxy.handlers import RpcServiceHandler
-from dubbo.remoting import Server, Transporter
-from dubbo.remoting.aio import constants as aio_constants
-from dubbo.remoting.aio.http2.protocol import Http2ClientProtocol, Http2ServerProtocol
-from dubbo.remoting.aio.http2.stream_handler import (
- StreamClientMultiplexHandler
-)
-from dubbo.url import URL
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class TripleProtocol(Protocol):
- """
- Triple protocol.
- """
-
- __slots__ = ["_url", "_transporter", "_invokers"]
-
- def __init__(self):
- self._transporter: Transporter = extensionLoader.get_extension(
- Transporter, common_constants.TRANSPORTER_DEFAULT_VALUE
- )()
- self._invokers = []
- self._server: Optional[Server] = None
-
- self._path_resolver: dict[str, RpcServiceHandler] = {}
-
- def export(self, url: URL):
- """
- Export a service.
- """
- if self._server is not None:
- return
-
- service_handler = url.attributes[common_constants.SERVICE_HANDLER_KEY]
-
- if isinstance(service_handler, Iterable):
- for handler in service_handler:
- self._path_resolver[handler.service_name] = handler
- else:
- self._path_resolver[service_handler.service_name] = service_handler
-
- method_executor = ThreadPoolExecutor(thread_name_prefix=f"dubbo_tri_method_{str(uuid.uuid4())}", max_workers=10)
-
- listener_factory = functools.partial(ServerTransportListener, self._path_resolver, method_executor)
-
- # set stream handler and protocol
- url.attributes[aio_constants.LISTENER_FACTORY_KEY] = listener_factory
- url.attributes[common_constants.PROTOCOL_KEY] = Http2ServerProtocol
-
- # Create a server
- self._server = self._transporter.bind(url)
-
- def refer(self, url: URL) -> Invoker:
- """
- Refer a remote service.
- :param url: The URL.
- :type url: URL
- """
- # Create a stream handler
- stream_multiplexer = StreamClientMultiplexHandler()
- # set stream handler and protocol
- url.attributes[aio_constants.STREAM_HANDLER_KEY] = stream_multiplexer
- url.attributes[common_constants.PROTOCOL_KEY] = Http2ClientProtocol
-
- # Create a client
- client = self._transporter.connect(url)
- invoker = TripleInvoker(url, client, stream_multiplexer)
- self._invokers.append(invoker)
- return invoker
diff --git a/src/dubbo/protocol/triple/results.py b/src/dubbo/protocol/triple/results.py
deleted file mode 100644
index a8ced5f..0000000
--- a/src/dubbo/protocol/triple/results.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any
-
-from dubbo.protocol import Result
-
-
-class TriResult(Result):
- """
- The triple result.
- """
-
- __slots__ = ["_future"]
-
- def __init__(self, future):
- self._future = future
-
- def set_value(self, value: Any) -> None:
- """
- Set the value.
- """
- self._future.set_result(value)
-
- def value(self) -> Any:
- """
- Get the value.
- """
- return self._future.result()
-
- def set_exception(self, exception: Exception) -> None:
- """
- Set the exception.
- """
- self._future.set_exception(exception)
-
- def exception(self) -> Exception:
- """
- Get the exception.
- """
- return self._future.exception()
diff --git a/src/dubbo/protocol/triple/status.py b/src/dubbo/protocol/triple/status.py
deleted file mode 100644
index 6e31790..0000000
--- a/src/dubbo/protocol/triple/status.py
+++ /dev/null
@@ -1,152 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Optional, Union
-
-from dubbo.protocol.triple.constants import GRpcCode
-from dubbo.protocol.triple.exceptions import StatusRpcError
-from dubbo.remoting.aio.http2.registries import HttpStatus
-
-
-class TriRpcStatus:
- """
- RPC status.
- """
-
- __slots__ = ["_code", "_cause", "_description"]
-
- def __init__(
- self,
- code: GRpcCode,
- cause: Optional[Exception] = None,
- description: Optional[str] = None,
- ):
- """
- Initialize the RPC status.
- :param code: The RPC status code.
- :type code: TriRpcCode
- :param description: The description.
- :type description: Optional[str]
- :param cause: The exception cause.
- :type cause: Optional[Exception]
- """
- if isinstance(code, int):
- code = GRpcCode.from_code(code)
- self._code = code
- self._description = description
- self._cause = cause
-
- @property
- def code(self) -> GRpcCode:
- return self._code
-
- @property
- def description(self) -> Optional[str]:
- return self._description
-
- @property
- def cause(self) -> Optional[Exception]:
- return self._cause
-
- def with_description(self, description: str) -> "TriRpcStatus":
- """
- Set the description.
- :param description: The description.
- :type description: str
- :return: The RPC status.
- :rtype: TriRpcStatus
- """
- self._description = description
- return self
-
- def with_cause(self, cause: Exception) -> "TriRpcStatus":
- """
- Set the cause.
- :param cause: The cause.
- :type cause: Exception
- :return: The RPC status.
- :rtype: TriRpcStatus
- """
- self._cause = cause
- return self
-
- def append_description(self, description: str) -> None:
- """
- Append the description.
- :param description: The description to append.
- :type description: str
- """
- if self._description:
- self._description += f"\n{description}"
- else:
- self._description = description
-
- def as_exception(self) -> Exception:
- """
- Convert the RPC status to an exception.
- :return: The exception.
- :rtype: Exception
- """
- return StatusRpcError(self)
-
- @staticmethod
- def limit_desc(description: str, limit: int = 1024) -> str:
- """
- Limit the description length.
- :param description: The description.
- :type description: str
- :param limit: The limit.(default: 1024)
- :type limit: int
- :return: The limited description.
- :rtype: str
- """
- if description and len(description) > limit:
- return f"{description[:limit]}..."
- return description
-
- @classmethod
- def from_rpc_code(cls, code: Union[int, GRpcCode]):
- if isinstance(code, int):
- code = GRpcCode.from_code(code)
- return cls(code)
-
- @classmethod
- def from_http_code(cls, code: Union[int, HttpStatus]):
- http_status = HttpStatus.from_code(code) if isinstance(code, int) else code
- rpc_code = GRpcCode.UNKNOWN
- if HttpStatus.is_1xx(http_status) or http_status in [
- HttpStatus.BAD_REQUEST,
- HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
- ]:
- rpc_code = GRpcCode.INTERNAL
- elif http_status == HttpStatus.UNAUTHORIZED:
- rpc_code = GRpcCode.UNAUTHENTICATED
- elif http_status == HttpStatus.FORBIDDEN:
- rpc_code = GRpcCode.PERMISSION_DENIED
- elif http_status == HttpStatus.NOT_FOUND:
- rpc_code = GRpcCode.NOT_FOUND
- elif http_status in [
- HttpStatus.BAD_GATEWAY,
- HttpStatus.TOO_MANY_REQUESTS,
- HttpStatus.SERVICE_UNAVAILABLE,
- HttpStatus.GATEWAY_TIMEOUT,
- ]:
- rpc_code = GRpcCode.UNAVAILABLE
-
- return cls(rpc_code)
-
- def __repr__(self):
- return f"TriRpcStatus(code={self._code}, cause={self._cause}, description={self._description})"
diff --git a/src/dubbo/protocol/triple/stream/__init__.py b/src/dubbo/protocol/triple/stream/__init__.py
deleted file mode 100644
index 5dc8c8f..0000000
--- a/src/dubbo/protocol/triple/stream/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import ClientStream, ServerStream
-
-__all__ = ["ClientStream", "ServerStream"]
diff --git a/src/dubbo/protocol/triple/stream/_interfaces.py b/src/dubbo/protocol/triple/stream/_interfaces.py
deleted file mode 100644
index 92c04e6..0000000
--- a/src/dubbo/protocol/triple/stream/_interfaces.py
+++ /dev/null
@@ -1,165 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from typing import Any
-
-from dubbo.protocol.triple.status import TriRpcStatus
-from dubbo.remoting.aio.http2.headers import Http2Headers
-
-__all__ = ["Stream", "ClientStream", "ServerStream"]
-
-
-class Stream(abc.ABC):
- """
- Stream is a bidirectional channel that manipulates the data flow between peers.
- Inbound data from remote peer is acquired by Stream.Listener.
- Outbound data to remote peer is sent directly by Stream
- """
-
- @abc.abstractmethod
- def send_headers(self, headers: Http2Headers) -> None:
- """
- Send headers to remote peer
- :param headers: The headers to send
- :type headers: Http2Headers
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def cancel_by_local(self, status: TriRpcStatus) -> None:
- """
- Cancel the stream by local
- :param status: The status
- :type status: TriRpcStatus
- """
- raise NotImplementedError()
-
- class Listener(abc.ABC):
- """
- Listener is a callback interface that receives events on the stream.
- """
-
- @abc.abstractmethod
- def on_message(self, data: bytes) -> None:
- """
- Called when data is received.
- :param data: The data received
- :type data: bytes
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def on_cancel_by_remote(self, status: TriRpcStatus) -> None:
- """
- Called when the stream is cancelled by remote
- :param status: The status
- :type status: TriRpcStatus
- """
- raise NotImplementedError()
-
-
-class ClientStream(Stream, abc.ABC):
- """
- ClientStream is used to send request to server and receive response from server.
- """
-
- @abc.abstractmethod
- def send_message(self, data: bytes, compress_flag: int, last: bool) -> None:
- """
- Send message to remote peer
- :param data: The message data
- :type data: bytes
- :param compress_flag: The compress flag (0: no compress, 1: compress)
- :type compress_flag: int
- :param last: Whether this is the last message
- :type last: bool
- """
- raise NotImplementedError()
-
- class Listener(Stream.Listener, abc.ABC):
- """
- Listener is a callback interface that receives events on the stream.
- """
-
- @abc.abstractmethod
- def on_complete(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- """
- Called when the stream is completed.
- :param status: The status
- :type status: TriRpcStatus
- :param attachments: The attachments
- :type attachments: Dict[str,Any]
- """
- raise NotImplementedError()
-
-
-class ServerStream(Stream, abc.ABC):
- """
- ServerStream is used to receive request from client and send response to client.
- """
-
- @abc.abstractmethod
- def set_compression(self, compression: str) -> None:
- """
- Set the compression.
- :param compression: The compression
- :type compression: str
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def send_message(self, data: bytes, compress_flag: bool) -> None:
- """
- Send message to remote peer
- :param data: The message data
- :type data: bytes
- :param compress_flag: The compress flag
- :type compress_flag: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def complete(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- """
- Complete the stream
- :param status: The status
- :type status: TriRpcStatus
- :param attachments: The attachments
- :type attachments: Dict[str,Any]
- """
- raise NotImplementedError()
-
- class Listener(Stream.Listener, abc.ABC):
- """
- Listener is a callback interface that receives events on the stream.
- """
-
- @abc.abstractmethod
- def on_headers(self, headers: dict[str, Any]) -> None:
- """
- Called when headers are received.
- :param headers: The headers
- :type headers: Http2Headers
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def on_complete(self) -> None:
- """
- Callback when no more data from client side
- """
- raise NotImplementedError()
diff --git a/src/dubbo/protocol/triple/stream/client_stream.py b/src/dubbo/protocol/triple/stream/client_stream.py
deleted file mode 100644
index 0007acb..0000000
--- a/src/dubbo/protocol/triple/stream/client_stream.py
+++ /dev/null
@@ -1,300 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Optional
-
-from dubbo.compression import Compressor, Decompressor
-from dubbo.compression.identities import Identity
-from dubbo.extension import ExtensionError, extensionLoader
-from dubbo.protocol.triple.coders import TriDecoder, TriEncoder
-from dubbo.protocol.triple.constants import (
- GRpcCode,
- TripleHeaderName,
- TripleHeaderValue,
-)
-from dubbo.protocol.triple.status import TriRpcStatus
-from dubbo.protocol.triple.stream import ClientStream
-from dubbo.remoting.aio.http2.headers import Http2Headers
-from dubbo.remoting.aio.http2.registries import Http2ErrorCode
-from dubbo.remoting.aio.http2.stream import Http2Stream
-
-__all__ = ["TriClientStream"]
-
-
-class TriClientStream(ClientStream):
- """
- Triple client stream.
- """
-
- def __init__(
- self,
- listener: ClientStream.Listener,
- compressor: Optional[Compressor],
- ):
- """
- Initialize the triple client stream.
- :param listener: The listener.
- :type listener: ClientStream.Listener
- :param compressor: The compression.
- """
- self._transport_listener = ClientTransportListener(listener)
- self._encoder = TriEncoder(compressor)
-
- self._stream: Optional[Http2Stream] = None
-
- @property
- def transport_listener(self) -> "ClientTransportListener":
- """
- Get the transport listener.
- :return: The transport listener.
- :rtype: ClientTransportListener
- """
- return self._transport_listener
-
- def bind(self, stream: Http2Stream) -> None:
- """
- Bind the stream.
- :param stream: The stream to bind.
- :type stream: Http2Stream
- """
- self._stream = stream
-
- def send_headers(self, headers: Http2Headers) -> None:
- """
- Send headers to remote peer.
- :param headers: The headers to send.
- :type headers: Http2Headers
- """
- self._stream.send_headers(headers)
-
- def send_message(self, data: bytes, compress_flag: int, last: bool) -> None:
- """
- Send message to remote peer.
- :param data: The message data.
- :type data: bytes
- :param compress_flag: The compress flag (0: no compress, 1: compress).
- :type compress_flag: int
- :param last: Whether this is the last message.
- :type last: bool
- """
- # encode the data
- encoded_data = self._encoder.encode(data, compress_flag)
- self._stream.send_data(encoded_data, last)
-
- def cancel_by_local(self, status: TriRpcStatus) -> None:
- """
- Cancel the stream by local
- :param status: The status
- :type status: TriRpcStatus
- """
- self._stream.cancel_by_local(Http2ErrorCode.CANCEL)
- self._transport_listener.rst = True
-
-
-class ClientTransportListener(Http2Stream.Listener, TriDecoder.Listener):
- """
- Client transport listener.
- """
-
- __slots__ = [
- "_listener",
- "_decoder",
- "_rpc_status",
- "_headers_received",
- "_rst",
- ]
-
- def __init__(self, listener: ClientStream.Listener):
- """
- Initialize the client transport listener.
- :param listener: The listener.
- """
- super().__init__()
- self._listener = listener
-
- self._decoder: Optional[TriDecoder] = None
- self._rpc_status: Optional[TriRpcStatus] = None
-
- self._headers_received = False
- self._rst = False
-
- self._trailers: Http2Headers = Http2Headers()
-
- @property
- def rst(self) -> bool:
- """
- Whether the stream is rest.
- :return: True if the stream is rest, otherwise False.
- :rtype: bool
- """
- return self._rst
-
- @rst.setter
- def rst(self, value: bool) -> None:
- """
- Set whether the stream is rest.
- :param value: True if the stream is rest, otherwise False.
- :type value: bool
- """
- self._rst = value
-
- def on_headers(self, headers: Http2Headers, end_stream: bool) -> None:
- if not end_stream:
- # handle headers
- self._on_headers_received(headers)
- else:
- # handle trailers
- self._on_trailers_received(headers)
-
- if end_stream and not self._headers_received:
- self._handle_transport_error(self._rpc_status)
-
- def on_data(self, data: bytes, end_stream: bool) -> None:
- if self._rpc_status:
- self._rpc_status.append_description(f"Data: {data.decode('utf-8')}")
- if len(self._rpc_status.description) > 512 or end_stream:
- self._handle_transport_error(self._rpc_status)
- return
-
- # decode the data
- self._decoder.decode(data)
-
- def cancel_by_remote(self, error_code: Http2ErrorCode) -> None:
- self.rst = True
- self._rpc_status = TriRpcStatus(
- GRpcCode.CANCELLED,
- description=f"Cancelled by remote peer, error code: {error_code}",
- )
- self._listener.on_complete(self._rpc_status, self._trailers.to_dict())
-
- def _on_headers_received(self, headers: Http2Headers) -> None:
- """
- Handle the headers received.
- :param headers: The headers.
- :type headers: Http2Headers
- """
- self._headers_received = True
-
- # validate headers
- self._validate_headers(headers)
- if self._rpc_status:
- return
-
- # get messageEncoding
- decompressor: Optional[Decompressor] = None
- message_encoding = headers.get(TripleHeaderName.GRPC_ENCODING.value, Identity.get_message_encoding())
- if message_encoding != Identity.get_message_encoding():
- try:
- # get decompressor by messageEncoding
- decompressor = extensionLoader.get_extension(Decompressor, message_encoding)()
- except ExtensionError:
- # unsupported
- self._rpc_status = TriRpcStatus(
- GRpcCode.UNIMPLEMENTED,
- description="Unsupported message encoding",
- )
- return
-
- self._decoder = TriDecoder(self, decompressor)
-
- def _validate_headers(self, headers: Http2Headers) -> None:
- """
- Validate the headers.
- :param headers: The headers.
- :type headers: Http2Headers
- """
- status_code = int(headers.status) if headers.status else None
- if status_code:
- content_type = headers.get(TripleHeaderName.CONTENT_TYPE.value, "")
- if not content_type.startswith(TripleHeaderValue.APPLICATION_GRPC.value):
- self._rpc_status = TriRpcStatus.from_http_code(status_code).with_description(
- f"Invalid content type: {content_type}"
- )
-
- else:
- self._rpc_status = TriRpcStatus(GRpcCode.INTERNAL, description="Missing HTTP status code")
-
- def _on_trailers_received(self, trailers: Http2Headers) -> None:
- """
- Handle the trailers received.
- :param trailers: The trailers.
- :type trailers: Http2Headers
- """
- if not self._rpc_status and not self._headers_received:
- self._validate_headers(trailers)
-
- if self._rpc_status:
- self._rpc_status.append_description(f"Trailers: {trailers}")
- else:
- self._rpc_status = self._get_status_from_trailers(trailers)
- self._trailers = trailers
-
- if self._decoder:
- self._decoder.close()
- else:
- self._listener.on_complete(self._rpc_status, trailers.to_dict())
-
- def _get_status_from_trailers(self, trailers: Http2Headers) -> TriRpcStatus:
- """
- Validate the trailers.
- :param trailers: The trailers.
- :type trailers: Http2Headers
- :return: The RPC status.
- :rtype: TriRpcStatus
- """
- grpc_status_code = int(trailers.get(TripleHeaderName.GRPC_STATUS.value, "-1"))
- if grpc_status_code != -1:
- status = TriRpcStatus.from_rpc_code(grpc_status_code)
- message = trailers.get(TripleHeaderName.GRPC_MESSAGE.value, "")
- status.append_description(message)
- return status
-
- # If the status code is not found , something is broken. Try to provide a rational error.
- if self._headers_received:
- return TriRpcStatus(GRpcCode.UNKNOWN, description="Missing GRPC status in response")
-
- # Try to get status from headers
- status_code = int(trailers.status) if trailers.status else None
- if status_code is not None:
- status = TriRpcStatus.from_http_code(status_code)
- else:
- status = TriRpcStatus(GRpcCode.INTERNAL, description="Missing HTTP status code")
-
- status.append_description("Missing GRPC status, please infer the error from the HTTP status code")
- return status
-
- def _handle_transport_error(self, transport_error: TriRpcStatus) -> None:
- """
- Handle the transport error.
- :param transport_error: The transport error.
- :type transport_error: TriRpcStatus
- """
- self._stream.cancel_by_local(Http2ErrorCode.NO_ERROR)
- self.rst = True
- self._listener.on_complete(transport_error, self._trailers.to_dict())
-
- def on_message(self, message: bytes) -> None:
- """
- Called when a message is received (TriDecoder.Listener callback).
- :param message: The message received.
- """
- self._listener.on_message(message)
-
- def close(self) -> None:
- """
- Called when the stream is closed (TriDecoder.Listener callback).
- """
- self._listener.on_complete(self._rpc_status, self._trailers.to_dict())
diff --git a/src/dubbo/protocol/triple/stream/server_stream.py b/src/dubbo/protocol/triple/stream/server_stream.py
deleted file mode 100644
index 78545c9..0000000
--- a/src/dubbo/protocol/triple/stream/server_stream.py
+++ /dev/null
@@ -1,315 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import logging
-from concurrent.futures import ThreadPoolExecutor
-from typing import Any, Optional
-
-from dubbo.compression import Decompressor
-from dubbo.compression.identities import Identity
-from dubbo.extension import ExtensionError, extensionLoader
-from dubbo.loggers import loggerFactory
-from dubbo.protocol.triple.call.server_call import TripleServerCall
-from dubbo.protocol.triple.coders import TriDecoder, TriEncoder
-from dubbo.protocol.triple.constants import (
- GRpcCode,
- TripleHeaderName,
- TripleHeaderValue,
-)
-from dubbo.protocol.triple.status import TriRpcStatus
-from dubbo.protocol.triple.stream import ServerStream
-from dubbo.proxy.handlers import RpcMethodHandler, RpcServiceHandler
-from dubbo.remoting.aio.http2.headers import Http2Headers, HttpMethod
-from dubbo.remoting.aio.http2.registries import Http2ErrorCode, HttpStatus
-from dubbo.remoting.aio.http2.stream import Http2Stream
-
-__all__ = ["ServerTransportListener", "TripleServerStream"]
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class TripleServerStream(ServerStream):
- def __init__(self, stream: Http2Stream):
- self._stream = stream
-
- self._tri_encoder = TriEncoder(Identity())
-
- self._rst = False
- self._headers_sent = False
- self._trailers_sent = False
-
- @property
- def rst(self) -> bool:
- return self._rst
-
- @rst.setter
- def rst(self, value: bool) -> None:
- self._rst = value
-
- @property
- def headers_sent(self) -> bool:
- return self._headers_sent
-
- @property
- def trailers_sent(self) -> bool:
- return self._trailers_sent
-
- def set_compression(self, compression: str) -> None:
- if compression == Identity.get_message_encoding():
- return
- try:
- decompressor = extensionLoader.get_extension(Decompressor, compression)()
- self._tri_encoder.compressor = decompressor
- except ExtensionError:
- _LOGGER.warning("Unsupported compression: %s", compression)
- self.cancel_by_local(TriRpcStatus(GRpcCode.INTERNAL, description="Unsupported compression"))
-
- def send_headers(self, headers: Http2Headers) -> None:
- if not self.headers_sent:
- self._stream.send_headers(headers)
- self._headers_sent = True
-
- def send_message(self, data: bytes, compress_flag: bool) -> None:
- # encode the message
- encoded_data = self._tri_encoder.encode(data, compress_flag)
- self._stream.send_data(encoded_data, end_stream=False)
-
- def complete(self, status: TriRpcStatus, attachments: dict[str, Any]) -> None:
- trailers = Http2Headers()
- if not self.headers_sent:
- trailers.status = HttpStatus.OK.value
- trailers.add(
- TripleHeaderName.CONTENT_TYPE.value,
- TripleHeaderValue.APPLICATION_GRPC_PROTO.value,
- )
-
- # add attachments
- [trailers.add(k, v) for k, v in attachments.items()]
-
- # add status
- trailers.add(TripleHeaderName.GRPC_STATUS.value, status.code.value)
- if status.code is not GRpcCode.OK:
- trailers.add(
- TripleHeaderName.GRPC_MESSAGE.value,
- TriRpcStatus.limit_desc(status.description),
- )
-
- # send trailers
- self._headers_sent = True
- self._trailers_sent = True
- self._stream.send_headers(trailers, end_stream=True)
-
- def cancel_by_local(self, status: TriRpcStatus) -> None:
- if _LOGGER.isEnabledFor(logging.DEBUG):
- _LOGGER.debug("Cancel stream: %s, status: %s", self._stream.id, status)
-
- if not self._rst:
- self._rst = True
- self._stream.cancel_by_local(Http2ErrorCode.CANCEL)
-
-
-class ServerTransportListener(Http2Stream.Listener):
- """
- ServerTransportListener is a callback interface that receives events on the stream.
- """
-
- def __init__(
- self,
- service_handles: dict[str, RpcServiceHandler],
- method_executor: ThreadPoolExecutor,
- ):
- super().__init__()
- self._listener: Optional[ServerStream.Listener] = None
- self._decoder: Optional[TriDecoder] = None
- self._service_handles = service_handles
- self._executor: Optional[ThreadPoolExecutor] = method_executor
-
- def on_headers(self, headers: Http2Headers, end_stream: bool) -> None:
- # check http method
- if headers.method != HttpMethod.POST.value:
- self._response_plain_text_error(
- HttpStatus.METHOD_NOT_ALLOWED.value,
- TriRpcStatus(
- GRpcCode.INTERNAL,
- description=f"Method {headers.method} is not supported",
- ),
- )
- return
-
- # check content type
- content_type = headers.get(TripleHeaderName.CONTENT_TYPE.value, "")
- if not content_type.startswith(TripleHeaderValue.APPLICATION_GRPC.value):
- self._response_plain_text_error(
- HttpStatus.UNSUPPORTED_MEDIA_TYPE.value,
- TriRpcStatus(
- GRpcCode.UNIMPLEMENTED,
- description=(
- f"Content-Type {content_type} is not supported"
- if content_type
- else "Content-Type is missing from the request"
- ),
- ),
- )
- return
-
- # check path
- path = headers.path
- if not path:
- self._response_plain_text_error(
- HttpStatus.NOT_FOUND.value,
- TriRpcStatus(
- GRpcCode.UNIMPLEMENTED,
- description="Expected path but is missing",
- ),
- )
- return
- elif not path.startswith("/"):
- self._response_plain_text_error(
- HttpStatus.NOT_FOUND.value,
- TriRpcStatus(
- GRpcCode.UNIMPLEMENTED,
- description=f"Expected path to start with /: {path}",
- ),
- )
- return
-
- # split the path
- parts = path.split("/")
- if len(parts) != 3:
- self._response_error(TriRpcStatus(GRpcCode.UNIMPLEMENTED, description=f"Bad path format: {path}"))
- return
-
- service_name, method_name = parts[1], parts[2]
-
- # get method handler
- handler = self._get_handler(service_name, method_name)
- if not handler:
- self._response_error(
- TriRpcStatus(
- GRpcCode.UNIMPLEMENTED,
- description=f"Service {service_name} is not found",
- )
- )
- return
-
- if end_stream:
- # Invalid request, ignore it.
- return
-
- decompressor: Decompressor = Identity()
- message_encoding = headers.get(TripleHeaderName.GRPC_ENCODING.value)
- if message_encoding and message_encoding != decompressor.get_message_encoding():
- # update decompressor
- try:
- decompressor = extensionLoader.get_extension(Decompressor, message_encoding)()
- except ExtensionError:
- self._response_error(
- TriRpcStatus(
- GRpcCode.UNIMPLEMENTED,
- description=f"Grpc-encoding '{message_encoding}' is not supported",
- )
- )
- return
-
- # create a server call
- self._listener = TripleServerCall(TripleServerStream(self._stream), handler, self._executor)
-
- # create a decoder
- self._decoder = TriDecoder(ServerTransportListener.ServerDecoderListener(self._listener), decompressor)
-
- # deliver the headers to the listener
- self._listener.on_headers(headers.to_dict())
-
- def _get_handler(self, service_name: str, method_name: str) -> Optional[RpcMethodHandler]:
- """
- Get the method handler.
- :param service_name: The service name
- :type service_name: str
- :param method_name: The method name
- :type method_name: str
- :return: The method handler
- :rtype: Optional[RpcMethodHandler]
- """
- if self._service_handles:
- service_handler = self._service_handles.get(service_name)
- if service_handler:
- return service_handler.method_handlers.get(method_name)
- return None
-
- def on_data(self, data: bytes, end_stream: bool) -> None:
- if self._decoder:
- self._decoder.decode(data)
- if end_stream:
- self._decoder.close()
-
- def cancel_by_remote(self, error_code: Http2ErrorCode) -> None:
- if self._listener:
- self._listener.on_cancel_by_remote(
- TriRpcStatus(
- GRpcCode.CANCELLED,
- description=f"Canceled by client ,errorCode= {error_code.value}",
- )
- )
-
- def _response_plain_text_error(self, code: int, status: TriRpcStatus) -> None:
- """
- Error before create server stream, http plain text will be returned.
- :param code: The error code
- :type code: int
- :param status: The status
- :type status: TriRpcStatus
- """
- # create headers
- headers = Http2Headers()
- headers.status = code
- headers.add(TripleHeaderName.GRPC_STATUS.value, status.code.value)
- headers.add(TripleHeaderName.GRPC_MESSAGE.value, status.description)
- headers.add(TripleHeaderName.CONTENT_TYPE.value, TripleHeaderValue.TEXT_PLAIN_UTF8.value)
-
- # send headers
- self._stream.send_headers(headers, end_stream=True)
-
- def _response_error(self, status: TriRpcStatus) -> None:
- """
- Error after create server stream, grpc error will be returned.
- :param status: The status
- :type status: TriRpcStatus
- """
- # create trailers
- trailers = Http2Headers()
- trailers.status = HttpStatus.OK.value
- trailers.add(TripleHeaderName.GRPC_STATUS.value, status.code.value)
- trailers.add(TripleHeaderName.GRPC_MESSAGE.value, status.description)
- trailers.add(
- TripleHeaderName.CONTENT_TYPE.value,
- TripleHeaderValue.APPLICATION_GRPC_PROTO.value,
- )
-
- # send trailers
- self._stream.send_headers(trailers, end_stream=True)
-
- class ServerDecoderListener(TriDecoder.Listener):
- """
- ServerDecoderListener is a callback interface that receives events on the decoder.
- """
-
- def __init__(self, listener: ServerStream.Listener):
- self._listener = listener
-
- def on_message(self, message: bytes) -> None:
- self._listener.on_message(message)
-
- def close(self):
- self._listener.on_complete()
diff --git a/src/dubbo/protocol/triple/streams.py b/src/dubbo/protocol/triple/streams.py
deleted file mode 100644
index 5376ec8..0000000
--- a/src/dubbo/protocol/triple/streams.py
+++ /dev/null
@@ -1,284 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import queue
-import threading
-from typing import Any, Optional, Union
-
-from dubbo.classes import EOF, ReadStream, ReadWriteStream, WriteStream
-from dubbo.protocol.triple.call import ClientCall, ServerCall
-from dubbo.protocol.triple.constants import GRpcCode
-from dubbo.protocol.triple.exceptions import RpcError
-from dubbo.protocol.triple.status import TriRpcStatus
-
-
-class TriReadStream(ReadStream):
- """
- Triple read stream. Support reading data from the stream.
- """
-
- __slots__ = ["_storage", "_lock", "_read_done"]
-
- def __init__(self):
- self._read_done = False
- self._storage = queue.Queue(maxsize=1000)
- self._lock = threading.RLock()
-
- def put(self, data: Any) -> None:
- """
- Put data into the stream. It is private and should not be called by the user.
- :param data: The data to put into the stream.
- :type data: Any
- """
- if self._read_done:
- return
- self._storage.put_nowait(data)
-
- def put_eof(self) -> None:
- """
- Put EOF into the stream. It is private and should not be called by the user.
- """
- if self._read_done:
- return
- self._read_done = True
- self._storage.put_nowait(EOF)
-
- def put_exception(self, e: Exception) -> None:
- """
- Set an exception to the stream. It is private and should not be called by the user.
- :param e: The exception to set.
- :type e: Exception
- """
- # Stop the read stream
- self.put_eof()
- # Raise the exception
- raise e
-
- def read(self, timeout: Optional[int] = None) -> Any:
- """
- Read the stream.
- :param timeout:
- The timeout in seconds. If None, it will block until the data is available.
- :type timeout: Optional[int]
- :return:
- The data read from the stream.
- If no more data, return EOF.
- If no data available within the timeout, return None.
- :rtype: Any
- """
- # If you can't read more data, return EOF
- if self._read_done and self._storage.empty():
- return EOF
-
- try:
- data = self._storage.get(timeout=max(0, timeout) if timeout is not None else None)
- return data
- except queue.Empty:
- return None
-
- def __iter__(self):
- return self
-
- def __next__(self):
- data = self.read()
- if data is EOF:
- raise StopIteration
- return data
-
-
-class TriClientWriteStream(WriteStream):
- """
- Triple client write stream. Support writing data to the stream.
- """
-
- __slots__ = ["_call", "_write_done"]
-
- def __init__(self, call: Optional[ClientCall] = None):
- self._call: Optional[ClientCall] = call
- self._write_done = False
-
- def set_call(self, call: ClientCall):
- self._call = call
-
- def can_write_more(self) -> bool:
- """
- Check if the stream can write more data.
- :return: True if the stream can write more data, False otherwise.
- :rtype: bool
- """
- return not self._write_done
-
- def write(self, *args, **kwargs) -> None:
- """
- Write data to the stream.
- :param args: The arguments to pass to the write method.
- :param kwargs: The keyword arguments to pass to the write method.
- :raises RpcError: If write after done writing.
- """
- if self._write_done:
- raise RpcError("Write after done writing")
- self._call.send_message((args, kwargs), False)
-
- def done_writing(self, **kwargs) -> None:
- """
- Done writing to the stream.
- :raises RpcError: If done writing multiple times.
- """
- if self._write_done:
- raise RpcError("Done writing multiple times")
-
- self._call.send_message(None, True)
- self._write_done = True
-
-
-class TriServerWriteStream(WriteStream):
- """
- Triple server write stream. Support writing data to the stream.
- """
-
- __slots__ = ["_call", "_write_done"]
-
- def __init__(self, call: ServerCall):
- self._call = call
- self._write_done = False
-
- def can_write_more(self) -> bool:
- """
- Check if the stream can write more data.
- :return: True if the stream can write more data, False otherwise.
- :rtype: bool
- """
- return not self._write_done
-
- def write(self, *args, **kwargs) -> None:
- """
- Write data to the stream.
- :param args: The arguments to pass to the write method.
- :param kwargs: The keyword arguments to pass to the write method.
- :raises RpcError: If write after done writing.
- """
- if self._write_done:
- raise RpcError("Write after done writing")
- self._call.send_message((args, kwargs))
-
- def done_writing(self, **kwargs) -> None:
- """
- Done writing to the stream.
- :param kwargs: The keyword arguments to pass to the done
- :raises RpcError: If done writing multiple times.
- """
- if self._write_done:
- raise RpcError("Done writing multiple times")
-
- # try to get TriRpcStatus from kwargs
- status = kwargs.get("tri_rpc_status")
- if status is None:
- status = TriRpcStatus(GRpcCode.OK)
- elif not isinstance(status, TriRpcStatus):
- raise RpcError("Invalid status type")
-
- # remove the status from kwargs
- kwargs.pop("tri-rpc-status", None)
-
- self._call.complete(status, kwargs)
- self._write_done = True
-
-
-class TriReadWriteStream(ReadWriteStream):
- """
- Triple client read write stream. Support reading and writing data from the stream.
- """
-
- __slots__ = ["_read_stream", "_write_stream"]
-
- def __init__(
- self,
- write_stream: Union[TriClientWriteStream, TriServerWriteStream],
- read_stream: TriReadStream,
- ):
- """
- Initialize the read write stream.
- :param write_stream: The write stream.
- :type write_stream: TriClientWriteStream
- :param read_stream: The read stream.
- :type read_stream: TriReadStream
- """
- self._read_stream = read_stream
- self._write_stream = write_stream
-
- def can_write_more(self) -> bool:
- """
- Check if the stream can write more data.
- :return: True if the stream can write more data, False otherwise.
- :rtype: bool
- """
- if self._write_stream is None:
- raise RpcError("Write stream is not set")
- return self._write_stream.can_write_more()
-
- def can_read_more(self) -> bool:
- """
- Check if there is more data to read.
- :return: True if there is more data to read, False otherwise.
- :rtype: bool
- """
- if self._read_stream is None:
- raise RpcError("Read stream is not set")
- return self._read_stream.can_read_more()
-
- def read(self, timeout: Optional[int] = None) -> Any:
- """
- Read the stream.
- :param timeout:
- The timeout in seconds. If None, it will block until the data is available.
- :type timeout: Optional[int]
- :return:
- The data read from the stream.
- If no more data, return EOF.
- If no data available within the timeout, return None.
- :rtype: Any
- """
- if self._read_stream is None:
- raise RpcError("Read stream is not set")
- return self._read_stream.read(timeout)
-
- def write(self, *args, **kwargs) -> None:
- """
- Write data to the stream.
- :param args: The arguments to pass to the write method.
- :param kwargs: The keyword arguments to pass to the write method.
- :raises RpcError: If write after done writing.
- """
- if self._write_stream is None:
- raise RpcError("Write stream is not set")
- self._write_stream.write(*args, **kwargs)
-
- def done_writing(self, **kwargs) -> None:
- """
- Done writing to the stream.
- :raises RpcError: If done writing multiple times.
- """
- if self._write_stream is None:
- raise RpcError("Write stream is not set")
- self._write_stream.done_writing(**kwargs)
-
- def __iter__(self):
- return self
-
- def __next__(self):
- data = self.read()
- if data is EOF:
- raise StopIteration
- return data
diff --git a/src/dubbo/proxy/__init__.py b/src/dubbo/proxy/__init__.py
deleted file mode 100644
index 4c4ddd8..0000000
--- a/src/dubbo/proxy/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import RpcCallable, RpcCallableFactory
-
-__all__ = ["RpcCallable", "RpcCallableFactory"]
diff --git a/src/dubbo/proxy/_interfaces.py b/src/dubbo/proxy/_interfaces.py
deleted file mode 100644
index 4a54686..0000000
--- a/src/dubbo/proxy/_interfaces.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-
-from dubbo.protocol import Invoker
-from dubbo.proxy.handlers import RpcServiceHandler
-from dubbo.url import URL
-
-__all__ = ["RpcCallable", "RpcCallableFactory"]
-
-
-class RpcCallable(abc.ABC):
- @abc.abstractmethod
- def __call__(self, *args, **kwargs):
- """
- call the rpc service
- """
- raise NotImplementedError()
-
-
-class RpcCallableFactory(abc.ABC):
- @abc.abstractmethod
- def get_callable(self, invoker: Invoker, url: URL) -> RpcCallable:
- """
- get the rpc proxy
- :param invoker: the invoker.
- :type invoker: Invoker
- :param url: the url.
- :type url: URL
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def get_invoker(self, service_handler: RpcServiceHandler, url: URL) -> Invoker:
- """
- get the rpc invoker
- :param service_handler: the service handler.
- :type service_handler: RpcServiceHandler
- :param url: the url.
- :type url: URL
- """
- raise NotImplementedError()
diff --git a/src/dubbo/proxy/callables.py b/src/dubbo/proxy/callables.py
deleted file mode 100644
index 3aa2b4a..0000000
--- a/src/dubbo/proxy/callables.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any
-
-from dubbo.classes import MethodDescriptor
-from dubbo.constants import common_constants
-from dubbo.protocol import Invoker
-from dubbo.protocol.invocation import RpcInvocation
-from dubbo.proxy import RpcCallable, RpcCallableFactory
-from dubbo.url import URL
-
-__all__ = ["MultipleRpcCallable", "DefaultRpcCallableFactory"]
-
-from dubbo.proxy.handlers import RpcServiceHandler
-
-
-class MultipleRpcCallable(RpcCallable):
- """
- The RpcCallable class.
- """
-
- def __init__(self, invoker: Invoker, url: URL):
- self._invoker = invoker
- self._url = url
-
- self._method_model: MethodDescriptor = url.attributes[common_constants.METHOD_DESCRIPTOR_KEY]
-
- self._service_name = url.path
- self._method_name = self._method_model.get_method_name()
-
- def _create_invocation(self, argument: Any) -> RpcInvocation:
- return RpcInvocation(
- self._service_name,
- self._method_name,
- argument,
- attributes={common_constants.METHOD_DESCRIPTOR_KEY: self._method_model},
- )
-
- def __call__(self, *args, **kwargs) -> Any:
- # Create a new RpcInvocation
- invocation = self._create_invocation((args, kwargs))
- # Do invoke.
- result = self._invoker.invoke(invocation)
- return result.value()
-
-
-class DefaultRpcCallableFactory(RpcCallableFactory):
- """
- The RpcCallableFactory class.
- """
-
- def get_callable(self, invoker: Invoker, url: URL) -> RpcCallable:
- return MultipleRpcCallable(invoker, url)
-
- def get_invoker(self, service_handler: RpcServiceHandler, url: URL) -> Invoker:
- pass
diff --git a/src/dubbo/proxy/handlers.py b/src/dubbo/proxy/handlers.py
deleted file mode 100644
index 8c89663..0000000
--- a/src/dubbo/proxy/handlers.py
+++ /dev/null
@@ -1,216 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Callable, Optional
-
-from dubbo.classes import MethodDescriptor
-from dubbo.types import (
- DeserializingFunction,
- RpcTypes,
- SerializingFunction,
-)
-
-__all__ = ["RpcMethodHandler", "RpcServiceHandler"]
-
-
-class RpcMethodHandler:
- """
- Rpc method handler
- """
-
- __slots__ = ["_method_descriptor"]
-
- def __init__(self, method_descriptor: MethodDescriptor):
- """
- Initialize the RpcMethodHandler
- :param method_descriptor: the method descriptor.
- :type method_descriptor: MethodDescriptor
- """
- self._method_descriptor = method_descriptor
-
- @property
- def method_descriptor(self) -> MethodDescriptor:
- """
- Get the method descriptor
- :return: the method descriptor
- :rtype: MethodDescriptor
- """
- return self._method_descriptor
-
- @classmethod
- def unary(
- cls,
- method: Callable,
- method_name: Optional[str] = None,
- request_deserializer: Optional[DeserializingFunction] = None,
- response_serializer: Optional[SerializingFunction] = None,
- ) -> "RpcMethodHandler":
- """
- Create a unary method handler
- :param method: the method.
- :type method: Callable
- :param method_name: the method name. If not provided, the method name will be used.
- :type method_name: Optional[str]
- :param request_deserializer: the request deserializer.
- :type request_deserializer: Optional[DeserializingFunction]
- :param response_serializer: the response serializer.
- :type response_serializer: Optional[SerializingFunction]
- :return: the unary method handler.
- :rtype: RpcMethodHandler
- """
- return cls(
- MethodDescriptor(
- callable_method=method,
- method_name=method_name or method.__name__,
- arg_serialization=(None, request_deserializer),
- return_serialization=(response_serializer, None),
- rpc_type=RpcTypes.UNARY.value,
- )
- )
-
- @classmethod
- def client_stream(
- cls,
- method: Callable,
- method_name: Optional[str] = None,
- request_deserializer: Optional[DeserializingFunction] = None,
- response_serializer: Optional[SerializingFunction] = None,
- ):
- """
- Create a client stream method handler
- :param method: the method.
- :type method: Callable
- :param method_name: the method name. If not provided, the method name will be used.
- :type method_name: Optional[str]
- :param request_deserializer: the request deserializer.
- :type request_deserializer: Optional[DeserializingFunction]
- :param response_serializer: the response serializer.
- :type response_serializer: Optional[SerializingFunction]
- :return: the client stream method handler.
- :rtype: RpcMethodHandler
- """
- return cls(
- MethodDescriptor(
- callable_method=method,
- method_name=method_name or method.__name__,
- arg_serialization=(None, request_deserializer),
- return_serialization=(response_serializer, None),
- rpc_type=RpcTypes.CLIENT_STREAM.value,
- )
- )
-
- @classmethod
- def server_stream(
- cls,
- method: Callable,
- method_name: Optional[str] = None,
- request_deserializer: Optional[DeserializingFunction] = None,
- response_serializer: Optional[SerializingFunction] = None,
- ):
- """
- Create a server stream method handler
- :param method: the method.
- :type method: Callable
- :param method_name: the method name. If not provided, the method name will be used.
- :type method_name: Optional[str]
- :param request_deserializer: the request deserializer.
- :type request_deserializer: Optional[DeserializingFunction]
- :param response_serializer: the response serializer.
- :type response_serializer: Optional[SerializingFunction]
- :return: the server stream method handler.
- :rtype: RpcMethodHandler
- """
- return cls(
- MethodDescriptor(
- callable_method=method,
- method_name=method_name or method.__name__,
- arg_serialization=(None, request_deserializer),
- return_serialization=(response_serializer, None),
- rpc_type=RpcTypes.SERVER_STREAM.value,
- )
- )
-
- @classmethod
- def bi_stream(
- cls,
- method: Callable,
- method_name: Optional[str] = None,
- request_deserializer: Optional[DeserializingFunction] = None,
- response_serializer: Optional[SerializingFunction] = None,
- ):
- """
- Create a bidi stream method handler
- :param method: the method.
- :type method: Callable
- :param method_name: the method name. If not provided, the method name will be used.
- :type method_name: Optional[str]
- :param request_deserializer: the request deserializer.
- :type request_deserializer: Optional[DeserializingFunction]
- :param response_serializer: the response serializer.
- :type response_serializer: Optional[SerializingFunction]
- :return: the bidi stream method handler.
- :rtype: RpcMethodHandler
- """
- return cls(
- MethodDescriptor(
- callable_method=method,
- method_name=method_name or method.__name__,
- arg_serialization=(None, request_deserializer),
- return_serialization=(response_serializer, None),
- rpc_type=RpcTypes.BI_STREAM.value,
- )
- )
-
-
-class RpcServiceHandler:
- """
- Rpc service handler
- """
-
- __slots__ = ["_service_name", "_method_handlers"]
-
- def __init__(self, service_name: str, method_handlers: list[RpcMethodHandler]):
- """
- Initialize the RpcServiceHandler
- :param service_name: the name of the service.
- :type service_name: str
- :param method_handlers: the method handlers.
- :type method_handlers: List[RpcMethodHandler]
- """
- self._service_name = service_name
- self._method_handlers: dict[str, RpcMethodHandler] = {}
-
- for method_handler in method_handlers:
- method_name = method_handler.method_descriptor.get_method_name()
- self._method_handlers[method_name] = method_handler
-
- @property
- def service_name(self) -> str:
- """
- Get the service name
- :return: the service name
- :rtype: str
- """
- return self._service_name
-
- @property
- def method_handlers(self) -> dict[str, RpcMethodHandler]:
- """
- Get the method handlers
- :return: the method handlers
- :rtype: Dict[str, RpcMethodHandler]
- """
- return self._method_handlers
diff --git a/src/dubbo/registry/__init__.py b/src/dubbo/registry/__init__.py
deleted file mode 100644
index 1af6cc3..0000000
--- a/src/dubbo/registry/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import NotifyListener, Registry, RegistryFactory
-
-__all__ = ["Registry", "RegistryFactory", "NotifyListener"]
diff --git a/src/dubbo/registry/_interfaces.py b/src/dubbo/registry/_interfaces.py
deleted file mode 100644
index d41c496..0000000
--- a/src/dubbo/registry/_interfaces.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-
-from dubbo.node import Node
-from dubbo.url import URL
-
-__all__ = ["Registry", "RegistryFactory", "NotifyListener"]
-
-
-class NotifyListener(abc.ABC):
- """
- The notify listener.
- """
-
- @abc.abstractmethod
- def notify(self, urls: list[URL]) -> None:
- """
- Notify the listener.
-
- :param urls: The list of registered information , is always not empty.
- """
- raise NotImplementedError()
-
-
-class Registry(Node, abc.ABC):
- @abc.abstractmethod
- def register(self, url: URL) -> None:
- """
- Register a service to registry.
-
- :param url: The service URL.
- :type url: URL
- :return: None
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def unregister(self, url: URL) -> None:
- """
- Unregister a service from registry.
-
- :param url: The service URL.
- :type url: URL
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def subscribe(self, url: URL, listener: NotifyListener) -> None:
- """
- Subscribe a service from registry.
- :param url: The service URL.
- :type url: URL
- :param listener: The listener to notify when service changed.
- :type listener: NotifyListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def unsubscribe(self, url: URL, listener: NotifyListener) -> None:
- """
- Unsubscribe a service from registry.
- :param url: The service URL.
- :type url: URL
- :param listener: The listener to notify when service changed.
- :type listener: NotifyListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def lookup(self, url: URL) -> None:
- """
- Lookup a service from registry.
- :param url: The service URL.
- :type url: URL
- """
- raise NotImplementedError()
-
-
-class RegistryFactory(abc.ABC):
- @abc.abstractmethod
- def get_registry(self, url: URL) -> Registry:
- """
- Get a registry instance.
-
- :param url: The registry URL.
- :type url: URL
- :return: The registry instance.
- :rtype: Registry
- """
- raise NotImplementedError()
diff --git a/src/dubbo/registry/protocol.py b/src/dubbo/registry/protocol.py
deleted file mode 100644
index 273f4af..0000000
--- a/src/dubbo/registry/protocol.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.cluster.directories import RegistryDirectory
-from dubbo.cluster.failfast_cluster import FailfastCluster
-from dubbo.cluster.monitor.cpu import CpuInnerRpcHandler, CpuMonitor
-from dubbo.configs import RegistryConfig
-from dubbo.constants import common_constants
-from dubbo.extension import extensionLoader
-from dubbo.protocol import Invoker, Protocol
-from dubbo.registry import RegistryFactory
-from dubbo.url import URL
-
-__all__ = ["RegistryProtocol"]
-
-
-class RegistryProtocol(Protocol):
- """
- Registry protocol.
- """
-
- def __init__(self, config: RegistryConfig, protocol: Protocol):
- self._config = config
- self._protocol = protocol
-
- self._factory: RegistryFactory = extensionLoader.get_extension(RegistryFactory, self._config.protocol)()
-
- def export(self, url: URL):
- # get the server registry
- registry = self._factory.get_registry(url)
-
- ref_url: URL = url.attributes[common_constants.EXPORT_KEY]
- registry.register(ref_url)
-
- # add cpu handler
- ref_url.attributes[common_constants.SERVICE_HANDLER_KEY] = [
- ref_url.attributes[common_constants.SERVICE_HANDLER_KEY],
- CpuInnerRpcHandler.get_service_handler(),
- ]
-
- # continue the export process
- self._protocol.export(ref_url)
-
- def refer(self, url: URL) -> Invoker:
- registry = self._factory.get_registry(url)
-
- # create the directory
- if url.parameters.get(common_constants.LOADBALANCE_KEY) == "cpu":
- directory = CpuMonitor(registry, self._protocol, url)
- else:
- directory = RegistryDirectory(registry, self._protocol, url)
-
- # continue the refer process
- return FailfastCluster().join(directory)
diff --git a/src/dubbo/registry/zookeeper/__init__.py b/src/dubbo/registry/zookeeper/__init__.py
deleted file mode 100644
index 8a9af4c..0000000
--- a/src/dubbo/registry/zookeeper/__init__.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import (
- ChildrenListener,
- DataListener,
- StateListener,
- ZookeeperClient,
- ZookeeperTransport,
-)
-
-__all__ = [
- "ChildrenListener",
- "DataListener",
- "StateListener",
- "ZookeeperClient",
- "ZookeeperTransport",
-]
diff --git a/src/dubbo/registry/zookeeper/_interfaces.py b/src/dubbo/registry/zookeeper/_interfaces.py
deleted file mode 100644
index c104bd2..0000000
--- a/src/dubbo/registry/zookeeper/_interfaces.py
+++ /dev/null
@@ -1,274 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import enum
-
-from dubbo.url import URL
-
-__all__ = [
- "StateListener",
- "DataListener",
- "ChildrenListener",
- "ZookeeperClient",
- "ZookeeperTransport",
-]
-
-
-class StateListener(abc.ABC):
- class State(enum.Enum):
- """
- Zookeeper connection state.
- """
-
- SUSPENDED = "SUSPENDED"
- CONNECTED = "CONNECTED"
- LOST = "LOST"
-
- @abc.abstractmethod
- def state_changed(self, state: "StateListener.State") -> None:
- """
- Notify when connection state changed.
-
- :param state: The new connection state.
- :type state: StateListener.State
- """
- raise NotImplementedError()
-
-
-class DataListener(abc.ABC):
- class EventType(enum.Enum):
- """
- Zookeeper data event type.
- """
-
- CREATED = "CREATED"
- DELETED = "DELETED"
- CHANGED = "CHANGED"
- CHILD = "CHILD"
- NONE = "NONE"
-
- @abc.abstractmethod
- def data_changed(self, path: str, data: bytes, event_type: "DataListener.EventType") -> None:
- """
- Notify when data changed.
-
- :param path: The node path.
- :type path: str
- :param data: The new data.
- :type data: bytes
- :param event_type: The event type.
- :type event_type: DataListener.EventType
- """
- raise NotImplementedError()
-
-
-class ChildrenListener(abc.ABC):
- @abc.abstractmethod
- def children_changed(self, path: str, children: list) -> None:
- """
- Notify when children changed.
-
- :param path: The node path.
- :type path: str
- :param children: The new children.
- :type children: list
- """
- raise NotImplementedError()
-
-
-class ZookeeperClient(abc.ABC):
- """
- Zookeeper Client interface.
- """
-
- __slots__ = ["_url"]
-
- def __init__(self, url: URL):
- """
- Initialize the zookeeper client.
-
- :param url: The zookeeper URL.
- :type url: URL
- """
- self._url = url
-
- @abc.abstractmethod
- def start(self) -> None:
- """
- Start the zookeeper client.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def stop(self) -> None:
- """
- Stop the zookeeper client.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def is_connected(self) -> bool:
- """
- Check if the client is connected to zookeeper.
-
- :return: True if connected, False otherwise.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def create(self, path: str, data: bytes = b"", ephemeral=False) -> None:
- """
- Create a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- :param data: The node data.
- :type data: bytes
- :param ephemeral: Whether the node is ephemeral. False: persistent, True: ephemeral.
- :type ephemeral: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def create_or_update(self, path: str, data: bytes, ephemeral=False) -> None:
- """
- Create or update a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- :param data: The node data.
- :type data: bytes
- :param ephemeral: Whether the node is ephemeral. False: persistent, True: ephemeral.
- :type ephemeral: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def check_exist(self, path: str) -> bool:
- """
- Check if a node exists in zookeeper.
-
- :param path: The node path.
- :type path: str
- :return: True if the node exists, False otherwise.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def get_data(self, path: str) -> bytes:
- """
- Get data of a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- :return: The node data.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def get_children(self, path: str) -> list:
- """
- Get children of a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- :return: The children of the node.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def delete(self, path: str) -> None:
- """
- Delete a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def add_state_listener(self, listener: StateListener) -> None:
- """
- Add a state listener to zookeeper.
-
- :param listener: The listener to notify when connection state changed.
- :type listener: StateListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def remove_state_listener(self, listener: StateListener) -> None:
- """
- Remove a state listener from zookeeper.
-
- :param listener: The listener to remove.
- :type listener: StateListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def add_data_listener(self, path: str, listener: DataListener) -> None:
- """
- Add a data listener to a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- :param listener: The listener to notify when data changed.
- :type listener: DataListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def remove_data_listener(self, listener: DataListener) -> None:
- """
- Remove a data listener from a node in zookeeper.
-
- :param listener: The listener to remove.
- :type listener: DataListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def add_children_listener(self, path: str, listener: ChildrenListener) -> None:
- """
- Add a children listener to a node in zookeeper.
-
- :param path: The node path.
- :type path: str
- :param listener: The listener to notify when children changed.
- :type listener: ChildrenListener
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def remove_children_listener(self, listener: ChildrenListener) -> None:
- """
- Remove a children listener from a node in zookeeper.
-
- :param listener: The listener to remove.
- :type listener: ChildrenListener
- """
- raise NotImplementedError()
-
-
-class ZookeeperTransport(abc.ABC):
- @abc.abstractmethod
- def connect(self, url: URL) -> ZookeeperClient:
- """
- Connect to a zookeeper.
- """
- raise NotImplementedError()
diff --git a/src/dubbo/registry/zookeeper/kazoo_transport.py b/src/dubbo/registry/zookeeper/kazoo_transport.py
deleted file mode 100644
index be34fc3..0000000
--- a/src/dubbo/registry/zookeeper/kazoo_transport.py
+++ /dev/null
@@ -1,413 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import threading
-from typing import Union
-
-from kazoo.client import KazooClient
-from kazoo.protocol.states import EventType, KazooState, WatchedEvent, ZnodeStat
-
-from dubbo.loggers import loggerFactory
-from dubbo.url import URL
-
-from ._interfaces import (
- ChildrenListener,
- DataListener,
- StateListener,
- ZookeeperClient,
- ZookeeperTransport,
-)
-
-__all__ = ["KazooZookeeperClient", "KazooZookeeperTransport"]
-
-_LOGGER = loggerFactory.get_logger("zookeeper")
-
-LISTENER_TYPE = Union[StateListener, DataListener, ChildrenListener]
-
-
-class AbstractListenerAdapter(abc.ABC):
- """
- Abstract listener adapter.
-
- This abstract class defines a template for listener adapters, providing thread-safe methods to
- manage listeners. Concrete implementations should provide specific behavior for these methods.
- """
-
- __slots__ = ["_lock", "_listeners"]
-
- def __init__(self, listener: LISTENER_TYPE):
- """
- Initialize the adapter with a reentrant lock to ensure thread safety and store the initial listener.
-
- :param listener: The listener to manage.
- :type listener: StateListener or DataListener or ChildrenListener
- """
- self._lock = threading.Lock()
- self._listeners = {listener}
-
- def add(self, listener: LISTENER_TYPE) -> None:
- """
- Add a listener to the adapter.
-
- This method adds a listener to the adapter's set of listeners in a thread-safe manner.
-
- :param listener: The listener to add.
- :type listener: StateListener or DataListener or ChildrenListener
- """
- with self._lock:
- self._listeners.add(listener)
-
- def remove(self, listener: LISTENER_TYPE) -> None:
- """
- Remove a listener from the adapter.
-
- This method removes a listener from the adapter's set of listeners in a thread-safe manner.
-
- :param listener: The listener to remove.
- :type listener: StateListener or DataListener or ChildrenListener
- """
- with self._lock:
- self._listeners.remove(listener)
-
-
-class AbstractListenerAdapterFactory(abc.ABC):
- """
- Abstract factory for creating and managing listener adapters.
-
- This abstract factory class provides methods to create and manage listener adapters
- in a thread-safe manner. It maintains a dictionary to track active adapters.
- """
-
- __slots__ = [
- "_client",
- "_lock",
- "_adapters",
- "_listener_to_path",
- ]
-
- def __init__(self, client: KazooClient):
- """
- Initialize the factory with a KazooClient and set up the necessary locks and dictionaries.
-
- :param client: An instance of KazooClient to manage Zookeeper connections.
- :type client: KazooClient
- """
- self._client = client
- self._lock = threading.Lock()
- self._adapters: dict[str, AbstractListenerAdapter] = {}
- self._listener_to_path: dict[LISTENER_TYPE, str] = {}
-
- def create(self, path: str, listener) -> None:
- """
- Create a new adapter or add a listener to an existing adapter.
-
- This method checks if the specified path already has an adapter. If so, it adds the listener
- to the existing adapter. Otherwise, it creates a new adapter using the abstract `do_create` method.
-
- :param path: The Znode path to watch.
- :type path: str
- :param listener: The listener for which to create or add to an adapter.
- :type listener: Any
- """
- with self._lock:
- adapter = self._adapters.get(path)
- if not adapter:
- # Creating a new adapter
- adapter = self.do_create(path, listener)
- self._adapters[path] = adapter
- else:
- # Add the listener to the adapter
- adapter.add(listener)
-
- def remove(self, listener) -> None:
- """
- Remove a listener and its associated adapter if no listeners remain.
-
- This method removes the listener's adapter from the active adapters dictionary and
- removes the listener from the adapter. If no listeners remain, the adapter is discarded.
-
- :param listener: The listener to remove.
- :type listener: Any
- """
- with self._lock:
- path = self._listener_to_path.pop(listener, None)
- if path is None:
- return
- adapter = self._adapters.get(path)
- if adapter is not None:
- adapter.remove(listener)
-
- @abc.abstractmethod
- def do_create(self, path: str, listener) -> AbstractListenerAdapter:
- """
- Define the creation of a new adapter.
-
- This abstract method must be implemented by subclasses to handle the actual creation logic
- for a new adapter.
-
- :param path: The Znode path to watch.
- :type path: str
- :param listener: The listener for which to create a new adapter.
- :type listener: Any
- :return: A new instance of an AbstractListenerAdapter.
- :rtype: AbstractListenerAdapter
- :raises NotImplementedError: If the method is not implemented by a subclass.
- """
- raise NotImplementedError()
-
-
-class StateListenerAdapter(AbstractListenerAdapter):
- """
- State listener adapter.
-
- This adapter inherits from `AbstractListenerAdapter` and is designed to handle state changes
- in a `KazooClient`. It converts Zookeeper states to internal states and notifies listeners.
- """
-
- def __init__(self, listener: StateListener):
- """
- Initialize the StateListenerAdapter with a given listener.
-
- :param listener: The listener to manage.
- :type listener: StateListener
- """
- super().__init__(listener)
-
- def __call__(self, state: KazooState):
- """
- Handle state changes and notify the listener.
-
- This method is called with the current state of the KazooClient, converts it to an internal
- state representation, and notifies all registered listeners.
-
- :param state: The current state of the KazooClient.
- :type state: KazooState
- """
- if state == KazooState.CONNECTED:
- state = StateListener.State.CONNECTED
- elif state == KazooState.LOST:
- state = StateListener.State.LOST
- elif state == KazooState.SUSPENDED:
- state = StateListener.State.SUSPENDED
-
- # Notify all listeners
- for listener in self._listeners:
- listener.state_changed(state)
-
-
-class DataListenerAdapter(AbstractListenerAdapter):
- """
- Data listener adapter.
-
- This adapter handles data change events for a specified Znode path and notifies a `DataListener`.
- """
-
- __slots__ = ["_path"]
-
- def __init__(self, path: str, listener: DataListener):
- """
- Initialize the DataListenerAdapter with a given path and listener.
-
- :param path: The Znode path to watch.
- :type path: str
- :param listener: The data listener to notify on data changes.
- :type listener: DataListener
- """
- super().__init__(listener)
- self._path = path
-
- def __call__(self, data: bytes, stat: ZnodeStat, event: WatchedEvent):
- """
- Handle data changes and notify the listener.
-
- This method is called with the current data, stat, and event of the watched Znode,
- processes the event type, and notifies all registered listeners.
-
- :param data: The current data of the Znode.
- :type data: bytes
- :param stat: The status of the Znode.
- :type stat: ZnodeStat
- :param event: The event that triggered the callback.
- :type event: WatchedEvent
- """
- with self._lock:
- if event is None or len(self._listeners) == 0:
- return
-
- event_type = None
- if event.type == EventType.NONE:
- event_type = DataListener.EventType.NONE
- elif event.type == EventType.CREATED:
- event_type = DataListener.EventType.CREATED
- elif event.type == EventType.DELETED:
- event_type = DataListener.EventType.DELETED
- elif event.type == EventType.CHANGED:
- event_type = DataListener.EventType.CHANGED
- elif event.type == EventType.CHILD:
- event_type = DataListener.EventType.CHILD
-
- # Notify all listeners
- for listener in self._listeners:
- listener.data_changed(self._path, data, event_type)
-
-
-class ChildrenListenerAdapter(AbstractListenerAdapter):
- """
- Children listener adapter.
-
- This adapter handles children change events for a specified Znode path and notifies a `ChildrenListener`.
- """
-
- __slots__ = ["_path"]
-
- def __init__(self, path: str, listener: ChildrenListener):
- """
- Initialize the ChildrenListenerAdapter with a given path and listener.
-
- :param path: The Znode path to watch.
- :type path: str
- :param listener: The children listener to notify on children changes.
- :type listener: ChildrenListener
- """
- super().__init__(listener)
- self._path = path
-
- def __call__(self, children: list[str]):
- """
- Handle children changes and notify the listener.
-
- This method is called with the current list of children of the watched Znode
- and notifies all registered listeners.
-
- :param children: The current list of children of the Znode.
- :type children: List[str]
- """
- with self._lock:
- # Notify all listeners
- for listener in self._listeners:
- listener.children_changed(self._path, children)
-
-
-class DataListenerAdapterFactory(AbstractListenerAdapterFactory):
- def do_create(self, path: str, listener: DataListener) -> AbstractListenerAdapter:
- data_adapter = DataListenerAdapter(path, listener)
- self._client.DataWatch(path, data_adapter)
- return data_adapter
-
-
-class ChildrenListenerAdapterFactory(AbstractListenerAdapterFactory):
- def do_create(self, path: str, listener: ChildrenListener) -> AbstractListenerAdapter:
- children_adapter = ChildrenListenerAdapter(path, listener)
- self._client.ChildrenWatch(path, children_adapter)
- return children_adapter
-
-
-class KazooZookeeperClient(ZookeeperClient):
- """
- Kazoo Zookeeper client.
- """
-
- def __init__(self, url: URL):
- super().__init__(url)
- self._client: KazooClient = KazooClient(hosts=url.location, logger=_LOGGER)
- # TODO: Add more attributes from url
-
- # state listener dict
- self._state_lock = threading.Lock()
- self._state_listeners: dict[StateListener, StateListenerAdapter] = {}
-
- self._data_adapter_factory = DataListenerAdapterFactory(self._client)
-
- self._children_adapter_factory = ChildrenListenerAdapterFactory(self._client)
-
- def start(self) -> None:
- # start the client
- self._client.start()
-
- def stop(self) -> None:
- # stop the client
- self._client.stop()
-
- def is_connected(self) -> bool:
- return self._client.connected
-
- def create(self, path: str, data: bytes = b"", ephemeral=False) -> None:
- self._client.create(path, data, ephemeral=ephemeral, makepath=True)
-
- def create_or_update(self, path: str, data: bytes, ephemeral=False) -> None:
- if self.check_exist(path):
- self._client.set(path, data)
- else:
- self.create(path, data, ephemeral=ephemeral)
-
- def check_exist(self, path: str) -> bool:
- return self._client.exists(path)
-
- def get_data(self, path: str) -> bytes:
- # data: bytes, stat: ZnodeStat
- data, stat = self._client.get(path)
- return data
-
- def get_children(self, path: str) -> list:
- return self._client.get_children(path)
-
- def delete(self, path: str) -> None:
- self._client.delete(path)
-
- def add_state_listener(self, listener: StateListener) -> None:
- with self._state_lock:
- if listener in self._state_listeners:
- return
- state_adapter = StateListenerAdapter(listener)
- self._client.add_listener(state_adapter)
- self._state_listeners[listener] = state_adapter
-
- def remove_state_listener(self, listener: StateListener) -> None:
- with self._state_lock:
- state_adapter = self._state_listeners.pop(listener, None)
- if state_adapter is not None:
- self._client.remove_listener(state_adapter)
-
- def add_data_listener(self, path: str, listener: DataListener) -> None:
- self._data_adapter_factory.create(path, listener)
-
- def remove_data_listener(self, listener: DataListener) -> None:
- self._data_adapter_factory.remove(listener)
-
- def add_children_listener(self, path: str, listener: ChildrenListener) -> None:
- self._children_adapter_factory.create(path, listener)
-
- def remove_children_listener(self, listener: ChildrenListener) -> None:
- self._children_adapter_factory.remove(listener)
-
-
-class KazooZookeeperTransport(ZookeeperTransport):
- def __init__(self):
- self._lock = threading.Lock()
- # key: location, value: KazooZookeeperClient
- self._zk_client_dict: dict[str, KazooZookeeperClient] = {}
-
- def connect(self, url: URL) -> ZookeeperClient:
- with self._lock:
- zk_client = self._zk_client_dict.get(url.location)
- if zk_client is None or zk_client.is_connected():
- # Create new KazooZookeeperClient
- zk_client = KazooZookeeperClient(url)
- zk_client.start()
- self._zk_client_dict[url.location] = zk_client
-
- return zk_client
diff --git a/src/dubbo/registry/zookeeper/zk_registry.py b/src/dubbo/registry/zookeeper/zk_registry.py
deleted file mode 100644
index ba46e31..0000000
--- a/src/dubbo/registry/zookeeper/zk_registry.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.constants import common_constants, registry_constants
-from dubbo.loggers import loggerFactory
-from dubbo.registry import NotifyListener, Registry, RegistryFactory
-from dubbo.registry.zookeeper import ChildrenListener, StateListener, ZookeeperTransport
-from dubbo.registry.zookeeper.kazoo_transport import KazooZookeeperTransport
-from dubbo.url import URL, create_url
-
-__all__ = ["ZookeeperRegistryFactory", "ZookeeperRegistry"]
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class _DefaultStateListener(StateListener):
- def state_changed(self, state: "StateListener.State") -> None:
- if state == StateListener.State.LOST:
- _LOGGER.warning("Connection lost")
- elif state == StateListener.State.CONNECTED:
- _LOGGER.info("Connection established")
- elif state == StateListener.State.SUSPENDED:
- _LOGGER.info("Connection suspended")
-
-
-class _DefaultChildrenListener(ChildrenListener):
- def __init__(self, listener: NotifyListener):
- self._listener = listener
-
- def children_changed(self, path: str, children: list[str]) -> None:
- urls = []
- for child in children:
- url = create_url(child, encoded=True)
- urls.append(url)
- self._listener.notify(urls)
-
-
-class ZookeeperRegistry(Registry):
- """
- Zookeeper registry implementation.
- """
-
- # default root is "dubbo"
- DEFAULT_ROOT = common_constants.DUBBO_VALUE
-
- def __init__(self, url: URL, zk_transport: ZookeeperTransport):
- self._url = url
- self._any_services = set()
-
- # connect to the zookeeper server
- self._zk_client = zk_transport.connect(self._url)
-
- # get the root path
- self._root = common_constants.PATH_SEPARATOR + url.parameters.get(
- common_constants.GROUP_KEY, self.DEFAULT_ROOT
- ).lstrip(common_constants.PATH_SEPARATOR)
-
- # add the state listener
- self._zk_client.add_state_listener(_DefaultStateListener())
-
- @property
- def root_dir(self) -> str:
- """
- Get the root directory.
- :return: the root directory.
- :rtype: str
- """
- if common_constants.PATH_SEPARATOR == self._root:
- return self._root
- return self._root + common_constants.PATH_SEPARATOR
-
- @property
- def root_path(self) -> str:
- """
- Get the root path.
- :return: the root path.
- :rtype: str
- """
- return self.root_dir
-
- def register(self, url: URL) -> None:
- self._zk_client.create_or_update(
- self.to_url_path(url),
- url.location.encode("utf-8"),
- ephemeral=bool(url.parameters.get(registry_constants.DYNAMIC_KEY, True)),
- )
-
- def unregister(self, url: URL) -> None:
- self._zk_client.delete(self.to_url_path(url))
-
- def subscribe(self, url: URL, listener: NotifyListener) -> None:
- for path in self.get_categories_path(url):
- children_listener = _DefaultChildrenListener(listener)
- self._zk_client.add_children_listener(path, children_listener)
-
- def unsubscribe(self, url: URL, listener: NotifyListener) -> None:
- # TODO: implement the unsubscribe
- pass
-
- def lookup(self, url: URL):
- providers = []
- for category_path in self.get_categories_path(url):
- children_list = self._zk_client.get_children(category_path)
- if children_list:
- providers.extend(children_list)
- return providers
-
- def get_service_path(self, url: URL) -> str:
- """
- Get the service path.
- :param url: The URL.
- :type url: URL
- :return: The service path.
- :rtype: str
- """
- service_path = url.parameters.get(common_constants.SERVICE_KEY, url.path)
- if service_path == common_constants.ANY_VALUE:
- return self.root_path
- return self.root_dir + service_path
-
- def get_category_path(self, url: URL) -> str:
- """
- Get the category path.
- :param url: The URL.
- :type url: URL
- :return: The category path.
- :rtype: str
- """
- category = url.parameters.get(registry_constants.CATEGORY_KEY, registry_constants.PROVIDERS_CATEGORY)
- return self.get_service_path(url) + common_constants.PATH_SEPARATOR + category
-
- def get_categories_path(self, url: URL) -> list[str]:
- """
- Get the categories' path.
- :param url: The URL.
- :type url: URL
- :return: The categories' paths.
- :rtype: List[str]
- """
- # get the categories
- if common_constants.ANY_VALUE == url.parameters.get(registry_constants.CATEGORY_KEY):
- categories = [
- registry_constants.PROVIDERS_CATEGORY,
- registry_constants.CONSUMERS_CATEGORY,
- ]
- else:
- parameter = url.parameters.get(registry_constants.CATEGORY_KEY, registry_constants.PROVIDERS_CATEGORY)
- categories = [s.strip() for s in parameter.split(common_constants.COMMA_SEPARATOR)]
-
- # get paths
- return [self.get_service_path(url) + common_constants.PATH_SEPARATOR + category for category in categories]
-
- def to_url_path(self, url: URL) -> str:
- """
- Convert the URL to the path.
- :param url: The URL.
- :type url: URL
- :return: The path.
- :rtype: str
- """
- # return the path
- return self.get_category_path(url) + common_constants.PATH_SEPARATOR + url.to_str(encode=True)
-
- def get_url(self) -> URL:
- return self._url
-
- def is_available(self) -> bool:
- return self._zk_client and self._zk_client.is_connected()
-
- def destroy(self) -> None:
- if self._zk_client:
- self._zk_client.stop()
-
- def check_destroy(self) -> None:
- if not self._zk_client:
- raise RuntimeError("registry is destroyed")
-
-
-class ZookeeperRegistryFactory(RegistryFactory):
- def __init__(self):
- self._transport: ZookeeperTransport = KazooZookeeperTransport()
-
- def get_registry(self, url: URL) -> Registry:
- return ZookeeperRegistry(url, self._transport)
diff --git a/src/dubbo/remoting/__init__.py b/src/dubbo/remoting/__init__.py
deleted file mode 100644
index a93961f..0000000
--- a/src/dubbo/remoting/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import Client, Server, Transporter
-
-__all__ = ["Client", "Server", "Transporter"]
diff --git a/src/dubbo/remoting/_interfaces.py b/src/dubbo/remoting/_interfaces.py
deleted file mode 100644
index 70a95d9..0000000
--- a/src/dubbo/remoting/_interfaces.py
+++ /dev/null
@@ -1,108 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-
-from dubbo.url import URL
-
-__all__ = ["Client", "Server", "Transporter"]
-
-
-class Client(abc.ABC):
- def __init__(self, url: URL):
- self._url = url
-
- @abc.abstractmethod
- def is_connected(self) -> bool:
- """
- Check if the client is connected.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def is_closed(self) -> bool:
- """
- Check if the client is closed.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def connect(self):
- """
- Connect to the server.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def close(self):
- """
- Close the client.
- """
- raise NotImplementedError()
-
-
-class Server:
- """
- Server
- """
-
- @abc.abstractmethod
- def is_exported(self) -> bool:
- """
- Check if the server is exported.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def is_closed(self) -> bool:
- """
- Check if the server is closed.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def export(self):
- """
- Export the server.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def close(self):
- """
- Close the server.
- """
- raise NotImplementedError()
-
-
-class Transporter(abc.ABC):
- """
- Transporter interface
- """
-
- @abc.abstractmethod
- def connect(self, url: URL) -> Client:
- """
- Connect to a server.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def bind(self, url: URL) -> Server:
- """
- Bind a server.
- """
- raise NotImplementedError()
diff --git a/src/dubbo/remoting/aio/__init__.py b/src/dubbo/remoting/aio/__init__.py
deleted file mode 100644
index e07e3a5..0000000
--- a/src/dubbo/remoting/aio/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import ConnectionStateListener, EmptyConnectionStateListener
-
-__all__ = ["ConnectionStateListener", "EmptyConnectionStateListener"]
diff --git a/src/dubbo/remoting/aio/_interfaces.py b/src/dubbo/remoting/aio/_interfaces.py
deleted file mode 100644
index d871b78..0000000
--- a/src/dubbo/remoting/aio/_interfaces.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import abc
-
-__all__ = ["ConnectionStateListener", "EmptyConnectionStateListener"]
-
-
-class ConnectionStateListener(abc.ABC):
- """
- Connection state listener. It is used to listen to the connection state.
- """
-
- @abc.abstractmethod
- async def connection_made(self):
- """
- Called when the connection is first established.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- async def connection_lost(self, exc):
- """
- Called when the connection is lost.
- """
- raise NotImplementedError()
-
-
-class EmptyConnectionStateListener(ConnectionStateListener):
- """
- An empty connection state listener. It does nothing.
- """
-
- async def connection_made(self):
- pass
-
- async def connection_lost(self, exc):
- pass
diff --git a/src/dubbo/remoting/aio/aio_transporter.py b/src/dubbo/remoting/aio/aio_transporter.py
deleted file mode 100644
index a47087b..0000000
--- a/src/dubbo/remoting/aio/aio_transporter.py
+++ /dev/null
@@ -1,282 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import concurrent
-import threading
-from typing import Union
-
-from dubbo.constants import common_constants
-from dubbo.loggers import loggerFactory
-from dubbo.remoting._interfaces import Client, Server, Transporter
-from dubbo.remoting.aio import ConnectionStateListener, constants as aio_constants
-from dubbo.remoting.aio.event_loop import EventLoop
-from dubbo.remoting.aio.exceptions import RemotingError
-from dubbo.url import URL
-from dubbo.utils import FutureHelper
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class AioClient(Client, ConnectionStateListener):
- """
- Asyncio client.
- """
-
- __slots__ = [
- "_global_lock",
- "_protocol",
- "_connected",
- "_closed",
- "_active_close",
- "_event_loop",
- ]
-
- def __init__(self, url: URL):
- """
- Initialize the client.
- :param url: The URL.
- :type url: URL
- """
- super().__init__(url)
-
- self._global_lock = threading.Lock()
-
- # Set the side of the transporter to client.
- self._protocol = None
-
- # the status of the client
- self._connected = False
- self._closed = False
- self._active_close = False
-
- # event loop
- self._event_loop: EventLoop = EventLoop()
-
- # connect to the server
- self.connect()
-
- def is_connected(self) -> bool:
- """
- Check if the client is connected.
- """
- return self._connected
-
- def is_closed(self) -> bool:
- """
- Check if the client is closed.
- """
- return self._closed
-
- def connect(self) -> None:
- """
- Connect to the server.
- """
- with self._global_lock:
- if self.is_connected():
- return
- elif self.is_closed():
- raise RemotingError("The client is closed.")
-
- # Run the connection logic in the event loop.
- if self._event_loop.stopped:
- raise RemotingError("The event loop is stopped.")
- elif not self._event_loop.started:
- self._event_loop.start()
-
- future = concurrent.futures.Future()
- asyncio.run_coroutine_threadsafe(self._do_connect(future), self._event_loop.loop)
-
- try:
- self._protocol = future.result(timeout=3)
- _LOGGER.info(
- "Connected to the server. host: %s, port: %s",
- self._url.host,
- self._url.port,
- )
- except Exception:
- raise RemotingError(f"Failed to connect to the server. host: {self._url.host}, port: {self._url.port}")
-
- async def _do_connect(self, future: Union[concurrent.futures.Future, asyncio.Future]):
- """
- Connect to the server.
- """
- running_loop = asyncio.get_running_loop()
- # Create the connection.
- _, protocol = await running_loop.create_connection(
- lambda: self._url.attributes[common_constants.PROTOCOL_KEY](self._url, self),
- self._url.host,
- self._url.port,
- )
- # Set the protocol.
- FutureHelper.set_result(future, protocol)
-
- def close(self) -> None:
- """
- Close the client.
- """
- with self._global_lock:
- if self.is_closed():
- return
-
- self._active_close = True
- self._protocol.close()
-
- async def connection_made(self):
- # Update the connection status.
- self._connected = True
-
- async def connection_lost(self, exc):
- self._connected = False
- self._closed = True
- # Check if it is an active shutdown
- if self._active_close:
- self._event_loop.stop()
- else:
- # try reconnect
- for _ in range(aio_constants.RECONNECT_TIMES):
- try:
- future = asyncio.Future()
- await self._do_connect(future)
-
- # Update the protocol.
- self._protocol = future.result()
-
- # Update the connection status.
- self._connected = True
- self._closed = False
- self._active_close = False
- _LOGGER.info(
- "Reconnected to the server. host: %s, port: %s",
- self._url.host,
- self._url.port,
- )
- return
- except Exception as e:
- exc = e
- _LOGGER.error("Failed to reconnect to the server. %s", exc)
- # wait for a while
- await asyncio.sleep(1)
-
- # cannot reconnect
- raise RemotingError(
- f"Failed to reconnect to the server.{exc}",
- )
-
-
-class AioServer(Server):
- """
- Asyncio server.
- """
-
- def __init__(self, url: URL):
- self._url = url
- # Set the side of the transporter to server.
- self._url.parameters[common_constants.SIDE_KEY] = common_constants.SERVER_VALUE
-
- # the event to indicate the close status of the server
- self._event_loop = EventLoop()
- self._event_loop.start()
-
- # Whether the server is exporting
- self._exporting = False
- # Whether the server is exported
- self._exported = False
-
- # Whether the server is closing
- self._closing = False
- # Whether the server is closed
- self._closed = False
-
- # start the server
- self.export()
-
- def is_exported(self) -> bool:
- return self._exported or self._exporting
-
- def is_closed(self) -> bool:
- return self._closed or self._closing
-
- def export(self):
- """
- Export the server.
- """
- if self.is_exported():
- return
- elif self.is_closed():
- raise RemotingError("The server is closed.")
-
- async def _inner_operation(_future: concurrent.futures.Future):
- try:
- running_loop = asyncio.get_running_loop()
- server = await running_loop.create_server(
- lambda: self._url.attributes[common_constants.PROTOCOL_KEY](self._url),
- self._url.host,
- self._url.port,
- )
-
- # Serve the server forever
- async with server:
- FutureHelper.set_result(_future, None)
- await server.serve_forever()
- except Exception as e:
- FutureHelper.set_exception(_future, e)
-
- # Run the server logic in the event loop.
- future = concurrent.futures.Future()
- asyncio.run_coroutine_threadsafe(_inner_operation(future), self._event_loop.loop)
-
- try:
- exc = future.exception()
- if exc:
- raise RemotingError("Failed to export the server") from exc
- else:
- self._exported = True
- _LOGGER.info(
- "Exported the server. host: %s, port: %s",
- self._url.host,
- self._url.port,
- )
- finally:
- self._exporting = False
-
- def close(self):
- """
- Close the server.
- """
- if self.is_closed():
- return
- self._closing = True
-
- try:
- self._event_loop.stop()
- self._closed = True
- except Exception as e:
- raise RemotingError("Failed to close the server") from e
- finally:
- self._closing = False
-
-
-class AioTransporter(Transporter):
- """
- Asyncio transporter.
- """
-
- def connect(self, url: URL) -> Client:
- return AioClient(url)
-
- def bind(self, url: URL) -> Server:
- return AioServer(url)
diff --git a/src/dubbo/remoting/aio/constants.py b/src/dubbo/remoting/aio/constants.py
deleted file mode 100644
index 103c184..0000000
--- a/src/dubbo/remoting/aio/constants.py
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-__all__ = ["STREAM_HANDLER_KEY"]
-
-STREAM_HANDLER_KEY = "stream-handler"
-
-LISTENER_FACTORY_KEY = "listener-factory"
-
-CLOSE_FUTURE_KEY = "close-future"
-
-HEARTBEAT_KEY = "heartbeat"
-DEFAULT_HEARTBEAT = 6
-
-RECONNECT_TIMES = 3
diff --git a/src/dubbo/remoting/aio/event_loop.py b/src/dubbo/remoting/aio/event_loop.py
deleted file mode 100644
index f0140a9..0000000
--- a/src/dubbo/remoting/aio/event_loop.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import threading
-import uuid
-from typing import Optional
-
-from dubbo.loggers import loggerFactory
-
-_LOGGER = loggerFactory.get_logger()
-
-
-def _try_use_uvloop() -> None:
- """
- Use uvloop instead of the default asyncio running_loop.
- """
- import asyncio
- import os
-
- # Check if the operating system.
- if os.name == "nt":
- # Windows is not supported.
- _LOGGER.warning("Unable to use uvloop, because it is not supported on your operating system.")
- return
-
- # Try import uvloop.
- try:
- import uvloop
- except ImportError:
- # uvloop is not available.
- _LOGGER.warning(
- "Unable to use uvloop, because it is not installed. You can install it by running `pip install uvloop`."
- )
- return
-
- # Use uvloop instead of the default asyncio running_loop.
- if not isinstance(asyncio.get_event_loop_policy(), uvloop.EventLoopPolicy):
- asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
-
-
-# Call the function to try to use uvloop.
-_try_use_uvloop()
-
-
-class EventLoop:
- def __init__(self, in_other_tread: bool = True):
- self._in_other_tread = in_other_tread
- # The event loop to run the asynchronous function.
- self._loop = asyncio.new_event_loop()
- # The thread to run the event loop.
- self._thread: Optional[threading.Thread] = None if in_other_tread else threading.current_thread()
-
- self._started = False
- self._stopped = False
-
- # The lock to protect the event loop.
- self._lock = threading.Lock()
-
- @property
- def loop(self):
- """
- Get the event loop.
- :return: The event loop.
- :rtype: asyncio.AbstractEventLoop
- """
- return self._loop
-
- @property
- def thread(self) -> Optional[threading.Thread]:
- """
- Get the thread of the event loop.
- :return: The thread of the event loop. If not yet started, this is None.
- :rtype: Optional[threading.Thread]
- """
- return self._thread
-
- def check_thread(self) -> bool:
- """
- Check if the current thread is the event loop thread.
- :return: True if the current thread is the event loop thread, otherwise False.
- :rtype: bool
- """
- return threading.current_thread().ident == self._thread.ident
-
- @property
- def started(self) -> bool:
- """
- Check if the event loop is started.
- :return: True if the event loop is started, otherwise False.
- :rtype: bool
- """
- return self._started
-
- @property
- def stopped(self) -> bool:
- """
- Check if the event loop is stopped.
- :return: True if the event loop is stopped, otherwise False.
- :rtype: bool
- """
- return self._stopped
-
- def start(self) -> None:
- """
- Start the asyncio event loop.
- """
- if self._started:
- return
- with self._lock:
- self._started = True
- self._stopped = False
- if self._in_other_tread:
- self._start_in_thread()
- else:
- self._start()
-
- def _start(self) -> None:
- """
- Real start the asyncio event loop in current thread.
- """
- asyncio.set_event_loop(self._loop)
- self._loop.run_forever()
-
- def _start_in_thread(self) -> None:
- """
- Real Start the asyncio event loop in a separate thread.
- """
- thread_name = f"dubbo-asyncio-loop-{str(uuid.uuid4())}"
- thread = threading.Thread(target=self._start, name=thread_name, daemon=True)
- thread.start()
- self._thread = thread
-
- def stop(self, wait: bool = False) -> None:
- """
- Stop the asyncio event loop.
- """
- if self._stopped:
- return
- with self._lock:
- signal = threading.Event()
- asyncio.run_coroutine_threadsafe(self._stop(signal=signal), self._loop)
- # Wait for the running_loop to stop
- if wait:
- signal.wait()
- if self._in_other_tread:
- self._thread.join()
- self._stopped = True
- self._started = False
-
- async def _stop(self, signal: threading.Event) -> None:
- """
- Real stop the asyncio event loop.
- """
- # Cancel all tasks
- tasks = [task for task in asyncio.all_tasks(self._loop) if task is not asyncio.current_task()]
- for task in tasks:
- task.cancel()
- await asyncio.gather(*tasks, return_exceptions=True)
- # Stop the event running_loop
- self._loop.stop()
- # Set the signal
- signal.set()
diff --git a/src/dubbo/remoting/aio/exceptions.py b/src/dubbo/remoting/aio/exceptions.py
deleted file mode 100644
index f941615..0000000
--- a/src/dubbo/remoting/aio/exceptions.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-class RemotingError(Exception):
- """
- The base exception class for remoting.
- """
-
- def __init__(self, message: str):
- super().__init__(message)
- self.message = message
-
- def __str__(self):
- return self.message
-
-
-class ProtocolError(RemotingError):
- """
- The exception class for protocol errors.
- """
-
- def __init__(self, message: str):
- super().__init__(message)
-
-
-class StreamError(RemotingError):
- """
- The exception class for stream errors.
- """
-
- def __init__(self, message: str):
- super().__init__(message)
diff --git a/src/dubbo/remoting/aio/http2/__init__.py b/src/dubbo/remoting/aio/http2/__init__.py
deleted file mode 100644
index bcba37a..0000000
--- a/src/dubbo/remoting/aio/http2/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
diff --git a/src/dubbo/remoting/aio/http2/controllers.py b/src/dubbo/remoting/aio/http2/controllers.py
deleted file mode 100644
index 04ab366..0000000
--- a/src/dubbo/remoting/aio/http2/controllers.py
+++ /dev/null
@@ -1,389 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import asyncio
-import threading
-from concurrent.futures import ThreadPoolExecutor
-from dataclasses import dataclass
-from typing import Optional
-
-from h2.connection import H2Connection
-
-from dubbo.loggers import loggerFactory
-from dubbo.remoting.aio.http2.frames import (
- DataFrame,
- HeadersFrame,
- UserActionFrames,
- WindowUpdateFrame,
-)
-from dubbo.remoting.aio.http2.registries import Http2FrameType
-from dubbo.remoting.aio.http2.stream import DefaultHttp2Stream, Http2Stream
-from dubbo.utils import EventHelper
-
-__all__ = ["RemoteFlowController", "FrameInboundController", "FrameOutboundController"]
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class Controller(abc.ABC):
- def __init__(self, loop: asyncio.AbstractEventLoop):
- self._loop = loop
- self._lock = threading.Lock()
- self._task: Optional[asyncio.Task] = None
- self._started = False
- self._closed = False
-
- def start(self) -> None:
- with self._lock:
- if self._started:
- return
- self._task = self._loop.create_task(self._run())
- self._started = True
-
- @abc.abstractmethod
- async def _run(self) -> None:
- raise NotImplementedError()
-
- def close(self) -> None:
- with self._lock:
- if self._closed or not self._task:
- return
- self._task.cancel()
- self._task = None
-
-
-class RemoteFlowController(Controller):
- @dataclass
- class Item:
- stream: Http2Stream
- data: bytearray
- end_stream: bool
- event: Optional[asyncio.Event]
-
- def __init__(
- self,
- h2_connection: H2Connection,
- transport: asyncio.Transport,
- loop: asyncio.AbstractEventLoop,
- ):
- super().__init__(loop)
- self._h2_connection = h2_connection
- self._transport = transport
-
- self._stream_dict: dict[int, RemoteFlowController.Item] = {}
-
- self._outbound_queue: asyncio.Queue[int] = asyncio.Queue()
- self._flow_controls: set[int] = set()
-
- # Start the controller
- self.start()
-
- def write_data(self, stream: Http2Stream, frame: DataFrame, event: Optional[asyncio.Event]) -> None:
- if stream.local_closed:
- EventHelper.set(event)
- _LOGGER.warning("Stream %s is closed locally, ignoring the data frame.", stream.id)
- return
-
- item = self._stream_dict.get(stream.id)
- if item:
- # Extend the data if the stream item exists
- item.data.extend(frame.data)
- item.end_stream = frame.end_stream
- # update the event
- EventHelper.set(item.event)
- item.event = event
- else:
- # Create a new stream item
- item = RemoteFlowController.Item(stream, bytearray(frame.data), frame.end_stream, event)
- self._stream_dict[stream.id] = item
- self._outbound_queue.put_nowait(stream.id)
-
- def release_flow_control(self, frame: WindowUpdateFrame) -> None:
- stream_id = frame.stream_id
- if stream_id is None or stream_id == 0:
- # This is for the entire connection.
- for i in self._flow_controls:
- self._outbound_queue.put_nowait(i)
- self._flow_controls.clear()
- elif stream_id in self._flow_controls:
- # This is specific to a single stream.
- self._flow_controls.remove(stream_id)
- self._outbound_queue.put_nowait(stream_id)
-
- async def _run(self) -> None:
- while True:
- # get the data to send.(async blocking)
- stream_id = await self._outbound_queue.get()
-
- # check if the stream is closed
- item = self._stream_dict[stream_id]
- stream = item.stream
- if stream.local_closed:
- # The local side of the stream is closed, so we don't need to send any data.
- EventHelper.set(item.event)
- continue
-
- # get the flow control window size
- data = item.data
- window_size = self._h2_connection.local_flow_control_window(stream.id)
- chunk_size = min(window_size, len(data))
- data_to_send = data[:chunk_size]
- data_to_buffer = data[chunk_size:]
-
- # send the data
- if data_to_send or item.end_stream:
- max_size = self._h2_connection.max_outbound_frame_size
- # Split the data into chunks and send them out
- for x in range(0, len(data_to_send), max_size):
- chunk = data_to_send[x : x + max_size]
- end_stream_flag = item.end_stream and not data_to_buffer and (x + max_size >= len(data_to_send))
- self._h2_connection.send_data(stream.id, chunk, end_stream=end_stream_flag)
-
- outbound_data = self._h2_connection.data_to_send()
- if not outbound_data:
- # If there is no outbound data to send but the stream needs to be closed,
- # send an empty headers frame with the end_stream flag set to True.
- self._h2_connection.send_data(stream.id, b"", end_stream=True)
- outbound_data = self._h2_connection.data_to_send()
- self._transport.write(outbound_data)
-
- if data_to_buffer:
- # Save the data that could not be sent due to flow control limits
- item.data = data_to_buffer
- self._flow_controls.add(stream.id)
- else:
- # If all data has been sent, trigger the event.
- self._stream_dict.pop(stream.id)
- EventHelper.set(item.event)
- if item.end_stream:
- stream.close_local()
-
-
-class FrameInboundController(Controller):
- """
- HTTP/2 frame inbound controller.
- This class is responsible for reading frames in the correct order.
- """
-
- def __init__(
- self,
- stream: Http2Stream,
- loop: asyncio.AbstractEventLoop,
- protocol,
- executor: Optional[ThreadPoolExecutor] = None,
- ):
- """
- Initialize the FrameInboundController.
- :param stream: The stream.
- :type stream: Http2Stream
- :param loop: The asyncio event loop.
- :type loop: asyncio.AbstractEventLoop
- :param protocol: The HTTP/2 protocol.
- :param executor: The thread pool executor for handling frames.
- :type executor: Optional[ThreadPoolExecutor]
- """
- from dubbo.remoting.aio.http2.protocol import AbstractHttp2Protocol
-
- super().__init__(loop)
-
- self._stream = stream
- self._protocol: AbstractHttp2Protocol = protocol
- self._executor = executor
-
- # The queue for receiving frames.
- self._inbound_queue: asyncio.Queue[UserActionFrames] = asyncio.Queue()
-
- self._condition: asyncio.Condition = asyncio.Condition()
-
- # Start the controller
- self.start()
-
- def write_frame(self, frame: UserActionFrames) -> None:
- """
- Put the frame into the frame queue (thread-unsafe).
- :param frame: The HTTP/2 frame to put into the queue.
- """
- self._inbound_queue.put_nowait(frame)
-
- def ack_frame(self, frame: UserActionFrames) -> None:
- """
- Acknowledge the frame by setting the frame event.(thread-safe)
- """
-
- async def _inner_operation(_frame: UserActionFrames):
- async with self._condition:
- if _frame.frame_type == Http2FrameType.DATA:
- self._protocol.ack_received_data(_frame.stream_id, _frame.padding)
- self._condition.notify_all()
-
- asyncio.run_coroutine_threadsafe(_inner_operation(frame), self._loop)
-
- async def _run(self) -> None:
- """
- Coroutine that continuously reads frames from the frame queue.
- """
- while True:
- async with self._condition:
- # get the frame from the queue
- frame = await self._inbound_queue.get()
-
- if self._stream.remote_closed:
- # The remote side of the stream is closed, so we don't need to process any more frames.
- break
-
- # handle frame in the thread pool
- self._loop.run_in_executor(self._executor, self._handle_frame, frame)
-
- if not frame.end_stream:
- # Waiting for the previous frame to be processed
- await self._condition.wait()
- else:
- # close the stream remotely
- self._stream.close_remote()
- break
-
- def _handle_frame(self, frame: UserActionFrames):
- listener = self._stream.listener
- # match the frame type
- frame_type = frame.frame_type
- if frame_type == Http2FrameType.HEADERS:
- listener.on_headers(frame.headers, frame.end_stream)
- elif frame_type == Http2FrameType.DATA:
- listener.on_data(frame.data, frame.end_stream)
- elif frame_type == Http2FrameType.RST_STREAM:
- listener.cancel_by_remote(frame.error_code)
- else:
- _LOGGER.warning("unprocessed frame type: %s", frame_type)
-
- # acknowledge the frame
- self.ack_frame(frame)
-
-
-class FrameOutboundController(Controller):
- """
- HTTP/2 frame outbound controller.
- This class is responsible for writing frames in the correct order.
- """
-
- LAST_DATA_FRAME = DataFrame(-1, b"", 0)
-
- def __init__(self, stream: DefaultHttp2Stream, loop: asyncio.AbstractEventLoop, protocol):
- from dubbo.remoting.aio.http2.protocol import AbstractHttp2Protocol
-
- super().__init__(loop)
-
- self._stream = stream
- self._protocol: AbstractHttp2Protocol = protocol
-
- self._headers_put_event: asyncio.Event = asyncio.Event()
- self._headers_sent_event: asyncio.Event = asyncio.Event()
- self._headers: Optional[HeadersFrame] = None
-
- self._data_queue: asyncio.Queue[DataFrame] = asyncio.Queue()
- self._data_sent_event: asyncio.Event = asyncio.Event()
-
- self._trailers: Optional[HeadersFrame] = None
-
- # Start the controller
- self.start()
-
- def write_headers(self, frame: HeadersFrame) -> None:
- """
- Write the headers frame by order.(thread-safe)
- :param frame: The headers frame.
- :type frame: HeadersFrame
- """
-
- def _inner_operation(_frame: HeadersFrame):
- if not self._headers:
- # send the frame directly -> the headers frame is the first frame
- self._headers = _frame
- EventHelper.set(self._headers_put_event)
- else:
- # put the frame into the queue -> the headers frame is not the first frame(trailers)
- self._trailers = _frame
- # Notify the data queue that the last data frame has reached.
- self._data_queue.put_nowait(FrameOutboundController.LAST_DATA_FRAME)
-
- self._loop.call_soon_threadsafe(_inner_operation, frame)
-
- def write_data(self, frame: DataFrame) -> None:
- """
- Write the data frame by order.(thread-safe)
- :param frame: The data frame.
- :type frame: DataFrame
- """
- self._loop.call_soon_threadsafe(self._data_queue.put_nowait, frame)
-
- def write_rst(self, frame: UserActionFrames) -> None:
- """
- Write the reset frame directly.(thread-safe)
- :param frame: The reset frame.
- :type frame: UserActionFrames
- """
-
- def _inner_operation(_frame: UserActionFrames):
- # -1 means the stream is not created, so we don't need to send the reset frame
- if self._stream.id == -1:
- return
-
- _frame.stream_id = self._stream.id
- self._protocol.send_frame(_frame, self._stream)
-
- self._stream.close_local()
- self._stream.close_remote()
-
- self._loop.call_soon_threadsafe(_inner_operation, frame)
-
- async def _run(self) -> None:
- """
- Coroutine that continuously writes frames from the frame queue.
- """
-
- # wait and send the headers frame
- await self._headers_put_event.wait()
- self._protocol.send_frame(self._headers, self._stream, self._headers_sent_event)
-
- # check if the headers frame is the last frame
- if self._headers.end_stream:
- self._stream.close_local()
- return
-
- # wait for the headers sent event
- await self._headers_sent_event.wait()
-
- # wait and send the data frames
- while True:
- frame = await self._data_queue.get()
- frame.stream_id = self._stream.id
- if frame is not FrameOutboundController.LAST_DATA_FRAME:
- self._data_sent_event = asyncio.Event()
- self._protocol.send_frame(frame, self._stream, self._data_sent_event)
- if frame.end_stream:
- # The last frame has been sent, so the stream is closed.
- return
- else:
- # The last frame has been reached.
- break
-
- # wait for the last data frame and send the trailers frame
- await self._data_sent_event.wait()
- self._trailers.stream_id = self._stream.id
- self._protocol.send_frame(self._trailers, self._stream)
-
- # close the stream
- self._stream.close_local()
diff --git a/src/dubbo/remoting/aio/http2/frames.py b/src/dubbo/remoting/aio/http2/frames.py
deleted file mode 100644
index 7576472..0000000
--- a/src/dubbo/remoting/aio/http2/frames.py
+++ /dev/null
@@ -1,200 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Union
-
-from dubbo.remoting.aio.http2.headers import Http2Headers
-from dubbo.remoting.aio.http2.registries import Http2ErrorCode, Http2FrameType
-
-__all__ = [
- "Http2Frame",
- "HeadersFrame",
- "DataFrame",
- "WindowUpdateFrame",
- "RstStreamFrame",
- "PingFrame",
- "UserActionFrames",
-]
-
-
-class Http2Frame:
- """
- HTTP/2 frame class. It is used to represent an HTTP/2 frame.
- """
-
- __slots__ = ["stream_id", "frame_type", "end_stream", "timestamp"]
-
- def __init__(
- self,
- stream_id: int,
- frame_type: Http2FrameType,
- end_stream: bool = False,
- ):
- """
- Initialize the HTTP/2 frame.
- :param stream_id: The stream identifier. 0 for connection-level frames.
- :type stream_id: int
- :param frame_type: The frame type.
- :type frame_type: Http2FrameType
- :param end_stream: Whether the stream is ended.
- :type end_stream: bool
- """
- self.stream_id = stream_id
- self.frame_type = frame_type
- self.end_stream = end_stream
-
- def __repr__(self) -> str:
- return f""
-
-
-class HeadersFrame(Http2Frame):
- """
- HTTP/2 headers frame.
- """
-
- __slots__ = ["headers"]
-
- def __init__(
- self,
- stream_id: int,
- headers: Http2Headers,
- end_stream: bool = False,
- ):
- """
- Initialize the HTTP/2 headers frame.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param headers: The headers to send.
- :type headers: Http2Headers
- :param end_stream: Whether the stream is ended.
- :type end_stream: bool
- """
- super().__init__(stream_id, Http2FrameType.HEADERS, end_stream)
- self.headers = headers
-
- def __repr__(self) -> str:
- return (
- f""
- )
-
-
-class DataFrame(Http2Frame):
- """
- HTTP/2 data frame.
- """
-
- __slots__ = ["data", "padding"]
-
- def __init__(
- self,
- stream_id: int,
- data: bytes,
- length: int,
- end_stream: bool = False,
- ):
- """
- Initialize the HTTP/2 data frame.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param data: The data to send.
- :type data: bytes
- :param length: The length of the data.
- :type length: int
- :param end_stream: Whether the stream is ended.
- """
- super().__init__(stream_id, Http2FrameType.DATA, end_stream)
- self.data = data
- self.padding = length
-
- def __repr__(self) -> str:
- return f""
-
-
-class WindowUpdateFrame(Http2Frame):
- """
- HTTP/2 window update frame.
- """
-
- __slots__ = ["delta"]
-
- def __init__(
- self,
- stream_id: int,
- delta: int,
- ):
- """
- Initialize the HTTP/2 window update frame.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param delta: The delta value.
- :type delta: int
- """
- super().__init__(stream_id, Http2FrameType.WINDOW_UPDATE, False)
- self.delta = delta
-
- def __repr__(self) -> str:
- return f""
-
-
-class RstStreamFrame(Http2Frame):
- """
- HTTP/2 reset stream frame.
- """
-
- __slots__ = ["error_code"]
-
- def __init__(
- self,
- stream_id: int,
- error_code: Http2ErrorCode,
- ):
- """
- Initialize the HTTP/2 reset stream frame.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param error_code: The error code.
- :type error_code: Http2ErrorCode
- """
- super().__init__(stream_id, Http2FrameType.RST_STREAM, True)
- self.error_code = error_code
-
- def __repr__(self) -> str:
- return f""
-
-
-class PingFrame(Http2Frame):
- """
- HTTP/2 ping frame.
- """
-
- __slots__ = ["data", "ack"]
-
- def __init__(self, data: bytes, ack: bool = False):
- """
- Initialize the HTTP/2 ping frame.
- :param data: The data.
- :type data: bytes
- """
- super().__init__(0, Http2FrameType.PING, False)
- self.data = data
- self.ack = ack
-
- def __repr__(self) -> str:
- return f""
-
-
-# User action frames.
-UserActionFrames = Union[HeadersFrame, DataFrame, RstStreamFrame]
diff --git a/src/dubbo/remoting/aio/http2/headers.py b/src/dubbo/remoting/aio/http2/headers.py
deleted file mode 100644
index 1f715cc..0000000
--- a/src/dubbo/remoting/aio/http2/headers.py
+++ /dev/null
@@ -1,167 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import enum
-from collections import OrderedDict
-from typing import Optional, Union
-
-
-class PseudoHeaderName(enum.Enum):
- """
- Pseudo-header names defined in RFC 7540 Section.
- See: https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2
- """
-
- SCHEME = ":scheme"
- # Request pseudo-headers
- METHOD = ":method"
- AUTHORITY = ":authority"
- PATH = ":path"
- # Response pseudo-headers
- STATUS = ":status"
-
- @classmethod
- def to_list(cls) -> list[str]:
- """
- Get all pseudo-header names.
- Returns:
- The pseudo-header names list.
- """
- return [header.value for header in cls]
-
-
-class HttpMethod(enum.Enum):
- """
- HTTP method types.
- """
-
- GET = "GET"
- POST = "POST"
- PUT = "PUT"
- DELETE = "DELETE"
- HEAD = "HEAD"
- OPTIONS = "OPTIONS"
- PATCH = "PATCH"
- TRACE = "TRACE"
- CONNECT = "CONNECT"
-
-
-class Http2Headers:
- """
- HTTP/2 headers.
- """
-
- __slots__ = ["_headers"]
-
- def __init__(self):
- self._headers: OrderedDict[str, Optional[str]] = OrderedDict()
- self._init()
-
- def _init(self):
- # keep the order of headers
- self._headers = {name: "" for name in PseudoHeaderName.to_list()}
-
- def add(self, name: str, value: str) -> None:
- self._headers[name] = str(value)
-
- def get(self, name: str, default: Optional[str] = None) -> Optional[str]:
- return self._headers.get(name, default)
-
- @property
- def method(self) -> Optional[str]:
- return self.get(PseudoHeaderName.METHOD.value)
-
- @method.setter
- def method(self, value: Union[HttpMethod, str]) -> None:
- if isinstance(value, HttpMethod):
- value = value.value
- else:
- value = value.upper()
- self.add(PseudoHeaderName.METHOD.value, value)
-
- @property
- def scheme(self) -> Optional[str]:
- return self.get(PseudoHeaderName.SCHEME.value)
-
- @scheme.setter
- def scheme(self, value: str) -> None:
- self.add(PseudoHeaderName.SCHEME.value, value)
-
- @property
- def authority(self) -> Optional[str]:
- return self.get(PseudoHeaderName.AUTHORITY.value)
-
- @authority.setter
- def authority(self, value: str) -> None:
- self.add(PseudoHeaderName.AUTHORITY.value, value)
-
- @property
- def path(self) -> Optional[str]:
- return self.get(PseudoHeaderName.PATH.value)
-
- @path.setter
- def path(self, value: str) -> None:
- self.add(PseudoHeaderName.PATH.value, value)
-
- @property
- def status(self) -> Optional[str]:
- return self.get(PseudoHeaderName.STATUS.value)
-
- @status.setter
- def status(self, value: str) -> None:
- self.add(PseudoHeaderName.STATUS.value, value)
-
- def to_list(self) -> list[tuple[str, str]]:
- """
- Convert the headers to a list. The list contains all non-None headers.
- :return: The headers list.
- :rtype: List[Tuple[str, str]]
- """
- headers = []
- pseudo_headers = PseudoHeaderName.to_list()
- for name, value in list(self._headers.items()):
- if name in pseudo_headers and value == "":
- continue
- headers.append((str(name), str(value) or ""))
- return headers
-
- def to_dict(self) -> OrderedDict[str, str]:
- """
- Convert the headers to an ordered dict.
- :return: The headers' dict.
- :rtype: OrderedDict[str, Optional[str]]
- """
- headers_dict = OrderedDict()
- for key, value in self._headers.items():
- if value is not None and value != "":
- headers_dict[key] = value
- return headers_dict
-
- def __repr__(self) -> str:
- return f""
-
- @classmethod
- def from_list(cls, headers: list[tuple[str, str]]) -> "Http2Headers":
- """
- Create an Http2Headers object from a list.
- :param headers: The headers list.
- :type headers: List[Tuple[str, str]]
- :return: The Http2Headers object.
- :rtype: Http2Headers
- """
- http2_headers = cls()
- http2_headers._headers = dict(headers) # type: ignore
- return http2_headers
diff --git a/src/dubbo/remoting/aio/http2/protocol.py b/src/dubbo/remoting/aio/http2/protocol.py
deleted file mode 100644
index a762d0a..0000000
--- a/src/dubbo/remoting/aio/http2/protocol.py
+++ /dev/null
@@ -1,368 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import abc
-import asyncio
-import struct
-import time
-from typing import Optional
-
-from h2.config import H2Configuration
-from h2.connection import H2Connection
-
-from dubbo.constants import common_constants
-from dubbo.loggers import loggerFactory
-from dubbo.remoting.aio import ConnectionStateListener, EmptyConnectionStateListener, constants as h2_constants
-from dubbo.remoting.aio.exceptions import ProtocolError
-from dubbo.remoting.aio.http2.stream_handler import StreamServerMultiplexHandler
-from dubbo.remoting.aio.http2.controllers import RemoteFlowController
-from dubbo.remoting.aio.http2.frames import (
- DataFrame,
- HeadersFrame,
- Http2Frame,
- PingFrame,
- RstStreamFrame,
- UserActionFrames,
- WindowUpdateFrame,
-)
-from dubbo.remoting.aio.http2.registries import Http2FrameType
-from dubbo.remoting.aio.http2.stream import Http2Stream
-from dubbo.remoting.aio.http2.utils import Http2EventUtils
-from dubbo.url import URL
-from dubbo.utils import EventHelper, FutureHelper
-
-__all__ = ["AbstractHttp2Protocol", "Http2ClientProtocol", "Http2ServerProtocol"]
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class AbstractHttp2Protocol(asyncio.Protocol, abc.ABC):
- """
- HTTP/2 protocol implementation.
- """
-
- DEFAULT_PING_DATA = struct.pack(">Q", 0) # 8 bytes of 0
-
- __slots__ = [
- "_url",
- "_loop",
- "_h2_connection",
- "_transport",
- "_flow_controller",
- "_stream_handler",
- "_last_read",
- "_last_write",
- ]
-
- def __init__(self, url: URL, h2_config: H2Configuration):
- self._url = url
- self._loop = asyncio.get_running_loop()
-
- # Create the H2 state machine
- self._h2_connection = H2Connection(h2_config)
-
- # The transport instance
- self._transport: Optional[asyncio.Transport] = None
-
- self._flow_controller: Optional[RemoteFlowController] = None
-
- if self._url.attributes[common_constants.PROTOCOL_KEY] == Http2ServerProtocol:
- listener_factory = self._url.attributes[h2_constants.LISTENER_FACTORY_KEY]
- self._stream_handler = StreamServerMultiplexHandler(listener_factory)
- else:
- self._stream_handler = self._url.attributes[h2_constants.STREAM_HANDLER_KEY]
-
- # last time of receiving data
- self._last_read = time.time()
- # last time of sending data
- self._last_write = time.time()
-
- @property
- def last_read(self) -> float:
- """
- Get the last time of receiving data.
- """
- return self._last_read
-
- def _update_last_read(self) -> None:
- """
- Update the last time of receiving data.
- """
- self._last_read = time.time()
-
- @property
- def last_write(self) -> float:
- """
- Get the last time of sending data.
- """
- return self._last_write
-
- def _update_last_write(self) -> None:
- """
- Update the last time of sending data.
- """
- self._last_write = time.time()
-
- def connection_made(self, transport: asyncio.Transport):
- """
- Called when the connection is first established. We complete the following actions:
- 1. Save the transport.
- 2. Initialize the H2 connection.
- 3. Create and start the follow controller.
- 4. Initialize the stream handler.
- """
- self._transport = transport
- self._h2_connection.initiate_connection()
- self._flush()
-
- # Create and start the follow controller
- self._flow_controller = RemoteFlowController(self._h2_connection, self._transport, self._loop)
-
- # Initialize the stream handler
- self._stream_handler.do_init(self._loop, self)
-
- def get_next_stream_id(self, future) -> None:
- """
- Create a new stream.(thread-safe)
- :param future: The future to set the stream identifier.
- """
-
- def _inner_operation(_future):
- stream_id = self._h2_connection.get_next_available_stream_id()
- FutureHelper.set_result(_future, stream_id)
-
- self._loop.call_soon_threadsafe(_inner_operation, future)
-
- def send_frame(
- self,
- frame: UserActionFrames,
- stream: Http2Stream,
- event: Optional[asyncio.Event] = None,
- ) -> None:
- """
- Send the HTTP/2 frame.(thread-unsafe)
- :param frame: The frame to send.
- :type frame: UserActionFrames
- :param stream: The stream.
- :type stream: Http2Stream
- :param event: The event to be set after sending the frame.
- :type event: Optional[asyncio.Event]
- """
- frame_type = frame.frame_type
- if frame_type == Http2FrameType.HEADERS:
- self._send_headers_frame(frame, stream, event)
- elif frame_type == Http2FrameType.DATA:
- self._flow_controller.write_data(stream, frame, event)
- elif frame_type == Http2FrameType.RST_STREAM:
- self._send_reset_frame(frame.stream_id, frame.error_code.value, event)
- else:
- _LOGGER.warning("Unhandled frame: %s", frame)
-
- def _send_headers_frame(
- self,
- frame: HeadersFrame,
- stream: Http2Stream,
- event: Optional[asyncio.Event] = None,
- ) -> None:
- """
- Send the HTTP/2 headers frame.(thread-unsafe)
- :param frame: The frame to send.
- :type frame: HeadersFrame
- :param stream: The stream.
- :type stream: Http2Stream
- :param event: The event to be set after sending the frame.
- """
- if stream.id == -1:
- stream.id = self._h2_connection.get_next_available_stream_id()
- self._stream_handler.put_stream(stream.id, stream)
-
- self._h2_connection.send_headers(stream.id, frame.headers.to_list(), end_stream=frame.end_stream)
- self._flush()
- EventHelper.set(event)
-
- def _send_reset_frame(self, stream_id: int, error_code: int, event: Optional[asyncio.Event] = None) -> None:
- """
- Send the HTTP/2 reset frame.(thread-unsafe)
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param error_code: The error code.
- :type error_code: int
- :param event: The event to be set after sending the frame.
- :type event: Optional[asyncio.Event]
- """
- self._h2_connection.reset_stream(stream_id, error_code)
- self._flush()
- EventHelper.set(event)
-
- def _send_ping_frame(self, data: bytes = DEFAULT_PING_DATA) -> None:
- """
- Send the HTTP/2 ping frame.(thread-unsafe)
- :param data: The data to send. The length of the data must be 8 bytes.
- :type data: bytes
- """
- self._h2_connection.ping(data)
- self._flush()
-
- def _flush(self) -> None:
- """
- Flush the data to the transport.
- """
- outbound_data = self._h2_connection.data_to_send()
- if outbound_data != b"":
- self._transport.write(outbound_data)
- # Update the last write time
- self._update_last_write()
-
- def data_received(self, data):
- """
- Called when some data is received from the transport.
- :param data: The data received.
- :type data: bytes
- """
- # Update the last read time
- self._update_last_read()
-
- # Process the event
- events = self._h2_connection.receive_data(data)
- try:
- for event in events:
- frame = Http2EventUtils.convert_to_frame(event)
-
- # If frame is None, there are two possible cases:
- # 1. Events that are handled automatically by the H2 library (e.g. RemoteSettingsChanged, PingReceived).
- # -> We just need to send it.
- # 2. Events that are not implemented or do not require attention. -> We'll ignore it for now.
- if frame is not None:
- if isinstance(frame, WindowUpdateFrame):
- # Because flow control may be at the connection level, it is handled here
- self._flow_controller.release_flow_control(frame)
- elif isinstance(frame, (HeadersFrame, DataFrame, RstStreamFrame)):
- # Handle the frame by the stream handler
- self._stream_handler.handle_frame(frame)
- else:
- # Try handling other frames
- self._do_other_frame(frame)
-
- # Flush the data
- self._flush()
-
- except Exception as e:
- raise ProtocolError("Failed to process the Http/2 event.") from e
-
- def _do_other_frame(self, frame: Http2Frame):
- """
- This is a scalable approach to handle other frames. Subclasses can override this method to handle other frames.
- :param frame: The frame to handle.
- :type frame: Http2Frame
- """
- pass
-
- def ack_received_data(self, stream_id: int, ack_length: int) -> None:
- """
- Acknowledge the received data.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param ack_length: The length of the data to acknowledge.
- :type ack_length: int
- """
-
- self._h2_connection.acknowledge_received_data(ack_length, stream_id)
- self._flush()
-
- def close(self):
- """
- Close the connection.
- """
- self._h2_connection.close_connection()
- self._flush()
- self._transport.close()
-
- def connection_lost(self, exc):
- """
- Called when the connection is lost.
- """
- self._flow_controller.close()
-
-
-class Http2ClientProtocol(AbstractHttp2Protocol):
- """
- HTTP/2 client protocol implementation.
- """
-
- def __init__(
- self,
- url: URL,
- connection_listener: ConnectionStateListener = None,
- ):
- super().__init__(url, H2Configuration(client_side=True, header_encoding="utf-8"))
- self._connection_listener = connection_listener or EmptyConnectionStateListener()
-
- # get heartbeat interval -> default 60s
- self._heartbeat_interval = url.parameters.get(h2_constants.HEARTBEAT_KEY, h2_constants.DEFAULT_HEARTBEAT)
- self._ping_ack_future: Optional[asyncio.Future] = None
- self._heartbeat_task: Optional[asyncio.Task] = None
-
- def connection_made(self, transport: asyncio.Transport):
- super().connection_made(transport)
-
- # Start the heartbeat task
- self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())
-
- # Notify the connection is established
- asyncio.create_task(self._connection_listener.connection_made())
-
- def _do_other_frame(self, frame: Http2Frame):
- # Handle the ping frame
- if isinstance(frame, PingFrame) and frame.ack:
- FutureHelper.set_result(self._ping_ack_future, None)
-
- async def _heartbeat_loop(self):
- """
- Heartbeat loop. It is used to check the connection status.
- """
- while True:
- await asyncio.sleep(self._heartbeat_interval)
-
- # check last read time
- now = time.time()
- if now - self.last_read < self._heartbeat_interval:
- # the connection is normal
- continue
-
- # try to send ping frame to check the connection
- self._ping_ack_future = asyncio.Future()
- self._send_ping_frame()
- try:
- # wait for the ping ack
- await asyncio.wait_for(self._ping_ack_future, timeout=5)
- except asyncio.TimeoutError:
- # close the connection
- self.close()
- break
-
- def connection_lost(self, exc):
- super().connection_lost(exc)
-
- # Notify the connection is lost
- asyncio.create_task(self._connection_listener.connection_lost(exc))
-
-
-class Http2ServerProtocol(AbstractHttp2Protocol):
- """
- HTTP/2 server protocol implementation.
- """
-
- def __init__(self, url: URL):
- super().__init__(url, H2Configuration(client_side=False, header_encoding="utf-8"))
diff --git a/src/dubbo/remoting/aio/http2/registries.py b/src/dubbo/remoting/aio/http2/registries.py
deleted file mode 100644
index e232807..0000000
--- a/src/dubbo/remoting/aio/http2/registries.py
+++ /dev/null
@@ -1,298 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import enum
-from typing import Optional, Union
-
-__all__ = ["Http2FrameType", "Http2ErrorCode", "Http2Settings", "HttpStatus"]
-
-
-class Http2FrameType(enum.Enum):
- """
- Frame types are used in the frame header to identify the type of the frame.
- See: https://datatracker.ietf.org/doc/html/rfc7540#section-11.2
- """
-
- # Data frame, carries HTTP message bodies.
- DATA = 0x0
-
- # Headers frame, carries HTTP headers.
- HEADERS = 0x1
-
- # Priority frame, specifies the priority of a stream.
- PRIORITY = 0x2
-
- # Reset Stream frame, cancels a stream.
- RST_STREAM = 0x3
-
- # Settings frame, exchanges configuration parameters.
- SETTINGS = 0x4
-
- # Push Promise frame, used by the server to push resources.
- PUSH_PROMISE = 0x5
-
- # Ping frame, measures round-trip time and checks connectivity.
- PING = 0x6
-
- # Goaway frame, signals that the connection will be closed.
- GOAWAY = 0x7
-
- # Window Update frame, manages flow control window size.
- WINDOW_UPDATE = 0x8
-
- # Continuation frame, transmits large header blocks.
- CONTINUATION = 0x9
-
-
-class Http2ErrorCode(enum.Enum):
- """
- Error codes are 32-bit fields that are used in RST_STREAM and GOAWAY frames
- to convey the reasons for the stream or connection error.
-
- see: https://datatracker.ietf.org/doc/html/rfc7540#section-11.4
- """
-
- # The associated condition is not a result of an error.
- NO_ERROR = 0x0
-
- # The endpoint detected an unspecific protocol error.
- PROTOCOL_ERROR = 0x1
-
- # The endpoint encountered an unexpected internal error.
- INTERNAL_ERROR = 0x2
-
- # The endpoint detected that its peer violated the flow-control protocol.
- FLOW_CONTROL_ERROR = 0x3
-
- # The endpoint sent a SETTINGS frame but did not receive a response in a timely manner.
- SETTINGS_TIMEOUT = 0x4
-
- # The endpoint received a frame after a stream was half-closed.
- STREAM_CLOSED = 0x5
-
- # The endpoint received a frame with an invalid size.
- FRAME_SIZE_ERROR = 0x6
-
- # The endpoint refused the stream prior to performing any application processing
- REFUSED_STREAM = 0x7
-
- # Used by the endpoint to indicate that the stream is no longer needed.
- CANCEL = 0x8
-
- # The endpoint is unable to maintain the header compression context for the connection.
- COMPRESSION_ERROR = 0x9
-
- # The connection established in response to a CONNECT request (Section 8.3) was reset or abnormally closed.
- CONNECT_ERROR = 0xA
-
- # The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load.
- ENHANCE_YOUR_CALM = 0xB
-
- # The underlying transport has properties that do not meet minimum security requirements (see Section 9.2).
- INADEQUATE_SECURITY = 0xC
-
- # The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
- HTTP_1_1_REQUIRED = 0xD
-
- @classmethod
- def get(cls, code: int):
- """
- Get the error code by code.
- :param code: The error code.
- :type code: int
- """
- for error_code in cls:
- if error_code.value == code:
- return error_code
- # Unknown or unsupported error codes MUST NOT trigger any special behavior.
- # These MAY be treated as equivalent to INTERNAL_ERROR.
- return cls.INTERNAL_ERROR
-
-
-class Http2Settings:
- """
- The settings are used to communicate configuration parameters that affect how endpoints communicate.
- See: https://datatracker.ietf.org/doc/html/rfc7540#section-11.3
- """
-
- class Http2Setting:
- """
- HTTP/2 setting.
- """
-
- def __init__(self, code: int, initial_value: Optional[int] = None):
- self.code = code
- # If the initial value is "none", it means no limitation.
- self.initial_value = initial_value
-
- # Allows the sender to inform the remote endpoint of the maximum size
- # of the header compression table used to decode header blocks, in octets.
- HEADER_TABLE_SIZE = Http2Setting(0x1, 4096)
-
- # This setting can be used to disable server push (Section 8.2).
- ENABLE_PUSH = Http2Setting(0x2, 1)
-
- # Indicates the maximum number of concurrent streams that the sender will allow.
- MAX_CONCURRENT_STREAMS = Http2Setting(0x3, None)
-
- # Indicates the sender's initial window size (in octets) for stream-level flow control.
- # This setting affects the window size of all streams
- INITIAL_WINDOW_SIZE = Http2Setting(0x4, 65535)
-
- # Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
- MAX_FRAME_SIZE = Http2Setting(0x5, 16384)
-
- # This advisory setting informs a peer of the maximum size of header list
- # that the sender is prepared to accept, in octets.
- MAX_HEADER_LIST_SIZE = Http2Setting(0x6, None)
-
-
-class HttpStatus(enum.Enum):
- """
- Enum for HTTP status codes as defined in RFC 7231 and related specifications.
- """
-
- # 1xx Informational
- CONTINUE = 100
- SWITCHING_PROTOCOLS = 101
-
- # 2xx Success
- OK = 200
- CREATED = 201
- ACCEPTED = 202
- NON_AUTHORITATIVE_INFORMATION = 203
- NO_CONTENT = 204
- RESET_CONTENT = 205
- PARTIAL_CONTENT = 206
-
- # 3xx Redirection
- MULTIPLE_CHOICES = 300
- MOVED_PERMANENTLY = 301
- FOUND = 302
- SEE_OTHER = 303
- NOT_MODIFIED = 304
- USE_PROXY = 305
- TEMPORARY_REDIRECT = 307
- PERMANENT_REDIRECT = 308
-
- # 4xx Client Error
- BAD_REQUEST = 400
- UNAUTHORIZED = 401
- PAYMENT_REQUIRED = 402
- FORBIDDEN = 403
- NOT_FOUND = 404
- METHOD_NOT_ALLOWED = 405
- NOT_ACCEPTABLE = 406
- PROXY_AUTHENTICATION_REQUIRED = 407
- REQUEST_TIMEOUT = 408
- CONFLICT = 409
- GONE = 410
- LENGTH_REQUIRED = 411
- PRECONDITION_FAILED = 412
- PAYLOAD_TOO_LARGE = 413
- URI_TOO_LONG = 414
- UNSUPPORTED_MEDIA_TYPE = 415
- RANGE_NOT_SATISFIABLE = 416
- EXPECTATION_FAILED = 417
- I_AM_A_TEAPOT = 418
- MISDIRECTED_REQUEST = 421
- UNPROCESSABLE_ENTITY = 422
- LOCKED = 423
- FAILED_DEPENDENCY = 424
- UPGRADE_REQUIRED = 426
- PRECONDITION_REQUIRED = 428
- TOO_MANY_REQUESTS = 429
- REQUEST_HEADER_FIELDS_TOO_LARGE = 431
- UNAVAILABLE_FOR_LEGAL_REASONS = 451
-
- # 5xx Server Error
- INTERNAL_SERVER_ERROR = 500
- NOT_IMPLEMENTED = 501
- BAD_GATEWAY = 502
- SERVICE_UNAVAILABLE = 503
- GATEWAY_TIMEOUT = 504
- HTTP_VERSION_NOT_SUPPORTED = 505
- VARIANT_ALSO_NEGOTIATES = 506
- INSUFFICIENT_STORAGE = 507
- LOOP_DETECTED = 508
- NOT_EXTENDED = 510
- NETWORK_AUTHENTICATION_REQUIRED = 511
-
- @classmethod
- def from_code(cls, code: int) -> "HttpStatus":
- for status in cls:
- if status.value == code:
- return status
-
- @staticmethod
- def is_1xx(status: Union["HttpStatus", int]) -> bool:
- """
- Check if the given status is an informational (1xx) status code.
- :param status: HttpStatus to check
- :type status: Union[HttpStatus, int]
- :return: True if the status code is in the 1xx range, False otherwise
- :rtype: bool
- """
- value = status if isinstance(status, int) else status.value
- return 100 <= value < 200
-
- @staticmethod
- def is_2xx(status: Union["HttpStatus", int]) -> bool:
- """
- Check if the given status is a successful (2xx) status code.
- :param status: HttpStatus to check
- :type status: Union[HttpStatus, int]
- :return: True if the status code is in the 2xx range, False otherwise
- :rtype: bool
- """
- value = status if isinstance(status, int) else status.value
- return 200 <= value < 300
-
- @staticmethod
- def is_3xx(status: Union["HttpStatus", int]) -> bool:
- """
- Check if the given status is a redirection (3xx) status code.
- :param status: HttpStatus to check
- :type status: Union[HttpStatus, int]
- :return: True if the status code is in the 3xx range, False otherwise
- :rtype: bool
- """
- value = status if isinstance(status, int) else status.value
- return 300 <= value < 400
-
- @staticmethod
- def is_4xx(status: Union["HttpStatus", int]) -> bool:
- """
- Check if the given status is a client error (4xx) status code.
- :param status: HttpStatus to check
- :type status: Union[HttpStatus, int]
- :return: True if the status code is in the 4xx range, False otherwise
- :rtype: bool
- """
- value = status if isinstance(status, int) else status.value
- return 400 <= value < 500
-
- @staticmethod
- def is_5xx(status: Union["HttpStatus", int]) -> bool:
- """
- Check if the given status is a server error (5xx) status code.
- :param status: HttpStatus to check
- :type status: Union[HttpStatus, int]
- :return: True if the status code is in the 5xx range, False otherwise
- :rtype: bool
- """
- value = status if isinstance(status, int) else status.value
- return 500 <= value < 600
diff --git a/src/dubbo/remoting/aio/http2/stream.py b/src/dubbo/remoting/aio/http2/stream.py
deleted file mode 100644
index 26d6d89..0000000
--- a/src/dubbo/remoting/aio/http2/stream.py
+++ /dev/null
@@ -1,277 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-import asyncio
-from concurrent.futures import ThreadPoolExecutor
-from typing import Optional
-
-from dubbo.remoting.aio.exceptions import StreamError
-from dubbo.remoting.aio.http2.frames import (
- DataFrame,
- HeadersFrame,
- RstStreamFrame,
- UserActionFrames,
-)
-from dubbo.remoting.aio.http2.headers import Http2Headers
-from dubbo.remoting.aio.http2.registries import Http2ErrorCode
-
-__all__ = ["Http2Stream", "DefaultHttp2Stream"]
-
-
-class Http2Stream(abc.ABC):
- """
- A "stream" is an independent, bidirectional sequence of frames exchanged
- between the client and server within an HTTP/2 connection.
- see: https://datatracker.ietf.org/doc/html/rfc7540#section-5
- """
-
- __slots__ = ["_id", "_listener", "_local_closed", "_remote_closed"]
-
- def __init__(self, stream_id: int, listener: "Http2Stream.Listener"):
- self._id = stream_id
-
- self._listener = listener
- self._listener.bind(self)
-
- # Whether the stream is closed locally. -> it means the stream can't send any more frames.
- self._local_closed = False
- # Whether the stream is closed remotely. -> it means the stream can't receive any more frames.
- self._remote_closed = False
-
- @property
- def id(self) -> int:
- """
- Get the stream identifier.
- """
- return self._id
-
- @id.setter
- def id(self, stream_id: int) -> None:
- """
- Set the stream identifier.
- """
- self._id = stream_id
-
- @property
- def listener(self) -> "Http2Stream.Listener":
- """
- Get the listener.
- """
- return self._listener
-
- @property
- def local_closed(self) -> bool:
- """
- Check if the stream is closed locally.
- """
- return self._local_closed
-
- @property
- def remote_closed(self) -> bool:
- """
- Check if the stream is closed remotely.
- """
- return self._remote_closed
-
- def close_local(self) -> None:
- """
- Close the stream locally.
- """
- if self._local_closed:
- return
- self._local_closed = True
-
- def close_remote(self) -> None:
- """
- Close the stream remotely.
- """
- if self._remote_closed:
- return
- self._remote_closed = True
-
- @abc.abstractmethod
- def send_headers(self, headers: Http2Headers, end_stream: bool = False) -> None:
- """
- Send the headers.
- :param headers: The HTTP/2 headers.
- The second send of headers will be treated as trailers (end_stream must be True).
- :type headers: Http2Headers
- :param end_stream: Whether to close the stream after sending the data.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def send_data(self, data: bytes, end_stream: bool = False) -> None:
- """
- Send the data.
- :param data: The data to send.
- :type data: bytes
- :param end_stream: Whether to close the stream after sending the data.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def cancel_by_local(self, error_code: Http2ErrorCode) -> None:
- """
- Cancel the stream locally. -> send RST_STREAM frame.
- :param error_code: The error code.
- :type error_code: Http2ErrorCode
- """
- raise NotImplementedError()
-
- class Listener(abc.ABC):
- """
- Http2StreamListener is a base class for handling events in an HTTP/2 stream.
-
- This class provides a set of callback methods that are called when specific
- events occur on the stream, such as receiving headers, receiving data, or
- resetting the stream. To use this class, create a subclass and implement the
- callback methods for the events you want to handle.
- """
-
- __slots__ = ["_stream"]
-
- def __init__(self):
- self._stream: Optional[Http2Stream] = None
-
- def bind(self, stream: "Http2Stream") -> None:
- """
- Bind the stream to the listener.
- :param stream: The stream to bind.
- :type stream: Http2Stream
- """
- self._stream = stream
-
- @property
- def stream(self) -> "Http2Stream":
- """
- Get the stream.
- """
- return self._stream
-
- @abc.abstractmethod
- def on_headers(self, headers: Http2Headers, end_stream: bool) -> None:
- """
- Called when the headers are received.
- :param headers: The HTTP/2 headers.
- :type headers: Http2Headers
- :param end_stream: Whether the stream is closed after receiving the headers.
- :type end_stream: bool
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def on_data(self, data: bytes, end_stream: bool) -> None:
- """
- Called when the data is received.
- :param data: The data.
- :type data: bytes
- :param end_stream: Whether the stream is closed after receiving the data.
- """
- raise NotImplementedError()
-
- @abc.abstractmethod
- def cancel_by_remote(self, error_code: Http2ErrorCode) -> None:
- """
- Cancel the stream remotely.
- :param error_code: The error code.
- :type error_code: Http2ErrorCode
- """
- raise NotImplementedError()
-
-
-class DefaultHttp2Stream(Http2Stream):
- """
- Default implementation of the Http2Stream.
- """
-
- __slots__ = [
- "_loop",
- "_protocol",
- "_inbound_controller",
- "_outbound_controller",
- "_headers_sent",
- ]
-
- def __init__(
- self,
- stream_id: int,
- listener: "Http2Stream.Listener",
- loop: asyncio.AbstractEventLoop,
- protocol,
- executor: Optional[ThreadPoolExecutor] = None,
- ):
- # Avoid circular import
- from dubbo.remoting.aio.http2.controllers import (
- FrameInboundController,
- FrameOutboundController,
- )
-
- super().__init__(stream_id, listener)
- self._loop = loop
- self._protocol = protocol
-
- # steam inbound controller
- self._inbound_controller: FrameInboundController = FrameInboundController(
- self, self._loop, self._protocol, executor
- )
- # steam outbound controller
- self._outbound_controller: FrameOutboundController = FrameOutboundController(self, self._loop, self._protocol)
-
- # The flag to indicate whether the headers have been sent.
- self._headers_sent = False
-
- def close_local(self) -> None:
- super().close_local()
- self._outbound_controller.close()
-
- def close_remote(self) -> None:
- super().close_remote()
- self._inbound_controller.close()
-
- def send_headers(self, headers: Http2Headers, end_stream: bool = False) -> None:
- if self.local_closed:
- raise StreamError("The stream has been closed locally.")
- elif self._headers_sent and not end_stream:
- raise StreamError("Trailers must be the last frame of the stream(end_stream must be True).")
-
- self._headers_sent = True
- headers_frame = HeadersFrame(self.id, headers, end_stream=end_stream)
- self._outbound_controller.write_headers(headers_frame)
-
- def send_data(self, data: bytes, end_stream: bool = False) -> None:
- if self.local_closed:
- raise StreamError("The stream has been closed locally.")
- elif not self._headers_sent:
- raise StreamError("Headers have not been sent.")
- data_frame = DataFrame(self.id, data, len(data), end_stream=end_stream)
- self._outbound_controller.write_data(data_frame)
-
- def cancel_by_local(self, error_code: Http2ErrorCode) -> None:
- if self.local_closed:
- # The stream has been closed locally.
- return
- rst_frame = RstStreamFrame(self.id, error_code)
- self._outbound_controller.write_rst(rst_frame)
-
- def receive_frame(self, frame: UserActionFrames) -> None:
- """
- Receive the frame.
- :param frame: The frame to receive.
- :type frame: UserActionFrames
- """
- self._inbound_controller.write_frame(frame)
diff --git a/src/dubbo/remoting/aio/http2/stream_handler.py b/src/dubbo/remoting/aio/http2/stream_handler.py
deleted file mode 100644
index 4e1064e..0000000
--- a/src/dubbo/remoting/aio/http2/stream_handler.py
+++ /dev/null
@@ -1,174 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import asyncio
-import uuid
-from concurrent.futures import ThreadPoolExecutor
-from typing import Callable, Optional
-
-from dubbo.loggers import loggerFactory
-from dubbo.remoting.aio.http2.frames import UserActionFrames
-from dubbo.remoting.aio.http2.registries import Http2FrameType
-from dubbo.remoting.aio.http2.stream import DefaultHttp2Stream, Http2Stream
-
-_all__ = [
- "StreamMultiplexHandler",
- "StreamClientMultiplexHandler",
- "StreamServerMultiplexHandler",
-]
-
-_LOGGER = loggerFactory.get_logger()
-
-
-class StreamMultiplexHandler:
- """
- The StreamMultiplexHandler class is responsible for managing the HTTP/2 streams.
- """
-
- __slots__ = ["_loop", "_protocol", "_streams", "_executor"]
-
- def __init__(self):
- # Import the Http2Protocol class here to avoid circular imports.
- from dubbo.remoting.aio.http2.protocol import AbstractHttp2Protocol
-
- self._loop: Optional[asyncio.AbstractEventLoop] = None
- self._protocol: Optional[AbstractHttp2Protocol] = None
-
- # The map of stream_id to stream.
- self._streams: Optional[dict[int, DefaultHttp2Stream]] = None
-
- # The executor for handling received frames.
- self._executor = ThreadPoolExecutor(thread_name_prefix=f"dubbo_tri_stream_{str(uuid.uuid4())}")
-
- def do_init(self, loop: asyncio.AbstractEventLoop, protocol) -> None:
- """
- Initialize the StreamMultiplexHandler.\
- :param loop: The event loop.
- :type loop: asyncio.AbstractEventLoop
- :param protocol: The HTTP/2 protocol.
- :type protocol: Http2Protocol
- """
- self._loop = loop
- self._protocol = protocol
- self._streams = {}
-
- def put_stream(self, stream_id: int, stream: DefaultHttp2Stream) -> None:
- """
- Put the stream into the stream map.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :param stream: The stream.
- :type stream: DefaultHttp2Stream
- """
- self._streams[stream_id] = stream
-
- def get_stream(self, stream_id: int) -> Optional[DefaultHttp2Stream]:
- """
- Get the stream by stream identifier.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :return: The stream.
- """
- return self._streams.get(stream_id)
-
- def remove_stream(self, stream_id: int) -> None:
- """
- Remove the stream by stream identifier.
- :param stream_id: The stream identifier.
- :type stream_id: int
- """
- self._streams.pop(stream_id, None)
-
- def handle_frame(self, frame: UserActionFrames) -> None:
- """
- Handle the HTTP/2 frame.
- :param frame: The HTTP/2 frame.
- :type frame: UserActionFrames
- """
- stream = self._streams.get(frame.stream_id)
- if stream:
- # It must be ensured that the event loop is not blocked,
- # and if there is a blocking operation, the executor must be used.
- stream.receive_frame(frame)
-
- if frame.end_stream and stream.local_closed:
- self.remove_stream(frame.stream_id)
- else:
- _LOGGER.warning("Stream %s not found. Ignoring frame %s", frame.stream_id, frame)
-
- def destroy(self) -> None:
- """
- Destroy the StreamMultiplexHandler.
- """
- self._streams = None
- self._protocol = None
- self._loop = None
-
-
-class StreamClientMultiplexHandler(StreamMultiplexHandler):
- """
- The StreamClientMultiplexHandler class is responsible for managing the HTTP/2 streams on the client side.
- """
-
- def create(self, listener: Http2Stream.Listener) -> DefaultHttp2Stream:
- """
- Create a new stream.
- :param listener: The stream listener.
- :type listener: Http2Stream.Listener
- :return: The stream.
- :rtype: DefaultHttp2Stream
- """
- return DefaultHttp2Stream(-1, listener, self._loop, self._protocol, self._executor)
-
-
-class StreamServerMultiplexHandler(StreamMultiplexHandler):
- """
- The StreamServerMultiplexHandler class is responsible for managing the HTTP/2 streams on the server side.
- """
-
- __slots__ = ["_listener_factory"]
-
- def __init__(
- self,
- listener_factory: Callable[[], Http2Stream.Listener],
- ):
- super().__init__()
- self._listener_factory = listener_factory
-
- def register(self, stream_id: int) -> DefaultHttp2Stream:
- """
- Register the stream.
- :param stream_id: The stream identifier.
- :type stream_id: int
- :return: The stream.
- :rtype: DefaultHttp2Stream
- """
- stream_listener = self._listener_factory()
- new_stream = DefaultHttp2Stream(stream_id, stream_listener, self._loop, self._protocol, self._executor)
- self.put_stream(stream_id, new_stream)
- return new_stream
-
- def handle_frame(self, frame: UserActionFrames) -> None:
- """
- Handle the HTTP/2 frame.
- :param frame: The HTTP/2 frame.
- """
- # Register the stream if the frame is a HEADERS frame.
- if frame.frame_type == Http2FrameType.HEADERS:
- self.register(frame.stream_id)
-
- # Handle the frame.
- super().handle_frame(frame)
diff --git a/src/dubbo/remoting/aio/http2/utils.py b/src/dubbo/remoting/aio/http2/utils.py
deleted file mode 100644
index 965a56a..0000000
--- a/src/dubbo/remoting/aio/http2/utils.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Union
-
-import h2.events as h2_event
-
-from dubbo.remoting.aio.http2.frames import (
- DataFrame,
- HeadersFrame,
- PingFrame,
- RstStreamFrame,
- WindowUpdateFrame,
-)
-from dubbo.remoting.aio.http2.headers import Http2Headers
-from dubbo.remoting.aio.http2.registries import Http2ErrorCode
-
-__all__ = ["Http2EventUtils"]
-
-
-class Http2EventUtils:
- """
- A utility class for converting H2 events to HTTP/2 frames.
- """
-
- @staticmethod
- def convert_to_frame(
- event: h2_event.Event,
- ) -> Union[HeadersFrame, DataFrame, RstStreamFrame, WindowUpdateFrame, PingFrame, None]:
- """
- Convert a h2.events.Event to HTTP/2 Frame.
- :param event: The H2 event.
- :type event: h2.events.Event
- :return: The HTTP/2 frame.
- :rtype: Union[HeadersFrame, DataFrame, RstStreamFrame, WindowUpdateFrame, PingFrame, None]
- """
- if isinstance(
- event,
- (
- h2_event.RequestReceived,
- h2_event.ResponseReceived,
- h2_event.TrailersReceived,
- ),
- ):
- # HEADERS frame.
- return HeadersFrame(
- event.stream_id,
- Http2Headers.from_list(event.headers),
- end_stream=event.stream_ended is not None,
- )
- elif isinstance(event, h2_event.DataReceived):
- # DATA frame.
- return DataFrame(
- event.stream_id,
- event.data,
- event.flow_controlled_length,
- end_stream=event.stream_ended is not None,
- )
- elif isinstance(event, h2_event.StreamReset):
- # RST_STREAM frame.
- return RstStreamFrame(event.stream_id, Http2ErrorCode.get(event.error_code))
- elif isinstance(event, h2_event.WindowUpdated):
- # WINDOW_UPDATE frame.
- return WindowUpdateFrame(event.stream_id, event.delta)
- elif isinstance(event, (h2_event.PingAckReceived, h2_event.PingReceived)):
- # PING frame.
- return PingFrame(event.ping_data, ack=isinstance(event, h2_event.PingAckReceived))
-
- return None
diff --git a/src/dubbo/serialization/__init__.py b/src/dubbo/serialization/__init__.py
deleted file mode 100644
index ee2ef61..0000000
--- a/src/dubbo/serialization/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from ._interfaces import Deserializer, SerializationError, Serializer, ensure_bytes
-from .custom_serializers import CustomDeserializer, CustomSerializer
-from .direct_serializers import DirectDeserializer, DirectSerializer
-
-__all__ = [
- "Serializer",
- "Deserializer",
- "SerializationError",
- "ensure_bytes",
- "DirectSerializer",
- "DirectDeserializer",
- "CustomSerializer",
- "CustomDeserializer",
-]
diff --git a/src/dubbo/serialization/_interfaces.py b/src/dubbo/serialization/_interfaces.py
deleted file mode 100644
index 5793dc1..0000000
--- a/src/dubbo/serialization/_interfaces.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import abc
-from typing import Any, Union
-
-__all__ = ["Serializer", "Deserializer", "SerializationError", "ensure_bytes"]
-
-
-class SerializationError(Exception):
- """
- Serialization error.
- """
-
- def __init__(self, message: str):
- super().__init__(message)
- self.message = message
-
- def __str__(self):
- return self.message
-
-
-def ensure_bytes(obj: Union[bytes, bytearray, memoryview]) -> bytes:
- """
- Ensure that the input object is bytes or can be converted to bytes.
- :param obj: The object to ensure.
- :type obj: Union[bytes, bytearray, memoryview]
- :return: The bytes object.
- :rtype: bytes
- """
-
- if isinstance(obj, bytes):
- return obj
- elif isinstance(obj, (bytearray, memoryview)):
- return bytes(obj)
- else:
- raise SerializationError(
- f"SerializationError: The incoming object is of type '{type(obj).__name__}', "
- f"which is not supported. Expected types are 'bytes', 'bytearray', or 'memoryview'.\n"
- f"Current object type: '{type(obj).__name__}'.\n"
- f"Please provide data of the correct type or configure the serializer to handle the current input type."
- )
-
-
-class Serializer(abc.ABC):
- """
- Interface for serializer.
- """
-
- @abc.abstractmethod
- def serialize(self, *args, **kwargs) -> bytes:
- """
- Serialize an object to bytes.
- :param args: The arguments to serialize.
- :param kwargs: The keyword arguments to serialize.
- :return: The serialized bytes.
- :rtype: bytes
- :raises SerializationError: If serialization fails.
- """
- raise NotImplementedError()
-
-
-class Deserializer(abc.ABC):
- """
- Interface for deserializer.
- """
-
- @abc.abstractmethod
- def deserialize(self, data: bytes) -> Any:
- """
- Deserialize bytes to an object.
- :param data: The bytes to deserialize.
- :type data: bytes
- :return: The deserialized object.
- :rtype: Any
- :raises SerializationError: If deserialization fails.
- """
- raise NotImplementedError()
diff --git a/src/dubbo/serialization/custom_serializers.py b/src/dubbo/serialization/custom_serializers.py
deleted file mode 100644
index 83ee494..0000000
--- a/src/dubbo/serialization/custom_serializers.py
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any
-
-from dubbo.serialization import (
- Deserializer,
- SerializationError,
- Serializer,
- ensure_bytes,
-)
-from dubbo.types import DeserializingFunction, SerializingFunction
-
-__all__ = ["CustomSerializer", "CustomDeserializer"]
-
-from dubbo.utils import FunctionHelper
-
-
-class CustomSerializer(Serializer):
- """
- Custom serializer that uses a custom serializing function to serialize objects.
- """
-
- __slots__ = ["serializer"]
-
- def __init__(self, serializer: SerializingFunction):
- self.serializer = serializer
-
- def serialize(self, *args, **kwargs) -> bytes:
- """
- Serialize an object to bytes.
- :param args: The arguments to serialize.
- :param kwargs: The keyword arguments to serialize.
- :return: The serialized bytes.
- :rtype: bytes
- :raises SerializationError: If the object is not of type bytes, bytearray, or memoryview.
- """
- try:
- serialized_obj = FunctionHelper.call_func(self.serializer, (args, kwargs))
- except Exception as e:
- raise SerializationError(
- f"SerializationError: Failed to serialize object. Please check the serializer. \nDetails: {str(e)}",
- )
-
- return ensure_bytes(serialized_obj)
-
-
-class CustomDeserializer(Deserializer):
- """
- Custom deserializer that uses a custom deserializing function to deserialize objects.
- """
-
- __slots__ = ["deserializer"]
-
- def __init__(self, deserializer: DeserializingFunction):
- self.deserializer = deserializer
-
- def deserialize(self, data: bytes) -> Any:
- """
- Deserialize bytes to an object.
- :param data: The bytes to deserialize.
- :type data: bytes
- :return: The deserialized object.
- :rtype: Any
- :raises SerializationError: If the object is not of type bytes, bytearray, or memoryview.
- """
- try:
- deserialized_obj = self.deserializer(data)
- except Exception as e:
- raise SerializationError(
- f"SerializationError: Failed to deserialize object. Please check the deserializer. \nDetails: {str(e)}",
- )
-
- return deserialized_obj
diff --git a/src/dubbo/serialization/direct_serializers.py b/src/dubbo/serialization/direct_serializers.py
deleted file mode 100644
index 0b41557..0000000
--- a/src/dubbo/serialization/direct_serializers.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from typing import Any
-
-from dubbo.classes import SingletonBase
-from dubbo.serialization import Deserializer, Serializer, ensure_bytes
-
-__all__ = ["DirectSerializer", "DirectDeserializer"]
-
-
-class DirectSerializer(Serializer, SingletonBase):
- """
- Direct serializer that does not perform any serialization. This serializer only checks if the given object is of
- type bytes, bytearray, or memoryview and ensures it is returned as a bytes object. If the object is not of the
- expected types, a SerializationError is raised. This serializer is a singleton.
- """
-
- def serialize(self, *args, **kwargs) -> bytes:
- """
- Serialize an object to bytes. we only return the first argument as bytes.
- :param args: The arguments to serialize.
- :param kwargs: The keyword arguments to serialize.
- :return: The serialized bytes.
- :rtype: bytes
- :raises SerializationError: If the object is not of type bytes, bytearray, or memoryview.
- """
- return ensure_bytes(args[0]) if args else b""
-
-
-class DirectDeserializer(Deserializer):
- """
- Direct deserializer that does not perform any serialization. This deserializer only returns the given bytes object
- """
-
- def deserialize(self, data: bytes) -> Any:
- """
- Deserialize bytes to an object.
- :param data: The bytes to deserialize.
- :type data: bytes
- :return: The deserialized object.
- :rtype: Any
- :raises SerializationError: If the object is not of type bytes, bytearray, or memoryview.
- """
- return data
diff --git a/src/dubbo/server.py b/src/dubbo/server.py
deleted file mode 100644
index b7c4dee..0000000
--- a/src/dubbo/server.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import threading
-from typing import Optional
-
-from dubbo.bootstrap import Dubbo
-from dubbo.configs import ServiceConfig
-from dubbo.constants import common_constants
-from dubbo.extension import extensionLoader
-from dubbo.protocol import Protocol
-from dubbo.registry.protocol import RegistryProtocol
-from dubbo.url import URL
-
-
-class Server:
- """
- Dubbo Server
- """
-
- def __init__(self, service_config: ServiceConfig, dubbo: Optional[Dubbo] = None):
- self._initialized = False
- self._global_lock = threading.RLock()
-
- self._service = service_config
- self._dubbo = dubbo or Dubbo()
-
- self._protocol: Optional[Protocol] = None
- self._url: Optional[URL] = None
- self._exported = False
-
- # initialize the server
- self._initialize()
-
- def _initialize(self):
- """
- Initialize the server.
- """
- with self._global_lock:
- if self._initialized:
- return
-
- # get the protocol
- service_protocol = extensionLoader.get_extension(Protocol, self._service.protocol)()
-
- registry_config = self._dubbo.registry_config
-
- self._protocol = (
- RegistryProtocol(registry_config, service_protocol) if self._dubbo.registry_config else service_protocol
- )
-
- # build url
- service_url = self._service.to_url()
- if registry_config:
- self._url = registry_config.to_url().copy()
- self._url.attributes[common_constants.EXPORT_KEY] = service_url
- for k, v in service_url.attributes.items():
- self._url.attributes[k] = v
- else:
- self._url = service_url
-
- def start(self):
- """
- Start the server.
- """
- with self._global_lock:
- if self._exported:
- return
-
- self._protocol.export(self._url)
-
- self._exported = True
diff --git a/src/dubbo/types.py b/src/dubbo/types.py
deleted file mode 100644
index f6c452b..0000000
--- a/src/dubbo/types.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import enum
-from dataclasses import dataclass
-from typing import Any, Callable
-
-__all__ = [
- "SerializingFunction",
- "DeserializingFunction",
- "RpcType",
- "RpcTypes",
-]
-
-SerializingFunction = Callable[..., bytes]
-DeserializingFunction = Callable[[bytes], Any]
-
-
-@dataclass
-class RpcType:
- """
- RpcType
- """
-
- name: str
- client_stream: bool
- server_stream: bool
-
-
-@enum.unique
-class RpcTypes(enum.Enum):
- UNARY = RpcType("Unary", False, False)
- CLIENT_STREAM = RpcType("ClientStream", True, False)
- SERVER_STREAM = RpcType("ServerStream", False, True)
- BI_STREAM = RpcType("BiStream", True, True)
-
- @classmethod
- def from_name(cls, name: str) -> RpcType:
- """
- Get RpcType by name. Case-insensitive.
- :param name: RpcType name
- :return: RpcType
- """
- for item in cls:
- # ignore case
- if item.value.name.lower() == name.lower():
- return item.value
- raise ValueError(f"Unknown RpcType name: {name}")
diff --git a/src/dubbo/url.py b/src/dubbo/url.py
deleted file mode 100644
index 8dae9fa..0000000
--- a/src/dubbo/url.py
+++ /dev/null
@@ -1,362 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import copy
-from typing import Any, Optional
-from urllib import parse
-from urllib.parse import urlencode, urlunparse
-
-from dubbo.constants import common_constants
-
-__all__ = ["URL", "create_url"]
-
-
-def create_url(url: str, encoded: bool = False) -> "URL":
- """
- Creates a URL object from a URL string.
-
- This function takes a URL string and converts it into a URL object.
- If the 'encoded' parameter is set to True, the URL string will be decoded before being converted.
-
- :param url: The URL string to be converted into a URL object.
- :type url: str
- :param encoded: Determines if the URL string should be decoded before being converted. Defaults to False.
- :type encoded: bool
- :return: A URL object.
- :rtype: URL
- :raises ValueError: If the URL format is invalid.
- """
- # If the URL is encoded, decode it
- if encoded:
- url = parse.unquote(url)
-
- if common_constants.PROTOCOL_SEPARATOR not in url:
- raise ValueError("Invalid URL format: missing protocol")
-
- parsed_url = parse.urlparse(url)
-
- if not parsed_url.scheme:
- raise ValueError("Invalid URL format: missing scheme.")
-
- return URL(
- parsed_url.scheme,
- parsed_url.hostname or "",
- parsed_url.port,
- parsed_url.username or "",
- parsed_url.password or "",
- parsed_url.path.lstrip("/"),
- {k: v[0] for k, v in parse.parse_qs(parsed_url.query).items()},
- )
-
-
-class URL:
- """
- URL - Uniform Resource Locator.
- """
-
- __slots__ = [
- "_scheme",
- "_host",
- "_port",
- "_location",
- "_username",
- "_password",
- "_path",
- "_parameters",
- "_attributes",
- ]
-
- def __init__(
- self,
- scheme: str,
- host: str,
- port: Optional[int] = None,
- username: str = "",
- password: str = "",
- path: str = "",
- parameters: Optional[dict[str, str]] = None,
- attributes: Optional[dict[str, Any]] = None,
- ):
- """
- Initialize the URL object.
-
- :param scheme: The scheme of the URL (e.g., 'http', 'https').
- :type scheme: str
- :param host: The host of the URL.
- :type host: str
- :param port: The port number of the URL, defaults to None.
- :type port: int, optional
- :param username: The username for authentication, defaults to an empty string.
- :type username: str, optional
- :param password: The password for authentication, defaults to an empty string.
- :type password: str, optional
- :param path: The path of the URL, defaults to an empty string.
- :type path: str, optional
- :param parameters: The query parameters of the URL as a dictionary, defaults to None.
- :type parameters: Dict[str, str], optional
- :param attributes: Additional attributes of the URL as a dictionary, defaults to None.
- :type attributes: Dict[str, Any], optional
- """
- self._scheme = scheme
- self._host = host
- self._port = port
- self._location = f"{host}:{port}" if port else host
- self._username = username
- self._password = password
- self._path = path
- self._parameters = parameters or {}
- self._attributes = attributes or {}
-
- @property
- def scheme(self) -> str:
- """
- Get or set the scheme of the URL.
-
- :return: The scheme of the URL.
- :rtype: str
- """
- return self._scheme
-
- @scheme.setter
- def scheme(self, value: str):
- self._scheme = value
-
- @property
- def host(self) -> str:
- """
- Get or set the host of the URL.
-
- :return: The host of the URL.
- :rtype: str
- """
- return self._host
-
- @host.setter
- def host(self, value: str):
- self._host = value
- self._location = f"{self.host}:{self.port}" if self.port else self.host
-
- @property
- def port(self) -> Optional[int]:
- """
- Get or set the port of the URL.
-
- :return: The port of the URL.
- :rtype: int, optional
- """
- return self._port
-
- @port.setter
- def port(self, value: int):
- if value > 0:
- self._port = value
- self._location = f"{self.host}:{self.port}"
-
- @property
- def location(self) -> str:
- """
- Get or set the location (host:port) of the URL.
-
- :return: The location of the URL.
- :rtype: str
- """
- return self._location
-
- @location.setter
- def location(self, value: str):
- try:
- values = value.split(":")
- self.host = values[0]
- if len(values) == 2:
- self.port = int(values[1])
- except Exception as e:
- raise ValueError(f"Invalid location: {value}") from e
-
- @property
- def username(self) -> str:
- """
- Get or set the username for authentication.
-
- :return: The username.
- :rtype: str
- """
- return self._username
-
- @username.setter
- def username(self, value: str):
- self._username = value
-
- @property
- def password(self) -> str:
- """
- Get or set the password for authentication.
-
- :return: The password.
- :rtype: str
- """
- return self._password
-
- @password.setter
- def password(self, value: str):
- self._password = value
-
- @property
- def path(self) -> str:
- """
- Get or set the path of the URL.
-
- :return: The path of the URL.
- :rtype: str
- """
- return self._path
-
- @path.setter
- def path(self, value: str):
- self._path = value.lstrip("/")
-
- @property
- def parameters(self) -> dict[str, str]:
- """
- Get the query parameters of the URL.
-
- :return: The query parameters as a dictionary.
- :rtype: Dict[str, str]
- """
- return self._parameters
-
- @property
- def attributes(self) -> dict[str, Any]:
- """
- Get the additional attributes of the URL.
-
- :return: The attributes as a dictionary.
- :rtype: Dict[str, Any]
- """
- return self._attributes
-
- def to_str(
- self,
- contain_ip: bool = True,
- contain_user: bool = True,
- contain_path: bool = True,
- contain_parameters: bool = True,
- encode: bool = False,
- ) -> str:
- """
- Converts the URL to a string.
- :param contain_ip: Determines if the URL should contain the IP address. Defaults to True.
- :type contain_ip: bool
- :param contain_user: Determines if the URL should contain the username. Defaults to True.
- :type contain_user: bool
- :param contain_path: Determines if the URL should contain the path. Defaults to True.
- :type contain_path: bool
- :param contain_parameters: Determines if the URL should contain the parameters. Defaults to True.
- :param encode: Determines if the URL should be encoded. Defaults to False.
- :type encode: bool
- :return: The URL string.
- :rtype: str
- """
-
- # Construct the scheme part
- scheme = ""
- netloc = ""
- if contain_ip:
- scheme = self.scheme
-
- # Construct the netloc part
- if contain_user and self.username and self.password:
- netloc = f"{self.username}:{self.password}@{self.location}"
- else:
- netloc = self.location
-
- # Construct the path part
- path = self.path if contain_path else ""
-
- # Construct the query part
- query = urlencode(self.parameters) if contain_parameters else ""
-
- # Construct the URL
- url = ""
- if scheme or netloc or path or query:
- url = urlunparse((scheme, netloc, path, "", query, ""))
-
- if encode:
- url = parse.quote(url, safe="")
-
- return url
-
- def copy(self) -> "URL":
- """
- Copy the URL object.
-
- :return: A shallow copy of the URL object.
- :rtype: URL
- """
- return copy.copy(self)
-
- def deepcopy(self) -> "URL":
- """
- Deep copy the URL object.
-
- :return: A deep copy of the URL object.
- :rtype: URL
- """
- return copy.deepcopy(self)
-
- def __eq__(self, other: Any) -> bool:
- if not isinstance(other, URL):
- return False
-
- return (
- self.scheme == other.scheme
- and self.host == other.host
- and self.port == other.port
- and self.username == other.username
- and self.password == other.password
- and self.path == other.path
- and self.parameters == other.parameters
- and self.attributes == other.attributes
- )
-
- def __copy__(self) -> "URL":
- return URL(
- self.scheme,
- self.host,
- self.port,
- self.username,
- self.password,
- self.path,
- self.parameters.copy(),
- self.attributes.copy(),
- )
-
- def __deepcopy__(self, memo) -> "URL":
- return URL(
- self.scheme,
- self.host,
- self.port,
- self.username,
- self.password,
- self.path,
- copy.deepcopy(self.parameters, memo),
- copy.deepcopy(self.attributes, memo),
- )
-
- def __str__(self) -> str:
- return self.to_str()
-
- def __repr__(self) -> str:
- return self.to_str()
diff --git a/src/dubbo/utils.py b/src/dubbo/utils.py
deleted file mode 100644
index d5e039e..0000000
--- a/src/dubbo/utils.py
+++ /dev/null
@@ -1,387 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import inspect
-import os
-import socket
-from collections.abc import Callable
-from typing import Any, Optional
-
-import psutil
-
-__all__ = ["EventHelper", "FutureHelper", "NetworkUtils", "CpuUtils", "FunctionHelper"]
-
-
-class EventHelper:
- """
- Helper class for event operations.
- """
-
- @staticmethod
- def is_set(event) -> bool:
- """
- Check if the event is set.
-
- :param event: Event object, you can use threading.Event or any other object that supports the is_set operation.
- :type event: Any
- :return: True if the event is set, or False if the is_set method is not supported or the event is invalid.
- :rtype: bool
- """
- return event.is_set() if event and hasattr(event, "is_set") else False
-
- @staticmethod
- def set(event) -> bool:
- """
- Attempt to set the event object.
-
- :param event: Event object, you can use threading.Event or any other object that supports the set operation.
- :type event: Any
- :return: True if the event was set, False otherwise
- (such as the event is invalid or does not support the set operation).
- :rtype: bool
- """
- if event is None:
- return False
-
- # If the event supports the set operation, set the event and return True
- if hasattr(event, "set"):
- event.set()
- return True
-
- # If the event is invalid or does not support the set operation, return False
- return False
-
- @staticmethod
- def clear(event) -> bool:
- """
- Attempt to clear the event object.
-
- :param event: Event object, you can use threading.Event or any other object that supports the clear operation.
- :type event: Any
- :return: True if the event was cleared, False otherwise
- (such as the event is invalid or does not support the clear operation).
- :rtype: bool
- """
- if not event:
- return False
-
- # If the event supports the clear operation, clear the event and return True
- if hasattr(event, "clear"):
- event.clear()
- return True
-
- # If the event is invalid or does not support the clear operation, return False
- return False
-
-
-class FutureHelper:
- """
- Helper class for future operations.
- """
-
- @staticmethod
- def done(future) -> bool:
- """
- Check if the future is done.
-
- :param future: Future object
- :type future: Any
- :return: True if the future is done, False otherwise.
- :rtype: bool
- """
- return future.done() if future and hasattr(future, "done") else False
-
- @staticmethod
- def set_result(future, result):
- """
- Set the result of the future.
-
- :param future: Future object
- :type future: Any
- :param result: Result to set
- :type result: Any
- """
- if not future or FutureHelper.done(future):
- return
-
- if hasattr(future, "set_result"):
- future.set_result(result)
-
- @staticmethod
- def set_exception(future, exception):
- """
- Set the exception to the future.
-
- :param future: Future object
- :type future: Any
- :param exception: Exception to set
- :type exception: Exception
- """
- if not future or FutureHelper.done(future):
- return
-
- if hasattr(future, "set_exception"):
- future.set_exception(exception)
-
-
-class NetworkUtils:
- """
- Helper class for network operations.
- """
-
- @staticmethod
- def is_address_reachable(ip: str, timeout: int = 0) -> bool:
- """
- Use ping to check if the IP address is reachable.
-
- :param ip: The IP address.
- :type ip: str
- :param timeout: The timeout in seconds.
- :type timeout: int
- :return: True if the IP address is reachable, False otherwise.
- :rtype: bool
- """
- try:
- # Use the ping command to check if the IP address is reachable
- result = os.system(f"ping -c 1 -W {timeout} {ip} > /dev/null 2>&1")
- return result == 0
- except Exception:
- return False
-
- @staticmethod
- def is_loopback_address(ip: str) -> bool:
- """
- Check if the IP address is a loopback address.
-
- :param ip: The IP address.
- :type ip: str
- :return: True if the IP address is a loopback address, False otherwise.
- :rtype: bool
- """
- return ip.startswith("127.") or ip == "localhost"
-
- @staticmethod
- def get_local_address() -> Optional[str]:
- """
- Find first valid IP from local network card.
-
- :return: The local IP address. If not found, return None.
- :rtype: str
- """
- try:
- # use psutil to get the local IP address
- for iface_name, iface_addrs in psutil.net_if_addrs().items():
- for addr in iface_addrs:
- # only consider IPv4 address
- if addr.family == socket.AF_INET:
- # ignore the loopback address and check if the IP address is reachable
- if not NetworkUtils.is_loopback_address(addr.address) and NetworkUtils.is_address_reachable(
- addr.address
- ):
- return addr.address
- except Exception:
- pass
-
- # if the local IP address is not found, try to get the IP address using the socket
- try:
- local_host_ip = socket.gethostbyname(socket.gethostname())
- return local_host_ip
- except Exception:
- pass
-
- return None
-
-
-class CpuUtils:
- """
- Helper class for CPU operations.
- """
-
- @staticmethod
- def get_cpu_count(logical=True) -> int:
- """
- Get the number of CPUs in the system.
-
- :return: The number of CPUs in the system.
- :rtype: int
- """
- return psutil.cpu_count(logical=logical)
-
- @staticmethod
- def get_total_cpu_usage(interval=1) -> float:
- """
- Get the total CPU usage of the system.
-
- :param interval: The interval in seconds.
- :type interval: int
- :return: The total CPU usage of the system.
- :rtype: float
- """
- return psutil.cpu_percent(interval=interval)
-
- @staticmethod
- def get_per_cpu_usage(interval=1) -> list[float]:
- """
- Get the per CPU usage of the system.
-
- :param interval: The interval in seconds.
- :type interval: int
- :return: The per CPU usage of the system.
- :rtype: list
- """
- return psutil.cpu_percent(interval=interval, percpu=True)
-
- @staticmethod
- def get_load_avg() -> tuple[float, float, float]:
- """
- Get the load average over the last 1, 5, and 15 minutes
-
- :return: The load average of the system.
- :rtype: list
- """
- return psutil.getloadavg()
-
- @staticmethod
- def get_cpu_stats():
- """
- Get the CPU stats of the system.
-
- :return: The CPU stats of the system.
- """
- return psutil.cpu_stats()
-
- @staticmethod
- def get_cpu_freq():
- """
- Get the current CPU frequency.
-
- :return: The current CPU frequency.
- """
- return psutil.cpu_freq()
-
-
-class FunctionHelper:
- """
- Helper class for function operations.
- """
-
- @staticmethod
- def is_callable(callable_func: Callable) -> bool:
- """
- Check if the function is callable.
-
- :param callable_func: The callable function.
- :type callable_func: Callable
- :return: True if the function is callable, False otherwise.
- :rtype: bool
- """
- return inspect.isfunction(callable_func) or inspect.ismethod(callable_func)
-
- @staticmethod
- def has_args(func: Callable) -> bool:
- """
- Check if the function has arguments.
-
- :param func: The callable function.
- :type func: Callable
- :return: True if the function has arguments, False otherwise.
- :rtype: bool
- """
- return inspect.Parameter.VAR_POSITIONAL in [p.kind for p in inspect.signature(func).parameters.values()]
-
- @staticmethod
- def has_kwargs(func: Callable) -> bool:
- """
- Check if the function has keyword arguments.
-
- :param func: The callable function.
- :type func: Callable
- :return: True if the function has keyword arguments, False otherwise.
- :rtype: bool
- """
- return inspect.Parameter.VAR_KEYWORD in [p.kind for p in inspect.signature(func).parameters.values()]
-
- @staticmethod
- def call_func(func: Callable, args_and_kwargs: Any = None) -> Any:
- """
- Call the function with the given arguments and keyword arguments.
-
- :param func:
- The callable function.
- :type func: Callable
- :param args_and_kwargs:
- The arguments and keyword arguments.
-
- the provided values must follow these forms:
- - No arguments required, pass -> None
- - Multiple positional arguments -> Tuple (e.g., ((1, 2),{}))
- - Multiple keyword arguments -> Dict (e.g., ((),{"a": 1, "b": 2}))
- - Both positional and keyword arguments -> Tuple of length 2
- (e.g., ((1, 2), {"a": 1, "b": 2}))
-
- :type args_and_kwargs: Tuple
- :return: The result of the function.
- :rtype: Any
- """
-
- # split the arguments and keyword arguments
- if isinstance(args_and_kwargs, tuple) and len(args_and_kwargs) == 2:
- args, kwargs = args_and_kwargs
- else:
- raise ValueError(
- "Invalid function arguments, the provided values must follow these forms:"
- "1.No arguments required, pass -> None"
- "2.Multiple positional arguments -> Tuple (e.g., ((1, 2),{}))"
- "3.Multiple keyword arguments -> Dict (e.g., ((),{'a': 1, 'b': 2}))"
- "4.Both positional and keyword arguments -> Tuple of length 2"
- " (e.g., ((1, 2), {'a': 1, 'b': 2}))"
- )
-
- # If the function is not callable, try to call the function directly
- try:
- if not FunctionHelper.is_callable(func):
- return func(*args, **kwargs)
- except Exception as e:
- raise e
-
- # Get the function signature
- sig = inspect.signature(func)
-
- # Get the function parameters and check if the function supports *args and **kwargs
- params = sig.parameters
- param_kinds = [p.kind for p in params.values()]
- has_var_positional = inspect.Parameter.VAR_POSITIONAL in param_kinds
- has_var_keyword = inspect.Parameter.VAR_KEYWORD in param_kinds
-
- # If the function has no arguments or only one argument, call the function directly
- if len(params) == 0 or args_and_kwargs is None:
- return func()
-
- # If the function accepts both *args and **kwargs
- if has_var_positional and has_var_keyword:
- return func(*args, **kwargs)
-
- # If the function supports *args but not **kwargs
- if has_var_positional:
- return func(*args)
-
- # If the function supports **kwargs but not *args
- if has_var_keyword:
- return func(**kwargs)
-
- # common case
- bound_args = sig.bind(*args, **kwargs)
- bound_args.apply_defaults()
- return func(*bound_args.args, **bound_args.kwargs)
diff --git a/tests/__init__.py b/tests/__init__.py
index bcba37a..ba48d2a 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,15 +1,17 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
diff --git a/tests/logging.conf b/tests/logging.conf
new file mode 100644
index 0000000..aeb83f8
--- /dev/null
+++ b/tests/logging.conf
@@ -0,0 +1,49 @@
+[loggers]
+keys=root, dubbo
+
+[handlers]
+keys=null,console,file
+
+[formatters]
+keys=verbose,simple,default
+
+[formatter_verbose]
+format=%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s
+datefmt=
+class=logging.Formatter
+
+[formatter_simple]
+format=%(levelname)s %(message)s
+datefmt=%Y-%m-%d %H:%M:%S
+class=logging.Formatter
+
+[formatter_default]
+format=%(asctime)s %(message)s
+datefmt=%Y-%m-%d %H:%M:%S
+class=logging.Formatter
+
+[logger_root]
+level=NOTSET
+handlers=
+
+[logger_dubbo]
+level=DEBUG
+handlers=console,file
+propagate=1
+qualname=
+
+[handler_null]
+class=NullHandler
+level=DEBUG
+args=()
+
+[handler_console]
+class=StreamHandler
+level=DEBUG
+args=()
+
+[handler_file]
+class=handlers.TimedRotatingFileHandler
+level=DEBUG
+formatter=default
+args=('dubbo.log','D',1,0,'utf8')
\ No newline at end of file
diff --git a/tests/test_client.txt b/tests/test_client.txt
new file mode 100644
index 0000000..a272ea1
Binary files /dev/null and b/tests/test_client.txt differ
diff --git a/tests/test_client_every_new.txt b/tests/test_client_every_new.txt
new file mode 100644
index 0000000..3bbb515
Binary files /dev/null and b/tests/test_client_every_new.txt differ
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..9c9953b
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,29 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+from dubbo_client import ApplicationConfig
+
+
+
+def test_application_config_new():
+ application_config = ApplicationConfig('test_app', version='2.0.0', owner='caozupeng', error='ssd')
+ assert application_config.architecture == 'web'
+ assert application_config.name == 'test_app'
+ assert application_config.environment == 'run'
+ assert application_config.version == '2.0.0'
+ assert 'owner' in application_config.__dict__
+ assert 'ssd' not in application_config.__dict__
diff --git a/tests/test_dubbo.txt b/tests/test_dubbo.txt
new file mode 100644
index 0000000..860d8bf
Binary files /dev/null and b/tests/test_dubbo.txt differ
diff --git a/tests/test_kstore_platform.py b/tests/test_kstore_platform.py
new file mode 100644
index 0000000..388d4ca
--- /dev/null
+++ b/tests/test_kstore_platform.py
@@ -0,0 +1,38 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+import time
+
+from dubbo_client import ZookeeperRegistry, DubboClient, DubboClientError, ApplicationConfig
+
+
+if __name__ == '__main__':
+ config = ApplicationConfig('test_rpclib')
+ service_interface = 'com.qianmi.kstore.provider.CustomerAddressProvider'
+ # 该对象较重,有zookeeper的连接,需要保存使用
+ registry = ZookeeperRegistry('zookeeper:2181', config)
+ # registry = MulticastRegistry('224.5.6.7:1234', config)
+ user_provider = DubboClient(service_interface, registry, version='1.0')
+ for i in range(1000):
+ try:
+ print user_provider.findAddressByCustomerId(1)
+ # print user_provider.save(1, 3)
+
+ except DubboClientError, client_error:
+ print client_error.message
+ print client_error.data
+ time.sleep(5)
diff --git a/tests/test_performance.py b/tests/test_performance.py
new file mode 100644
index 0000000..72ffde8
--- /dev/null
+++ b/tests/test_performance.py
@@ -0,0 +1,94 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+import pstats
+
+from pyjsonrpc import HttpClient
+
+from dubbo_client import ZookeeperRegistry, DubboClient
+
+
+number = 1000
+
+
+def test_client_every_new():
+ for x in range(number):
+ user_provider = HttpClient(url="http://{0}{1}".format('172.19.3.111:38080/', 'com.ofpay.demo.api.UserProvider'))
+ user_provider.getUser('A003')
+ user_provider.queryUser(
+ {u'age': 18, u'time': 1428463514153, u'sex': u'MAN', u'id': u'A003', u'name': u'zhangsan'})
+ # user_provider.queryAll()
+ user_provider.isLimit('MAN', 'Joe')
+ user_provider('getUser', 'A005')
+
+
+def test_client():
+ user_provider = HttpClient(url="http://{0}{1}".format('172.19.3.111:38080/', 'com.ofpay.demo.api.UserProvider'))
+ for x in range(number):
+ user_provider.getUser('A003')
+ user_provider.queryUser(
+ {u'age': 18, u'time': 1428463514153, u'sex': u'MAN', u'id': u'A003', u'name': u'zhangsan'})
+ # user_provider.queryAll()
+ user_provider.isLimit('MAN', 'Joe')
+ user_provider('getUser', 'A005')
+
+
+def test_dubbo():
+ service_interface = 'com.ofpay.demo.api.UserProvider'
+ # 该对象较重,有zookeeper的连接,需要保存使用
+ registry = ZookeeperRegistry('172.19.65.33:2181')
+ user_provider = DubboClient(service_interface, registry, version='2.0')
+ for x in range(number):
+ user_provider.getUser('A003')
+ user_provider.queryUser(
+ {u'age': 18, u'time': 1428463514153, u'sex': u'MAN', u'id': u'A003', u'name': u'zhangsan'})
+ # user_provider.queryAll()
+ user_provider.isLimit('MAN', 'Joe')
+ user_provider('getUser', 'A005')
+
+
+if __name__ == '__main__':
+ """
+ 在我的mac 4c8g笔记本上,同时启动服务端和客户端(忽略网络开销)
+ test_client_every_new 运行1000次,13.380 seconds
+ test_client 运行1000次, 12.851 seconds
+ test_dubbo运行1000次 13.559 seconds
+ 说明
+ 1、加上Dubbo的封装,和原生的jsonrpclib差距很小,可以忽略不计
+ 2、每次new HttpClient和保持一个HttpClient句柄复用的效率相差不大
+ 3、每秒接近300次的调用,对一个业务系统来说绰绰有余
+ 大量的应用程序不需要这么快的运行速度,因为用户根本感觉不出来。
+ 例如开发一个下载MP3的网络应用程序,C程序的运行时间需要0.001秒,
+ 而Python程序的运行时间需要0.1秒,慢了100倍,但由于网络更慢,需要等待1秒,
+ 你想,用户能感觉到1.001秒和1.1秒的区别吗?
+ 这就好比F1赛车和普通的出租车在北京三环路上行驶的道理一样,
+ 虽然F1赛车理论时速高达400公里,但由于三环路堵车的时速只有20公里,
+ 因此,作为乘客,你感觉的时速永远是20公里。
+
+ """
+ # print u'test_client_every_new 运行{0}次, 耗时{1}'.format(number, timeit.timeit(test_client_every_new, number=1))
+ # print u'test_client 运行{0}次, 耗时{1}'.format(number, timeit.timeit(test_client, number=1))
+ # print u'test_dubbo 运行{0}次, 耗时{1}'.format(number, timeit.timeit(test_dubbo, number=1))
+ # profile.run("test_dubbo()", 'test_dubbo.txt')
+ p = pstats.Stats('test_dubbo.txt')
+ p.sort_stats('time').print_stats()
+ # profile.run('test_client_every_new()', 'test_client_every_new.txt')
+ np = pstats.Stats('test_client_every_new.txt')
+ np.sort_stats('time').print_stats()
+ # profile.run('test_client()', 'test_client.txt')
+ cp = pstats.Stats('test_client.txt')
+ cp.sort_stats('time').print_stats()
diff --git a/tests/test_rawclient.py b/tests/test_rawclient.py
new file mode 100644
index 0000000..85db169
--- /dev/null
+++ b/tests/test_rawclient.py
@@ -0,0 +1,32 @@
+# coding = utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+from pyjsonrpc import HttpClient
+
+
+def test_client_every_new():
+ user_provider = HttpClient(url="http://{0}{1}".format('zookeeper:38081/', 'com.ofpay.demo.api.UserProvider2'))
+ print user_provider.getUser('A003')
+ print user_provider.queryUser(
+ {u'age': 18, u'time': 1428463514153, u'sex': u'MAN', u'id': u'A003', u'name': u'zhangsan'})
+ print user_provider.queryAll()
+ print user_provider.isLimit('MAN', 'Joe')
+ print user_provider('getUser', 'A005')
+
+
+if __name__ == '__main__':
+ test_client_every_new()
diff --git a/tests/test_register_config.py b/tests/test_register_config.py
new file mode 100644
index 0000000..7976c91
--- /dev/null
+++ b/tests/test_register_config.py
@@ -0,0 +1,39 @@
+# coding=utf-8
+
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+import time
+
+from dubbo_client import ApplicationConfig
+from dubbo_client import DubboClient, DubboClientError
+from dubbo_client import ZookeeperRegistry
+
+
+def test_config_init():
+ config = ApplicationConfig('test_register_config')
+ service_interface = 'com.ofpay.demo.api.UserProvider'
+ registry = ZookeeperRegistry('172.19.66.49:2181', config)
+ user_provider = DubboClient(service_interface, registry, version='1.0.0')
+ for i in range(10000):
+ try:
+ print user_provider.findOne()
+ except DubboClientError, client_error:
+ print client_error
+ time.sleep(1)
+
+if __name__ == '__main__':
+ test_config_init()
diff --git a/tests/test_registry.py b/tests/test_registry.py
new file mode 100644
index 0000000..5350c65
--- /dev/null
+++ b/tests/test_registry.py
@@ -0,0 +1,53 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+from dubbo_client import ZookeeperRegistry, MulticastRegistry, Registry
+
+__author__ = 'caozupeng'
+
+
+def multicat():
+ registry = MulticastRegistry('224.5.6.7:1234')
+ registry.subscribe('com.ofpay.demo.api.UserProvider')
+ print registry.get_providers('com.ofpay.demo.api.UserProvider')
+
+
+def zookeeper():
+ registry = ZookeeperRegistry('172.19.65.33:2181')
+ registry.subscribe('com.ofpay.demo.api.UserProvider')
+ print registry.get_providers('com.ofpay.demo.api.UserProvider')
+
+
+def test_registry():
+ registry = Registry()
+ registry._add_node("com.ofpay.demo.api.UserProvider",
+ "jsonrpc://192.168.2.1:38081/com.ofpay.demo.api.UserProvider2?"
+ "anyhost=true&application=jsonrpcdemo&default.timeout=10000&"
+ "dubbo=2.4.10&environment=product&interface=com.ofpay.demo.api.UserProvider&"
+ "methods=getUser,queryAll,isLimit,queryUser&owner=wenwu&pid=60402&revision=2.0&"
+ "side=provider×tamp=1429105028153&version=2.0")
+ registry._add_node("com.ofpay.demo.api.UserProvider",
+ "jsonrpc://192.168.2.1:38081/com.ofpay.demo.api.UserProvider?"
+ "anyhost=true&application=jsonrpcdemo&default.timeout=10000&"
+ "dubbo=2.4.10&environment=product&interface=com.ofpay.demo.api.UserProvider&"
+ "methods=getUser,queryAll,isLimit,queryUser&owner=wenwu&pid=60402&revision=2.0&"
+ "side=provider×tamp=1429105028153&version=1.0")
+ assert registry._service_providers
+
+
+if __name__ == '__main__':
+ multicat()
diff --git a/tests/test_rpclib.py b/tests/test_rpclib.py
new file mode 100644
index 0000000..bd1a619
--- /dev/null
+++ b/tests/test_rpclib.py
@@ -0,0 +1,47 @@
+# coding=utf-8
+"""
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+"""
+import time
+
+from dubbo_client import ZookeeperRegistry, DubboClient, DubboClientError, ApplicationConfig
+
+__author__ = 'caozupeng'
+
+if __name__ == '__main__':
+ config = ApplicationConfig('test_rpclib')
+ service_interface = 'com.ofpay.demo.api.UserProvider'
+ # 该对象较重,有zookeeper的连接,需要保存使用
+ registry = ZookeeperRegistry('115.28.74.185:2181', config)
+ # registry = MulticastRegistry('224.5.6.7:1234', config)
+ user_provider = DubboClient(service_interface, registry, version='2.0')
+ for i in range(1000):
+ try:
+ print user_provider.getUser('A003')
+ # print user_provider.getUser(123)
+ # print user_provider.queryUser(
+ # {u'age': 18, u'time': 1428463514153, u'sex': u'MAN', u'id': u'A003', u'name': u'zhangsan'})
+ # datas = user_provider.queryAll()
+ # for key, user in datas.items():
+ # print user['name']
+ # print user_provider.isLimit('MAN', 'Joe')
+ # print user_provider('getUser', 'A005')
+ # print user_provider.notFunc()
+ # print user_provider.gotException()
+ except DubboClientError, client_error:
+ print client_error.message
+ print client_error.data
+ time.sleep(5)
diff --git a/tests/test_url.py b/tests/test_url.py
deleted file mode 100644
index e0f538b..0000000
--- a/tests/test_url.py
+++ /dev/null
@@ -1,76 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from dubbo.url import URL, create_url
-
-
-def test_str_to_url():
- url_0 = create_url("http://www.facebook.com/friends?param1=value1¶m2=value2")
- assert url_0.scheme == "http"
- assert url_0.host == "www.facebook.com"
- assert url_0.port is None
- assert url_0.path == "friends"
- assert url_0.parameters["param1"] == "value1"
- assert url_0.parameters["param2"] == "value2"
-
- url_1 = create_url("ftp://username:password@192.168.1.7:21/1/read.txt")
- assert url_1.scheme == "ftp"
- assert url_1.username == "username"
- assert url_1.password == "password"
- assert url_1.host == "192.168.1.7"
- assert url_1.port == 21
- assert url_1.location == "192.168.1.7:21"
- assert url_1.path == "1/read.txt"
-
- url_2 = create_url("file:///home/user1/router.js?type=script")
- assert url_2.scheme == "file"
- assert url_2.path == "home/user1/router.js"
-
- url_3 = create_url(
- "http%3A//www.facebook.com/friends%3Fparam1%3Dvalue1%26param2%3Dvalue2",
- encoded=True,
- )
- assert url_3.scheme == "http"
- assert url_3.host == "www.facebook.com"
- assert url_3.port is None
- assert url_3.path == "friends"
- assert url_3.parameters["param1"] == "value1"
- assert url_3.parameters["param2"] == "value2"
-
-
-def test_url_to_str():
- url_0 = URL(
- scheme="tri",
- host="127.0.0.1",
- port=12,
- username="username",
- password="password",
- path="path",
- parameters={"type": "a"},
- )
- assert url_0.to_str() == "tri://username:password@127.0.0.1:12/path?type=a"
-
- url_1 = URL(
- scheme="tri",
- host="127.0.0.1",
- port=12,
- path="path",
- parameters={"type": "a"},
- )
- assert url_1.to_str() == "tri://127.0.0.1:12/path?type=a"
-
- url_2 = URL(scheme="tri", host="127.0.0.1", port=12, parameters={"type": "a"})
- assert url_2.to_str() == "tri://127.0.0.1:12?type=a"
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..7d70f13
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+1.0.0b6
\ No newline at end of file