[Task #7] modify project structure and add example (#8)

* refactor: add license and refactor structure

* feat: add example and deploy doc

* refactor: rename project name

* feat: add example

* Delete gateway_proxy directory

* refactor: modify example structure

Co-authored-by: shuimudongtian <2267491517@qq.com>
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..a08668f
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,30 @@
+__pycache__/
+*.py[cod]
+
+# Distribution / packaging
+bin/
+build/
+develop-eggs/
+dist/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Unit test / coverage reports
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+*.log
+*.pot
+
+# vscode
+.vscode/
\ No newline at end of file
diff --git a/client/client/__init__.py b/client/client/__init__.py
new file mode 100644
index 0000000..d996748
--- /dev/null
+++ b/client/client/__init__.py
@@ -0,0 +1,34 @@
+# -*- 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.
+ */
+"""
+
+__project__ = "gateway-proxy"
+__author__ = "https://shenyu.apache.org"
+
+
+def get_help():
+    print("Please contact author:{}".format(__author__))
+
+
+def get_doc():
+    print("detail document: ")
+
+
+
+
diff --git a/client/client/api.py b/client/client/api.py
new file mode 100644
index 0000000..e99174c
--- /dev/null
+++ b/client/client/api.py
@@ -0,0 +1,229 @@
+# -*- 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 requests
+from requests.exceptions import (ReadTimeout, RequestException, ConnectTimeout)
+
+from client.client.config import GatewayConfig, ALL_ENV
+from client.client.exception import (EnvTypeExp, SetUpUriExp, SetUpRegisterExp,
+                                     SetUpGatewayExp, GetRegisterTokenErr)
+
+
+class GatewayProxy(object):
+    """
+    gateway proxy class
+    """
+    def __init__(self):
+        self.headers = {"Content-Type": "application/json;charset=UTF-8"}
+        self.env = GatewayConfig.uri.get("environment")
+        if not isinstance(self.env, str) or self.env not in ALL_ENV:
+            raise EnvTypeExp(env=self.env)
+        self.register_token = None
+        self._set_up_gateway_service_url()
+        self._setup_uri_params()
+        self._setup_register_params()
+        self._get_register_token()
+        if not self.register_token:
+            raise GetRegisterTokenErr(msg="can't get register token")
+        else:
+            self.headers.update({"X-Access-Token": self.register_token})
+
+    def _set_up_gateway_service_url(self):
+        try:
+            self.gateway_base_urls = GatewayConfig.__dict__.get(self.env, {}).get("servers", "").split(",")
+            self.port = GatewayConfig.__dict__.get(self.env, {}).get("port")
+            url_pre = "http://{}:{}"
+            self.gateway_base_urls = [url_pre.format(_url, self.port) for _url in self.gateway_base_urls]
+            self.register_meta_data_suffix = "/gateway-shenyu/register-metadata"
+            self.register_uri_suffix = "/gateway-shenyu/register-uri"
+
+            self.register_meta_data_path_list = [_url + self.register_meta_data_suffix for _url in
+                                                 self.gateway_base_urls]
+            self.register_uri_list = [_url + self.register_uri_suffix for _url in self.gateway_base_urls]
+        except SetUpGatewayExp as sue:
+            raise SetUpUriExp(app_name=GatewayConfig.uri.get("app_name"), msg=str(sue), env=self.env)
+
+    def _setup_uri_params(self):
+        """
+        setup uri params
+        """
+        try:
+            self.host = GatewayConfig.uri.get("host")
+            self.port = GatewayConfig.uri.get("port")
+            self.app_name = GatewayConfig.uri.get("app_name")
+            self.rpc_type = GatewayConfig.uri.get("rpc_type")
+            self.context_path = GatewayConfig.uri.get("context_path")
+            self.register_type = GatewayConfig.register.get("register_type")
+            self.register_servers = GatewayConfig.register.get("register_servers")
+        except SetUpUriExp as se:
+            raise SetUpUriExp(app_name=GatewayConfig.uri.get("app_name"), msg=str(se), env=self.env)
+        
+    def _setup_register_params(self):
+        """
+        setup register params
+        """
+        try:
+            self.register_token_type = GatewayConfig.register.get("register_type")
+            self.register_base_servers = GatewayConfig.register.get("servers").split(",")
+            self.register_path = "/platform/login"
+            self.register_token_servers = [_url + self.register_uri_suffix for _url in self.register_base_servers]
+            self.register_username = GatewayConfig.register.get("props", {}).get("username")
+            self.register_password = GatewayConfig.register.get("props", {}).get("password")
+        except SetUpRegisterExp as se:
+            raise SetUpRegisterExp(app_name=GatewayConfig.uri.get("app_name"), msg=str(se), env=self.env)
+
+    def _request(self, url, json_data):
+        """
+        base post request
+        """
+        if not url or not isinstance(url, str) or not isinstance(json_data, dict):
+            print("_request url or data format error")
+            return False
+        try:
+            res = requests.post(url, json=json_data, headers=self.headers, timeout=5)
+            status_code = res.status_code
+            msg = res.text
+        except ConnectTimeout as ce:
+            print("connect timeout, detail is:{}".format(str(ce)))
+            return False
+        except ReadTimeout as rte:
+            print("read time out, detail is:{}".format(str(rte)))
+            return False
+        except RequestException as rqe:
+            print("request except, detail is:{}".format(str(rqe)))
+            return False
+        except Exception as e:
+            print("request ({}) except, detail is:{}".format(url, str(e)))
+            return False
+        else:
+            # According to the interface return value of the gateway registry, the request is considered successful
+            # only when msg==success; if the interface return value of the gateway registry changes, the judgment
+            # method should also be modified
+            if msg == "success":
+                return True
+            print("request ({}) fail, status code is:{}, msg is:{}".format(res.url, status_code, msg))
+            return False
+
+    def _get_register_token(self):
+        """
+        base get http request
+        """
+        default_res = ""
+        params = {
+            "userName": self.register_username,
+            "password": self.register_password
+        }
+        try:
+            for url in self.register_token_servers:
+                res = requests.get(url, params=params, timeout=5)
+                status_code = res.status_code
+                res_data = res.json()
+                token = res_data.get("data", {}).get("token", "")
+                if token:
+                    self.register_token = token
+                    break
+        except ConnectTimeout as ce:
+            print("connect timeout, detail is:{}".format(str(ce)))
+            return False
+        except ReadTimeout as rte:
+            print("read time out, detail is:{}".format(str(rte)))
+            return False
+        except RequestException as rqe:
+            print("request except, detail is:{}".format(str(rqe)))
+            return False
+        except Exception as e:
+            print("get register token except, detail is:{}".format(str(e)))
+            return False
+
+    def register_uri(self):
+        """
+        register uri
+        """
+        json_data = {
+            "appName": self.app_name,
+            "contextPath": self.context_path,
+            "rpcType": self.rpc_type,
+            "host": self.host,
+            "port": self.port
+        }
+        register_flag = False
+        for _url in self.register_uri_list:
+            res = self._request(_url, json_data)
+            if not res:
+                continue
+            else:
+                print("[SUCCESS], register uri success, register data is:{}".format(str(json_data)))
+                register_flag = True
+                break
+        if not register_flag:
+            print("[ERROR], register uri fail, app_name is:{}, host is:{}, port is:{}".format(self.app_name,
+                                                                                              self.host,
+                                                                                              self.port))
+        return register_flag
+
+    def register_metadata(self, **kwargs):
+        """
+        register path to gateway
+        path:            The path needs to be unique,  for example, your path is: /order/findById, your request prefix
+                         is: /hello, the path must be /hello/order/findById
+        register_all     Register all paths ?
+        rule_name:       Can be the same as path
+        enabled:         Whether to open, If you want to open the gateway proxy, you must fill in True
+        path_desc:       Path description, optional filling
+        register_meta_data: Need to register metadata, not for http request, fill in false
+        """
+        if not kwargs.get("register_all") and not kwargs.get("path"):
+            return False
+
+        register_all = kwargs.get("register_all", False)
+        path = kwargs.get("path", "")
+        rule_name = kwargs.get("rule_name", "")
+        enabled = kwargs.get("enabled", True)
+        path_desc = kwargs.get("path_desc", "")
+        register_meta_data = kwargs.get("register_meta_data", False)
+        if register_all:
+            path = self.context_path + "**" if self.context_path.endswith("/") else self.context_path + "/**"
+        rule_name = path if not rule_name else rule_name
+        json_data = {
+            "appName": self.app_name,
+            "contextPath": self.context_path,
+            "path": path,
+            "pathDesc": path_desc,
+            "rpcType": self.rpc_type,
+            "ruleName": rule_name,
+            "enabled": enabled,
+            "registerMetaData": register_meta_data,
+            "pluginNames": []
+
+        }
+        register_flag = False
+        for _url in self.register_meta_data_path_list:
+            res = self._request(_url, json_data)
+            if not res:
+                continue
+            else:
+                print("[SUCCESS], register metadata success, register data is:{}".format(str(json_data)))
+                register_flag = True
+                break
+        if not register_flag:
+            print("[ERROR],register metadata fail, app_name:{}, path:{}, contextPath:{}".format(self.app_name,
+                                                                                                path,
+                                                                                                self.context_path))
+        return register_flag
diff --git a/client/client/config.py b/client/client/config.py
new file mode 100644
index 0000000..46aeec5
--- /dev/null
+++ b/client/client/config.py
@@ -0,0 +1,63 @@
+# -*- 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.
+ */
+"""
+
+ALL_ENV = ("test", "pre", "prod")
+
+
+class GatewayConfig:
+    """
+    If there are multiple gateway registry servers, separated by commas,
+    for example: "servers": "10.11.12.12,10.11.12.13"
+    """
+    # Now only HTTP mode is supported
+
+    register_type = "http"
+
+    test = {
+        "servers": "xx.xx.xx.xx",
+        "port": 1001
+    }
+    pre = {
+        "servers": "xx.xx.xx.xx",
+        "port": 1001
+    }
+    prod = {
+        "servers": "xx.xx.xx.xx",
+        "port": 1001
+    }
+
+    register = {
+        "register_type": "http",
+        "servers": "xx.xx.xx.xx",
+        "props": {
+            "username": "admin",
+            "password": "123456"
+        }
+    }
+
+    # Items of configuration that need to be modified according to the project
+    uri = {
+        "app_name": "app1",                 # app name
+        "host": "127.0.0.1",                # python service host
+        "port": 8000,                       # python service port
+        "context_path": "/xxx",             # context_path
+        "environment": "test",              # environment
+        "rpc_type": register_type           # rpc type
+    }
diff --git a/client/client/exception.py b/client/client/exception.py
new file mode 100755
index 0000000..57c3add
--- /dev/null
+++ b/client/client/exception.py
@@ -0,0 +1,64 @@
+# -*- 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 GatewayProxyBaseExp(Exception):
+    def __init__(self, app_name="", path="", msg="", env=""):
+        self.app_name = app_name
+        self.path = path
+        self.msg = msg
+        self.env = env
+
+    def __str__(self):
+        return "Gateway Proxy Exception, app_name:{}, path is:({}), msg:{}".format(self.app_name, self.path, self.msg)
+
+    def __repr__(self):
+        return self.__str__()
+
+
+class EnvTypeExp(GatewayProxyBaseExp):
+    pass
+
+
+class SetUpUriExp(GatewayProxyBaseExp):
+    pass
+
+
+class SetUpRegisterExp(GatewayProxyBaseExp):
+    pass
+
+
+class GetRegisterTokenErr(GatewayProxyBaseExp):
+    pass
+
+
+class SetUpGatewayExp(GatewayProxyBaseExp):
+    pass
+
+
+class RegisterUriExp(GatewayProxyBaseExp):
+    pass
+
+
+class RegisterMetaDataExp(GatewayProxyBaseExp):
+    pass
+
+
+class RegisterAllMetaDataExp(GatewayProxyBaseExp):
+    pass
diff --git a/client/client/main.py b/client/client/main.py
new file mode 100644
index 0000000..6623e9a
--- /dev/null
+++ b/client/client/main.py
@@ -0,0 +1,19 @@
+# -*- 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.
+ */
+"""
\ No newline at end of file
diff --git a/client/client/register.py b/client/client/register.py
new file mode 100644
index 0000000..6936e46
--- /dev/null
+++ b/client/client/register.py
@@ -0,0 +1,76 @@
+# -*- 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 copy import deepcopy
+from functools import wraps
+
+from client.client.api import GatewayProxy
+from client.client.exception import (RegisterUriExp, RegisterMetaDataExp, RegisterAllMetaDataExp)
+
+
+def register_uri(func):
+    @wraps(func)
+    def _register_uri():
+        try:
+            gt = GatewayProxy()
+            gt.register_uri()
+            func()
+        except RegisterUriExp as e:
+            raise RegisterUriExp()
+        except Exception as e:
+            print("register_uri except, detail is:{}".format(str(e)))
+            raise e
+    return _register_uri
+
+
+def register_metadata(**kwargs):
+    def register_decorator(func):
+        @wraps(func)
+        def _wrapped_register_metadata():
+            try:
+                gt = GatewayProxy()
+                gt.register_metadata(**kwargs)
+                func()
+            except RegisterMetaDataExp as ee:
+                raise ee
+            except Exception as e:
+                print("register_metadata except, detail is:{}".format(str(e)))
+                raise e
+        return _wrapped_register_metadata
+    return register_decorator
+
+
+def register_all_metadata(**kwargs):
+    def register_all_metadata_decorator(func):
+        @wraps(func)
+        def _wrapped_register_all_metadata():
+            try:
+                gt = GatewayProxy()
+                _kwargs = deepcopy(kwargs)
+                _kwargs.update({"register_all": True})
+                gt.register_metadata(**_kwargs)
+                func()
+            except RegisterAllMetaDataExp as ee:
+                raise ee
+            except Exception as e:
+                print("register_all_metadata except, detail is:{}".format(str(e)))
+                raise e
+        return _wrapped_register_all_metadata
+    return register_all_metadata_decorator
diff --git a/client/setup.py b/client/setup.py
new file mode 100644
index 0000000..2564e42
--- /dev/null
+++ b/client/setup.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.
+ */
+"""
+
+from setuptools import setup
+
+setup(
+    name="gateway_proxy",                                               # project name, pip3 install gateway_proxy
+    version="0.1.0",                                                    # version
+    author="https://shenyu.apache.org",                                 # author
+    author_email="https://shenyu.apache.org/zh/community",              # email
+    url="https://github.com/mutianzero/incubator-shenyu-client-python", # project repo url
+    description="shenyu python proxy service sdk",                      # description
+    packages=["gateway_proxy"],                                         # packages name
+    install_requires=["requests>=2.25.1", "PyYAML>=5.3"],               # requires third packages
+    python_requires=">=3.6",                                            # python version condition
+    entry_points={                                                      # console scripts
+        "console_scripts": [
+            "gp-help=gateway_proxy:get_help",
+            "gp-doc=gateway_proxy:get_doc"
+        ]
+    }
+)
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 0000000..bba03bf
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,11 @@
+## python service gateway proxy use example
+
+
+### 1、Install gateway-proxy package
+pip install gateway-proxy
+
+### 2、Config, for example the following
+
+### 3、For specific usage examples, please refer to the file app.py.
+
+
diff --git a/example/app.py b/example/app.py
new file mode 100644
index 0000000..f0f52b3
--- /dev/null
+++ b/example/app.py
@@ -0,0 +1,102 @@
+# -*- 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 json
+
+from flask import Flask, jsonify, request
+
+# pip install gateway-proxy
+from gateway_proxy.config import GatewayConfig
+from gateway_proxy.api import GatewayProxy
+from gateway_proxy.register import register_uri, register_all_metadata
+
+
+app = Flask(__name__)
+
+"""
+Configure the gateway first
+"""
+GatewayConfig.register_type = "http"
+GatewayConfig.test = {
+    "servers": "172.12.21.10",
+    "port": 1001
+}
+GatewayConfig.pre = {
+    "servers": "172.12.21.11",
+    "port": 1001
+}
+GatewayConfig.prod = {
+    "servers": "172.12.21.12",
+    "port": 1001
+}
+GatewayConfig.uri = {
+        "app_name": "app1",                 # app name
+        "host": "172.24.43.8",              # python service host
+        "port": 5000,                       # python service port, this service port ois 5000
+        "context_path": "/flask_demo",       # context_path
+        "environment": "test",              # environment
+        "rpc_type": "http"                  # rpc type
+    }
+GatewayConfig.register = {
+        "register_type": "http",
+        "servers": "xx.xx.xx.xx",
+        "props": {
+            "username": "admin",
+            "password": "123456"
+        }
+    }
+
+
+
+"""
+Use example
+"""
+
+gt = GatewayProxy()
+uri = gt.register_uri()
+gt.register_metadata(register_all=True)
+
+@register_uri
+@register_all_metadata(register_all=True)
+def create_app():
+    return
+
+
+@app.route('/userinfo', methods=['POST'])
+def hello_world():
+    data = request.data.decode("utf-8")
+    res = {"code": 200, "msg": "success", "data": "hello world"}
+    return jsonify(res)
+
+
+@app.route('/hel', methods=['GET'])
+def hello_world1():
+    res = {"code": 200, "msg": "success", "data": "hello world1"}
+    return jsonify(res)
+
+
+@app.route('/hell')
+def hello_world2():
+    res = {"code": 200, "msg": "success", "data": "hello world2"}
+    return jsonify(res)
+
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=5000, debug=True)
diff --git a/example/requiremnets.txt b/example/requiremnets.txt
new file mode 100644
index 0000000..64e9ae3
--- /dev/null
+++ b/example/requiremnets.txt
@@ -0,0 +1,2 @@
+flask==2.0.3
+gateway-proxy==0.1.0
\ No newline at end of file