├── .asf.yaml ├── .github └── workflows │ └── it.yaml ├── .gitignore ├── DISCLAIMER ├── LICENSE ├── NOTICE ├── README.md ├── RELEASE-NOTES.md ├── example ├── consul │ └── nginx.conf ├── etcd │ └── nginx.conf ├── eureka │ └── nginx.conf ├── nacos │ └── nginx.conf └── zookeeper │ └── nginx.conf ├── lib └── shenyu │ └── register │ ├── balancer.lua │ ├── consul.lua │ ├── core │ ├── string.lua │ ├── struct.lua │ └── utils.lua │ ├── etcd.lua │ ├── eureka.lua │ ├── nacos.lua │ ├── zookeeper.lua │ └── zookeeper │ ├── connection.lua │ ├── zk_client.lua │ ├── zk_cluster.lua │ ├── zk_const.lua │ └── zk_proto.lua ├── rockspec ├── shenyu-nginx-1.0.0-1.rockspec └── shenyu-nginx-1.0.0-2.rockspec └── test └── it ├── case ├── etcd │ ├── conf │ │ ├── gateway.conf │ │ └── mock-shenyu.conf │ └── docker-compose.yml └── nacos │ ├── conf │ ├── gateway.conf │ └── mock-shenyu.conf │ └── docker-compose.yml ├── consumer ├── Dockerfile └── bin │ └── entrypoint.sh ├── gateway └── Dockerfile └── mock-shenyu └── Dockerfile /.asf.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | github: 19 | description: Apache ShenYu Nginx. 20 | homepage: https://shenyu.apache.org/ 21 | labels: 22 | - shenyu 23 | - nginx 24 | features: 25 | wiki: true 26 | issues: true 27 | projects: true 28 | enabled_merge_buttons: 29 | squash: true 30 | merge: false 31 | rebase: false 32 | protected_branches: 33 | main: 34 | required_status_checks: 35 | strict: true 36 | required_pull_request_reviews: 37 | dismiss_stale_reviews: true 38 | required_approving_review_count: 1 39 | notifications: 40 | commits: notifications@shenyu.apache.org 41 | issues: notifications@shenyu.apache.org 42 | pullrequests: notifications@shenyu.apache.org 43 | -------------------------------------------------------------------------------- /.github/workflows/it.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | name: IT 18 | 19 | on: 20 | pull_request: 21 | push: 22 | branches: 23 | - master 24 | tags: 25 | - 'v*' 26 | 27 | jobs: 28 | nacos: 29 | runs-on: ubuntu-latest 30 | timeout-minutes: 20 31 | steps: 32 | - uses: actions/checkout@v3 33 | with: 34 | submodules: true 35 | - name: run testcase 36 | run: | 37 | set -x 38 | docker-compose -f ./test/it/case/nacos/docker-compose.yml run -d consumer --build 39 | status=$(docker wait $(docker ps -qa --filter Name=nacos_consumer)) 40 | if [[ $status -ne 200 ]]; then 41 | docker-compose -f ./test/it/case/nacos/docker-compose.yml logs 42 | fi 43 | docker logs $(docker ps -qa --filter Name=nacos_consumer) 44 | docker-compose -f ./test/it/case/nacos/docker-compose.yml kill 45 | docker-compose -f ./test/it/case/nacos/docker-compose.yml rm -f 46 | if [[ $status -ne 200 ]]; then 47 | exit 1 48 | fi 49 | 50 | etcd: 51 | runs-on: ubuntu-latest 52 | timeout-minutes: 20 53 | steps: 54 | - uses: actions/checkout@v3 55 | with: 56 | submodules: true 57 | - name: run testcase 58 | run: | 59 | set -x 60 | docker-compose -f ./test/it/case/etcd/docker-compose.yml run -d consumer --build 61 | status=$(docker wait $(docker ps -qa --filter Name=etcd_consumer)) 62 | if [[ $status -ne 200 ]]; then 63 | docker-compose -f ./test/it/case/etcd/docker-compose.yml logs 64 | fi 65 | docker logs $(docker ps -qa --filter Name=etcd_consumer) 66 | docker-compose -f ./test/it/case/etcd/docker-compose.yml kill 67 | docker-compose -f ./test/it/case/etcd/docker-compose.yml rm -f 68 | if [[ $status -ne 200 ]]; then 69 | exit 1 70 | fi 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # idea ignore 19 | .idea/ 20 | *.ipr 21 | *.iml 22 | *.iws 23 | 24 | # temp ignore 25 | logs/ 26 | *.log 27 | *.doc 28 | *.cache 29 | *.diff 30 | *.patch 31 | *.tmp 32 | 33 | /out/ 34 | /.vscode/ 35 | -------------------------------------------------------------------------------- /DISCLAIMER: -------------------------------------------------------------------------------- 1 | Apache ShenYu is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC. 2 | Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, 3 | communications, and decision making process have stabilized in a manner consistent with other successful ASF projects. 4 | While incubation status is not necessarily a reflection of the completeness or stability of the code, 5 | it does indicate that the project has yet to be fully endorsed by the ASF. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | ======================================================================== 204 | Apache 2.0 licenses 205 | ======================================================================== 206 | 207 | The following components are provided under the Apache License. See project link for details. 208 | The text of each license is the standard MIT license. 209 | https://github.com/iryont/lua-struct struct 210 | 211 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache ShenYu 2 | Copyright 2021-2022 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Apache ShenYu Nginx Module 2 | --- 3 | 4 | This module provided SDK to watch available ShenYu instance list as upstream nodes by Service Register Center for OpenResty. 5 | 1. [ETCD](#greeting-etcd) (Supported) 6 | 2. [Nacos](#greeting-nacos) (Supported) 7 | 3. [Zookeeper](#greeting-zookeeper) (Supported) 8 | 4. [Consul](#greeting-consul) (Supported) 9 | 10 | In the cluster mode, Apache ShenYu supports the deployment of multiple ShenYu instances, which may have new instances joining or leaving at any time. 11 | Hence, Apache ShenYu introduces Service Discovery modules to help client to detect the available instances. 12 | Currently, Apache ShenYu Bootstrap already supports Apache Zookeeper, Nacos, Etcd, and consul. Client or LoadBalancer can get the available ShenYu instances by those Service register center. 13 | 14 | Here provides a completed [examples](https://github.com/apache/shenyu-nginx/tree/main/example). 15 | 16 | ======= 17 | 18 | Here is a completed [example](https://github.com/apache/shenyu-nginx/blob/main/example/etcd/nginx.conf) working with ETCD. 19 | 20 | Here is a completed [example](https://github.com/apache/shenyu-nginx/blob/main/example/nacos/nginx.conf) working with Nacos. 21 | 22 | Here is a completed [example](https://github.com/apache/shenyu-nginx/blob/main/example/consul/nginx.conf) working with Consul. 23 | 24 | Here is a completed [example](https://github.com/apache/shenyu-nginx/blob/main/example/zookeeper/nginx.conf) working with Zookeeper. 25 | 26 | ## Getting Started 27 | 28 | - Prerequisite: 29 | 1. Luarocks 30 | 2. OpenResty 31 | 32 | ### Build from source 33 | 34 | The first, clone the source from GitHub. 35 | 36 | ```shell 37 | git clone https://github.com/apache/shenyu-nginx 38 | ``` 39 | 40 | Then, build from source and install. 41 | 42 | ```shell 43 | cd shenyu-nginx 44 | luarocks make rockspec/shenyu-nginx-1.0.0-2.rockspec 45 | ``` 46 | 47 | ### Greeting ETCD 48 | 49 | Modify the Nginx configure, create and initialize the ShenYu Register to connect to the target register center. 50 | The module will fetch the all of ShenYu instances which are registered to Etcd in the same cluster. 51 | It works like Etcd client to watch(based on long polling) ShenYu instance lists. 52 | 53 | Here is an example for Etcd. 54 | 55 | ``` 56 | init_worker_by_lua_block { 57 | local register = require("shenyu.register.etcd") 58 | register.init({ 59 | balancer_type = "chash", 60 | etcd_base_url = "http://127.0.0.1:2379", 61 | }) 62 | } 63 | ``` 64 | 65 | 1. `balancer_type` specify the balancer. It has supported `chash` and `round robin`. 66 | 2. `etcd_base_url` specify the Etcd server.(Currently, authentication is not supported.) 67 | 68 | Add an `upstream block` for ShenYu and enable to update upstream servers dynamically. This case will synchronize the ShenYu instance list with register center. 69 | And then pick one up for handling the request. 70 | 71 | ``` 72 | upstream shenyu { 73 | server 0.0.0.1; -- bad 74 | 75 | balancer_by_lua_block { 76 | require("shenyu.register.etcd").pick_and_set_peer() 77 | } 78 | } 79 | ``` 80 | 81 | 82 | ### Greeting Nacos 83 | 84 | Modify the Nginx configure, create and initialize the ShenYu Register to connect to target register center. Here is an example for Nacos. 85 | 86 | ``` 87 | init_worker_by_lua_block { 88 | local register = require("shenyu.register.nacos") 89 | register.init({ 90 | shenyu_storage = ngx.shared.shenyu_storage, 91 | balancer_type = "chash", 92 | nacos_base_url = "http://127.0.0.1:8848", 93 | username = "nacos", 94 | password = "naocs", 95 | }) 96 | } 97 | ``` 98 | 99 | 1. `balancer_type` specify the balancer. It has supported `chash` and `round robin`. 100 | 2. `nacos_base_url` specify the Nacos server address. 101 | 3. `username` specify the username to log in Nacos. (it is only required when Nacos auth enable) 102 | 4. `password` specify the password to log in Nacos. 103 | 104 | Modify the `upstream` to enable to update upstream servers dynamically. This case will synchronize the ShenYu instance list with register center. 105 | And then pick one up for handling the request. 106 | 107 | ``` 108 | upstream shenyu { 109 | server 0.0.0.1; -- bad 110 | 111 | balancer_by_lua_block { 112 | require("shenyu.register.nacos").pick_and_set_peer() 113 | } 114 | } 115 | ``` 116 | 117 | ## Greeting Zookeeper 118 | 119 | Modify the Nginx configure, create and initialize the ShenYu register to connect to target register center. 120 | Listen for changes to the node via the zookeeper watch event. Here is an example of the zookeeper configuration. 121 | 122 | ```shell 123 | init_worker_by_lua_block { 124 | local register = require("shenyu.register.zookeeper") 125 | register.init({ 126 | servers = {"127.0.0.1:2181","127.0.0.1:2182"}, 127 | shenyu_storage = ngx.shared.shenyu_storage, 128 | balancer_type = "roundrobin" 129 | }); 130 | } 131 | ``` 132 | 133 | 1. `servers` zookeeper cluster address. 134 | 2. ``balancer_type`` specify the balancer. It has supported `chash` and `round robin`. 135 | 136 | Modify the upstream to enable to update upstream servers dynamically. This case will synchronize the ShenYu instance list with register center. And then pick one up for handling the request. 137 | 138 | ```shell 139 | upstream shenyu { 140 | server 0.0.0.1; 141 | balancer_by_lua_block { 142 | require("shenyu.register.zookeeper").pick_and_set_peer() 143 | } 144 | } 145 | ``` 146 | 147 | ### Greeting Consul 148 | 149 | Modify the Nginx configure, create and initialize the ShenYu register to connect to target register center. 150 | Listen for changes to the node via the consul watch event. Here is an example of the consul configuration. 151 | 152 | ```shell 153 | init_worker_by_lua_block { 154 | local register = require "shenyu.register.consul"; 155 | register.init({ 156 | uri = "http://127.0.0.1:8500", 157 | path = "/v1/catalog/service/demo", 158 | shenyu_storage = ngx.shared.shenyu_storage, 159 | balancer_type = "chash", 160 | }) 161 | } 162 | ``` 163 | 164 | 1. ``balancer_type`` specify the balancer. It has supported `chash` and `round robin`. 165 | 2. `uri` consul server address. 166 | 3. `path` path of service. 167 | 168 | Modify the upstream to enable to update upstream servers dynamically. This case will synchronize the ShenYu instance list with register center. And then pick one up for handling the request. 169 | 170 | ```shell 171 | 172 | upstream shenyu { 173 | server 0.0.0.1; 174 | balancer_by_lua_block { 175 | require("shenyu.register.consul").pick_and_set_peer() 176 | } 177 | } 178 | ``` 179 | 180 | ## Finally 181 | 182 | Finally, restart OpenResty. 183 | 184 | ```shell 185 | openresty -s reload 186 | ``` 187 | 188 | 189 | ## Contributor and Support 190 | 191 | * [How to Contributor](https://shenyu.apache.org/community/contributor-guide) 192 | * [Mailing Lists](mailto:dev@shenyu.apache.org) 193 | 194 | ## License 195 | 196 | [Apache License 2.0](https://apache.org/licenses/LICENSE-2.0) 197 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0-1 2 | 3 | ### New Features 4 | 5 | * Support for listening to Apache ShenYu node changes using Zookeeper 6 | * Support for listening to Apache ShenYu node changes using ETCD. 7 | * Support for listening to Apache ShenYu node changes using Nacos 8 | * Support for listening to Apache ShenYu node changes using Consul 9 | * Add demo code to add the above. 10 | * Add a description of the documentation for the above. -------------------------------------------------------------------------------- /example/consul/nginx.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 2; 19 | error_log /dev/stdout debug; 20 | events { 21 | worker_connections 1024; 22 | } 23 | http { 24 | default_type application/octet-stream; 25 | access_log logs/access.log; 26 | 27 | lua_shared_dict shenyu_storage 10m; 28 | init_worker_by_lua_block { 29 | local register = require "shenyu.register.consul"; 30 | register.init({ 31 | uri = "http://192.168.152.128:8500", 32 | path = "/v1/catalog/service/demo", 33 | shenyu_storage = ngx.shared.shenyu_storage, 34 | balancer_type = "chash", 35 | }) 36 | } 37 | 38 | upstream shenyu { 39 | server 0.0.0.1 down; 40 | balancer_by_lua_block { 41 | local consul = require "shenyu.register.consul"; 42 | consul.pick_and_set_peer(); 43 | } 44 | } 45 | server { 46 | listen 80; 47 | server_name localhost; 48 | default_type text/html; 49 | location = /favicon.ico { 50 | log_not_found off; 51 | access_log off; 52 | } 53 | 54 | location ~ /* { 55 | proxy_pass http://shenyu; 56 | 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/etcd/nginx.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 4; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | http { 26 | lua_shared_dict shenyu_storage 10m; 27 | 28 | init_worker_by_lua_block { 29 | local register = require("shenyu.register.etcd") 30 | register.init({ 31 | shenyu_storage = ngx.shared.shenyu_storage, 32 | balancer_type = "chash", 33 | etcd_base_url = "http://192.168.0.106:2379", 34 | }) 35 | } 36 | 37 | upstream shenyu { 38 | server 0.0.0.1; 39 | balancer_by_lua_block { 40 | require("shenyu.register.etcd").pick_and_set_peer() 41 | } 42 | } 43 | 44 | server { 45 | listen 8080; 46 | 47 | location ~ /* { 48 | proxy_pass http://shenyu; 49 | } 50 | } 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /example/eureka/nginx.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | lua_package_path "/usr/local/openresty/lualib/?.lua;;"; 19 | lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; 20 | 21 | lua_shared_dict upstream_list 10m; 22 | 23 | # Initialize for the first time 24 | init_by_lua_block { 25 | local eureka = require "eureka"; 26 | eureka.init({ 27 | upstream_list = ngx.shared.upstream_list, 28 | base_url = "http://192.168.9.252:8761", 29 | path = "/eureka/apps/demo", 30 | 31 | }); 32 | } 33 | 34 | # Periodic pull configuration 35 | init_worker_by_lua_block { 36 | local eureka = require "eureka"; 37 | local handle = nil; 38 | 39 | handle = function () 40 | # Only one worker executes the control at a time 41 | eureka.get_server_list(); 42 | ngx.timer.at(5, handle); 43 | end 44 | ngx.timer.at(5, handle); 45 | } 46 | 47 | upstream api_server { 48 | server 0.0.0.1 down; #Placeholder server 49 | 50 | balancer_by_lua_block { 51 | local balancer = require "ngx.balancer"; 52 | local eureka = require "eureka"; 53 | local tmp_upstreams = eureka.get_upstreams(); 54 | local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))]; 55 | balancer.set_current_peer(ip_port.ip, ip_port.port); 56 | } 57 | } 58 | 59 | server { 60 | listen 12000; 61 | server_name localhost; 62 | charset utf-8; 63 | location / { 64 | proxy_pass http://api_server; 65 | } 66 | } -------------------------------------------------------------------------------- /example/nacos/nginx.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 4; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | http { 26 | lua_shared_dict shenyu_storage 1m; 27 | 28 | init_worker_by_lua_block { 29 | local register = require("shenyu.register.nacos") 30 | register.init({ 31 | shenyu_storage = ngx.shared.shenyu_storage, 32 | balancer_type = "chash", 33 | nacos_base_url = "http://127.0.0.1:8848", 34 | username = "nacos", 35 | password = "nacos", 36 | }) 37 | } 38 | 39 | upstream shenyu { 40 | server 0.0.0.1; 41 | balancer_by_lua_block { 42 | require("shenyu.register.nacos").pick_and_set_peer() 43 | } 44 | } 45 | 46 | server { 47 | listen 8080; 48 | 49 | location ~ /* { 50 | proxy_pass http://shenyu; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/zookeeper/nginx.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 2; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | http { 26 | lua_shared_dict shenyu_storage 1m; 27 | 28 | # lua_package_path "$prefix/lib/?.lua;;"; 29 | 30 | init_worker_by_lua_block { 31 | local register = require("shenyu.register.zookeeper") 32 | register.init({ 33 | servers = {"127.0.0.1:2181"}, 34 | shenyu_storage = ngx.shared.shenyu_storage, 35 | balancer_type = "chash" 36 | }); 37 | } 38 | 39 | upstream shenyu { 40 | server 0.0.0.1; 41 | balancer_by_lua_block { 42 | require("shenyu.register.zookeeper").pick_and_set_peer() 43 | } 44 | } 45 | 46 | server { 47 | listen 80; 48 | 49 | location ~ /* { 50 | proxy_pass http://shenyu; 51 | } 52 | } 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/shenyu/register/balancer.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local _M = {} 18 | local str_null = string.char(0) 19 | 20 | function _M.new(balancer_type) 21 | local balancer_type = (balancer_type or "roundrobin") 22 | if balancer_type == "chash" then 23 | _M.init = function(self, server_list) 24 | local servers, nodes = {}, {} 25 | for serv, weight in pairs(server_list) do 26 | local id = string.gsub(serv, ":", str_null) 27 | servers[id] = serv 28 | nodes[id] = weight 29 | end 30 | 31 | _M.balancer = require("resty.chash"):new(nodes) 32 | _M.servers = servers 33 | end 34 | 35 | _M.reinit = function(self, server_list) 36 | local servers, nodes = {}, {} 37 | for serv, weight in pairs(server_list) do 38 | local id = string.gsub(serv, ":", str_null) 39 | 40 | servers[id] = serv 41 | nodes[id] = weight 42 | end 43 | 44 | _M.balancer:reinit(nodes) 45 | _M.servers = servers 46 | end 47 | 48 | _M.find = function(self, key) 49 | local id = _M.balancer:find(key) 50 | return _M.servers[id] 51 | end 52 | elseif balancer_type == "roundrobin" then 53 | _M.init = function(self, servers) 54 | _M.balancer = require("resty.roundrobin"):new(servers) 55 | end 56 | 57 | _M.reinit = function(self, servers) 58 | _M.balancer:reinit(servers) 59 | end 60 | 61 | _M.find = function(self, key) 62 | return _M.balancer:find() 63 | end 64 | else 65 | log(ERR, "unknown balancer_type[" .. balancer_type .. "]") 66 | return 67 | end 68 | 69 | return _M 70 | end 71 | 72 | return _M 73 | -------------------------------------------------------------------------------- /lib/shenyu/register/consul.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local http = require("resty.http") 19 | local json = require("cjson") 20 | local ngx_balancer = require("ngx.balancer") 21 | 22 | local balancer = require("shenyu.register.balancer") 23 | 24 | local ngx_timer_at = ngx.timer.at 25 | local ngx_worker_exiting = ngx.worker.exiting 26 | 27 | local log = ngx.log 28 | local ERR = ngx.ERR 29 | 30 | local _M = {} 31 | 32 | _M._VERSION="0.1" 33 | 34 | local function get_server_list() 35 | local httpc = http.new() 36 | 37 | local res, err = httpc:request_uri(_M.uri, { 38 | method = "GET", 39 | path = _M.path, 40 | 41 | }) 42 | 43 | if not res then 44 | log(ERR, "failed to request.") 45 | return nil, err 46 | end 47 | 48 | if res.status == 200 then 49 | local upstreams = {} 50 | local kvs = json.decode(res.body) 51 | 52 | for _, v in ipairs(kvs) do 53 | local instanceId = "" 54 | instanceId = instanceId .. v.ServiceAddress .. ":" .. v.ServicePort 55 | upstreams[instanceId] = 1 56 | end 57 | 58 | return upstreams, err 59 | end 60 | 61 | return nil, err 62 | end 63 | 64 | local function sync(premature) 65 | 66 | if premature or ngx_worker_exiting() then 67 | return 68 | end 69 | 70 | local storage = _M.storage 71 | 72 | local server_list = storage:get("server_list") 73 | local servers = json.decode(server_list) 74 | 75 | _M.balancer:reinit(servers) 76 | 77 | local ok, err = ngx_timer_at(2, sync) 78 | 79 | if not ok then 80 | log(ERR, "failed to start sync ", err) 81 | end 82 | 83 | end 84 | 85 | local function subscribe(premature, initialized) 86 | if premature or ngx_worker_exiting() then 87 | return 88 | end 89 | 90 | if not initialized then 91 | 92 | local server_list, err = get_server_list() 93 | 94 | if not server_list then 95 | log(ERR, "", err) 96 | goto continue 97 | end 98 | 99 | _M.balancer:init(server_list) 100 | 101 | local server_list_in_json = json.encode(server_list) 102 | _M.storage:set("server_list", server_list_in_json) 103 | 104 | initialized = true 105 | else 106 | local server_list, err = get_server_list() 107 | 108 | if not server_list then 109 | log(ERR, "", err) 110 | goto continue 111 | end 112 | 113 | _M.balancer:reinit(server_list) 114 | 115 | local server_list_in_json = json.encode(server_list) 116 | _M.storage:set("server_list", server_list_in_json) 117 | end 118 | 119 | 120 | :: continue :: 121 | local ok, err = ngx_timer_at(2, subscribe, initialized) 122 | if not ok then 123 | log(ERR, "failed to subscribe: ", err) 124 | end 125 | 126 | return 127 | 128 | 129 | end 130 | 131 | 132 | function _M:pick_and_set_peer(key) 133 | local server = _M.balancer:find(key) 134 | ngx_balancer.set_current_peer(server) 135 | end 136 | 137 | 138 | function _M.init(conf) 139 | 140 | _M.uri = conf.uri 141 | _M.path = conf.path 142 | _M.storage = conf.shenyu_storage 143 | _M.balancer = balancer.new(conf.balancer_type) 144 | if 0 == ngx.worker.id() then 145 | local ok, err = ngx_timer_at(0, subscribe) 146 | if not ok then 147 | log(ERR, "failed to start watch: ", err) 148 | return 149 | end 150 | end 151 | 152 | local ok, err = ngx_timer_at(2, sync) 153 | if not ok then 154 | log(ERR, "failed to start sync ", err) 155 | end 156 | 157 | end 158 | 159 | return _M -------------------------------------------------------------------------------- /lib/shenyu/register/core/string.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local table_insert = table.insert 18 | local _M ={} 19 | 20 | function _M.split(str, delimiter) 21 | if not str or str == "" then return {} end 22 | if not delimiter or delimiter == "" then return { str } end 23 | local result = {} 24 | for match in (str .. delimiter):gmatch("(.-)" .. delimiter) do 25 | table_insert(result, match) 26 | end 27 | return result 28 | end 29 | 30 | return _M -------------------------------------------------------------------------------- /lib/shenyu/register/core/struct.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015-2020 Iryont 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | ]] 22 | local unpack = table.unpack or _G.unpack 23 | local struct = {} 24 | 25 | function struct.pack(format, ...) 26 | local stream = {} 27 | local vars = {...} 28 | local endianness = true 29 | 30 | for i = 1, format:len() do 31 | local opt = format:sub(i, i) 32 | 33 | if opt == "<" then 34 | endianness = true 35 | elseif opt == ">" then 36 | endianness = false 37 | elseif opt:find("[bBhHiIlL]") then 38 | local n = opt:find("[hH]") and 2 or opt:find("[iI]") and 4 or opt:find("[lL]") and 8 or 1 39 | local val = tonumber(table.remove(vars, 1)) 40 | 41 | local bytes = {} 42 | for j = 1, n do 43 | table.insert(bytes, string.char(val % (2 ^ 8))) 44 | val = math.floor(val / (2 ^ 8)) 45 | end 46 | 47 | if not endianness then 48 | table.insert(stream, string.reverse(table.concat(bytes))) 49 | else 50 | table.insert(stream, table.concat(bytes)) 51 | end 52 | elseif opt:find("[fd]") then 53 | local val = tonumber(table.remove(vars, 1)) 54 | local sign = 0 55 | 56 | if val < 0 then 57 | sign = 1 58 | val = -val 59 | end 60 | 61 | local mantissa, exponent = math.frexp(val) 62 | if val == 0 then 63 | mantissa = 0 64 | exponent = 0 65 | else 66 | mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == "d") and 53 or 24) 67 | exponent = exponent + ((opt == "d") and 1022 or 126) 68 | end 69 | 70 | local bytes = {} 71 | if opt == "d" then 72 | val = mantissa 73 | for i = 1, 6 do 74 | table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) 75 | val = math.floor(val / (2 ^ 8)) 76 | end 77 | else 78 | table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8))) 79 | val = math.floor(mantissa / (2 ^ 8)) 80 | table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) 81 | val = math.floor(val / (2 ^ 8)) 82 | end 83 | 84 | table.insert(bytes, string.char(math.floor(exponent * ((opt == "d") and 16 or 128) + val) % (2 ^ 8))) 85 | val = math.floor((exponent * ((opt == "d") and 16 or 128) + val) / (2 ^ 8)) 86 | table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8))) 87 | val = math.floor((sign * 128 + val) / (2 ^ 8)) 88 | 89 | if not endianness then 90 | table.insert(stream, string.reverse(table.concat(bytes))) 91 | else 92 | table.insert(stream, table.concat(bytes)) 93 | end 94 | elseif opt == "s" then 95 | table.insert(stream, tostring(table.remove(vars, 1))) 96 | table.insert(stream, string.char(0)) 97 | elseif opt == "c" then 98 | local n = format:sub(i + 1):match("%d+") 99 | local str = tostring(table.remove(vars, 1)) 100 | local len = tonumber(n) 101 | if len <= 0 then 102 | len = str:len() 103 | end 104 | if len - str:len() > 0 then 105 | str = str .. string.rep(" ", len - str:len()) 106 | end 107 | table.insert(stream, str:sub(1, len)) 108 | i = i + n:len() 109 | end 110 | end 111 | 112 | return table.concat(stream) 113 | end 114 | 115 | function struct.unpack(format, stream, pos) 116 | local vars = {} 117 | local iterator = pos or 1 118 | local endianness = true 119 | 120 | for i = 1, format:len() do 121 | local opt = format:sub(i, i) 122 | 123 | if opt == "<" then 124 | endianness = true 125 | elseif opt == ">" then 126 | endianness = false 127 | elseif opt:find("[bBhHiIlL]") then 128 | local n = opt:find("[hH]") and 2 or opt:find("[iI]") and 4 or opt:find("[lL]") and 8 or 1 129 | local signed = opt:lower() == opt 130 | 131 | local val = 0 132 | for j = 1, n do 133 | local byte = string.byte(stream:sub(iterator, iterator)) 134 | if endianness then 135 | val = val + byte * (2 ^ ((j - 1) * 8)) 136 | else 137 | val = val + byte * (2 ^ ((n - j) * 8)) 138 | end 139 | iterator = iterator + 1 140 | end 141 | 142 | if signed and val >= 2 ^ (n * 8 - 1) then 143 | val = val - 2 ^ (n * 8) 144 | end 145 | 146 | table.insert(vars, math.floor(val)) 147 | elseif opt:find("[fd]") then 148 | local n = (opt == "d") and 8 or 4 149 | local x = stream:sub(iterator, iterator + n - 1) 150 | iterator = iterator + n 151 | 152 | if not endianness then 153 | x = string.reverse(x) 154 | end 155 | 156 | local sign = 1 157 | local mantissa = string.byte(x, (opt == "d") and 7 or 3) % ((opt == "d") and 16 or 128) 158 | for i = n - 2, 1, -1 do 159 | mantissa = mantissa * (2 ^ 8) + string.byte(x, i) 160 | end 161 | 162 | if string.byte(x, n) > 127 then 163 | sign = -1 164 | end 165 | 166 | local exponent = 167 | (string.byte(x, n) % 128) * ((opt == "d") and 16 or 2) + 168 | math.floor(string.byte(x, n - 1) / ((opt == "d") and 16 or 128)) 169 | if exponent == 0 then 170 | table.insert(vars, 0.0) 171 | else 172 | mantissa = (math.ldexp(mantissa, (opt == "d") and -52 or -23) + 1) * sign 173 | table.insert(vars, math.ldexp(mantissa, exponent - ((opt == "d") and 1023 or 127))) 174 | end 175 | elseif opt == "s" then 176 | local bytes = {} 177 | for j = iterator, stream:len() do 178 | if stream:sub(j, j) == string.char(0) or stream:sub(j) == "" then 179 | break 180 | end 181 | 182 | table.insert(bytes, stream:sub(j, j)) 183 | end 184 | 185 | local str = table.concat(bytes) 186 | iterator = iterator + str:len() + 1 187 | table.insert(vars, str) 188 | elseif opt == "c" then 189 | local n = format:sub(i + 1):match("%d+") 190 | local len = tonumber(n) 191 | if len <= 0 then 192 | len = table.remove(vars) 193 | end 194 | 195 | table.insert(vars, stream:sub(iterator, iterator + len - 1)) 196 | iterator = iterator + len 197 | i = i + n:len() 198 | end 199 | end 200 | 201 | return vars, iterator 202 | end 203 | 204 | function struct.tbunpack(vars) 205 | -- body 206 | return unpack(vars) 207 | end 208 | 209 | return struct 210 | -------------------------------------------------------------------------------- /lib/shenyu/register/core/utils.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local str = require("shenyu.register.core.string") 18 | local _M = {} 19 | 20 | --Delimited string 21 | function _M.paras_host(host, delimiter) 22 | return str.split(host, delimiter) 23 | end 24 | 25 | function _M.long_to_hex_string(long) 26 | return string.format("0x%06x", long) 27 | end 28 | 29 | -- table len 30 | function _M.table_len(args) 31 | -- body 32 | local n = 0 33 | if args then 34 | n = #args 35 | end 36 | return n 37 | end 38 | 39 | return _M 40 | -------------------------------------------------------------------------------- /lib/shenyu/register/etcd.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local _M = {} 19 | 20 | local http = require("resty.http") 21 | local json = require("cjson.safe") 22 | local ngx_balancer = require("ngx.balancer") 23 | 24 | local balancer = require("shenyu.register.balancer") 25 | 26 | local ngx = ngx 27 | 28 | local re = ngx.re.match 29 | local ngx_timer_at = ngx.timer.at 30 | local ngx_worker_exiting = ngx.worker.exiting 31 | local encode_base64 = ngx.encode_base64 32 | local decode_base64 = ngx.decode_base64 33 | 34 | local log = ngx.log 35 | local ERR = ngx.ERR 36 | local INFO = ngx.INFO 37 | 38 | _M.start_key = "/shenyu/register/instance/ " 39 | _M.end_key = "/shenyu/register/instance/~" 40 | _M.revision = -1 41 | _M.time_at = 0 42 | 43 | -- <= 3.2 /v3alpha 44 | -- == 3.3 /v3beta 45 | -- >= 3.4 /v3 46 | local function detect_etcd_version(base_url) 47 | local httpc = http.new() 48 | local res, err = httpc:request_uri(base_url .. "/version") 49 | if not res then 50 | log(ERR, "failed to get version from etcd server.", err) 51 | end 52 | 53 | local m 54 | local response = json.decode(res.body) 55 | m, err = re(response.etcdcluster, "^(\\d+)\\.(\\d+)\\.(\\d+)$") 56 | if not m then 57 | log(ERR, "failed to resolve etcd version.", err) 58 | end 59 | 60 | if tonumber(m[1]) ~= 3 then 61 | log(ERR, "support etcd v3 only.") 62 | end 63 | 64 | local ver_minor = tonumber(m[2]) 65 | if ver_minor <= 2 then 66 | return "/v3alpha" 67 | elseif ver_minor == 3 then 68 | return "/v3beta" 69 | else 70 | return "/v3" 71 | end 72 | end 73 | 74 | local function parse_base_url(base_url) 75 | local m, err = re(base_url, [=[([^\/]+):\/\/([\da-zA-Z.-]+|\[[\da-fA-F:]+\]):?(\d+)?(\/)?$]=], "jo") 76 | if not m then 77 | return nil, "failed to parse etcd base_url[" .. base_url .. "], " .. (err or "unknown") 78 | end 79 | 80 | local base_url = m[1] .. "://" .. m[2] .. ":" .. m[3] 81 | return { 82 | scheme = m[1], 83 | host = m[2], 84 | port = tonumber(m[3]), 85 | base_url = base_url, 86 | prefix = detect_etcd_version(base_url) 87 | } 88 | end 89 | 90 | local function parse_value(value) 91 | local obj = json.decode(decode_base64(value)) 92 | return obj.host .. ":" .. obj.port 93 | end 94 | 95 | local function fetch_shenyu_instances(conf) 96 | local range_request = { 97 | key = encode_base64(_M.start_key), 98 | range_end = encode_base64(_M.end_key) 99 | } 100 | 101 | local httpc = http.new() 102 | local res, err = 103 | httpc:request_uri( 104 | conf.base_url .. conf.prefix .. "/kv/range", 105 | { 106 | method = "POST", 107 | body = json.encode(range_request) 108 | } 109 | ) 110 | if not res then 111 | return nil, "failed to list shenyu instances from etcd, " .. (err or "unknown") 112 | end 113 | 114 | if res.status >= 400 then 115 | if not res.body then 116 | return nil, "failed to send range_request to etcd, " .. (err or "unknown") 117 | end 118 | return nil, "failed to send range_request to etcd, " .. res.body 119 | end 120 | 121 | local _revision = _M.revision 122 | local shenyu_instances = _M.shenyu_instances 123 | 124 | local kvs = json.decode(res.body).kvs 125 | if not kvs then 126 | return nil, "get the empty shenyu instances from etcd." 127 | end 128 | 129 | local server_list = {} 130 | for _, kv in pairs(kvs) do 131 | local ver = tonumber(kv.mod_revision) 132 | if _revision < ver then 133 | _revision = ver 134 | end 135 | 136 | local key = parse_value(kv.value) 137 | server_list[key] = 1 138 | shenyu_instances[kv.key] = key 139 | end 140 | 141 | _M.revision = _revision + 1 142 | _M.shenyu_instances = shenyu_instances 143 | 144 | local _servers = json.encode(server_list) 145 | _M.storage:set("server_list", _servers) 146 | _M.storage:set("revision", _M.revision) 147 | 148 | _M.balancer:init(server_list) 149 | return true 150 | end 151 | 152 | local function parse_watch_response(response_body) 153 | -- message WatchResponse { 154 | -- ResponseHeader header = 1; 155 | -- int64 watch_id = 2; 156 | -- bool created = 3; 157 | -- bool canceled = 4; 158 | -- int64 compact_revision = 5; 159 | 160 | -- repeated mvccpb.Event events = 11; 161 | -- } 162 | local response = json.decode(response_body) 163 | if response == nil or response.result == nil then 164 | return 165 | end 166 | local events = response.result.events 167 | if not events then 168 | return 169 | end 170 | log(INFO, "watch response: " .. response_body) 171 | 172 | local shenyu_instances = _M.shenyu_instances 173 | -- message Event { 174 | -- enum EventType { 175 | -- PUT = 0; 176 | -- DELETE = 1; 177 | -- } 178 | -- EventType type = 1; 179 | -- KeyValue kv = 2; 180 | -- KeyValue prev_kv = 3; 181 | -- } 182 | 183 | local _revision = _M.revision 184 | for _, event in pairs(events) do 185 | local kv = event.kv 186 | _revision = tonumber(kv.mod_revision) 187 | 188 | -- event.type: delete 189 | if event.type == "DELETE" then 190 | log(INFO, "remove upstream node of shenyu, key: " .. kv.key) 191 | shenyu_instances[kv.key] = nil 192 | else 193 | local updated = parse_value(kv.value) 194 | log(INFO, "update upstream node of shenyu: " .. updated) 195 | shenyu_instances[kv.key] = update 196 | end 197 | end 198 | 199 | if _M.revision < _revision then 200 | local server_list = {} 201 | for _, value in pairs(shenyu_instances) do 202 | server_list[value] = 1 203 | end 204 | log(INFO, "updated upstream nodes successful.") 205 | 206 | local _servers = json.encode(server_list) 207 | _M.storage:set("server_list", _servers) 208 | _M.storage:set("revision", _M.revision) 209 | 210 | _M.balancer:init(server_list) 211 | 212 | _M.revision = _revision + 1 213 | _M.shenyu_instances = shenyu_instances 214 | end 215 | end 216 | 217 | local function sync(premature) 218 | if premature or ngx_worker_exiting() then 219 | return 220 | end 221 | 222 | local time_at = 1 223 | local storage = _M.storage 224 | 225 | local lock = storage:get("_lock") 226 | local ver = storage:get("revision") 227 | 228 | if not lock and ver > _M.revision then 229 | local server_list = storage:get("server_list") 230 | local servers = json.decode(server_list) 231 | if _M.revision <= 1 then 232 | _M.balancer:init(servers) 233 | else 234 | _M.balancer:reinit(servers) 235 | end 236 | time_at = 0 237 | _M.revision = ver 238 | end 239 | 240 | local ok, err = ngx_timer_at(time_at, sync) 241 | if not ok then 242 | log(ERR, "failed to start sync ", err) 243 | end 244 | end 245 | 246 | local function watch(premature, watching) 247 | if premature or ngx_worker_exiting() then 248 | return 249 | end 250 | 251 | if not watching then 252 | if not _M.etcd_conf then 253 | _M.storage:set("_lock", true) 254 | 255 | local conf, err = parse_base_url(_M.etcd_base_url) 256 | if not conf then 257 | log(ERR, err) 258 | return err 259 | end 260 | _M.etcd_conf = conf 261 | end 262 | 263 | local ok, err = fetch_shenyu_instances(_M.etcd_conf) 264 | if not ok then 265 | log(ERR, err) 266 | _M.time_at = 3 267 | else 268 | watching = true 269 | _M.storage:set("_lock", false) 270 | end 271 | else 272 | local conf = _M.etcd_conf 273 | local httpc = http.new() 274 | local ok, err = 275 | httpc:connect( 276 | { 277 | scheme = conf.scheme, 278 | host = conf.host, 279 | port = tonumber(conf.port) 280 | } 281 | ) 282 | if not ok then 283 | log(ERR, "failed to connect to etcd server", err) 284 | _M.time_at = 3 285 | end 286 | -- message WatchCreateRequest { 287 | -- bytes key = 1; 288 | -- bytes range_end = 2; 289 | -- int64 start_revision = 3; 290 | -- bool progress_notify = 4; 291 | -- enum FilterType { 292 | -- NOPUT = 0; 293 | -- NODELETE = 1; 294 | -- } 295 | -- repeated FilterType filters = 5; 296 | -- bool prev_kv = 6; 297 | -- } 298 | local request = { 299 | create_request = { 300 | key = encode_base64(_M.start_key), 301 | range_end = encode_base64(_M.end_key), 302 | start_revision = _M.revision 303 | } 304 | } 305 | 306 | local res, err = 307 | httpc:request( 308 | { 309 | path = "/v3/watch", 310 | method = "POST", 311 | body = json.encode(request) 312 | } 313 | ) 314 | if not res then 315 | log(ERR, "failed to watch keys under '/shenyu/register/instance/'", err) 316 | _M.time_at = 3 317 | goto continue 318 | end 319 | 320 | local reader = res.body_reader 321 | local buffer_size = 8192 322 | 323 | _M.time_at = 0 324 | repeat 325 | local buffer, err = reader(buffer_size) 326 | if err then 327 | if err ~= "timeout" then 328 | log(ERR, err) 329 | end 330 | goto continue 331 | end 332 | 333 | if buffer then 334 | parse_watch_response(buffer) 335 | end 336 | until not buffer 337 | local ok, err = httpc:set_keepalive() 338 | if not ok then 339 | log(ERR, "failed to set keepalive: ", err) 340 | end 341 | end 342 | 343 | ::continue:: 344 | local ok, err = ngx_timer_at(_M.time_at, watch, watching) 345 | if not ok then 346 | log(ERR, "failed to start watch: ", err) 347 | end 348 | return 349 | end 350 | 351 | -- conf = { 352 | -- balance_type = "chash", 353 | -- etcd_base_url = "http://127.0.0.1:2379", 354 | -- } 355 | function _M.init(conf) 356 | _M.storage = conf.shenyu_storage 357 | _M.balancer = balancer.new(conf.balancer_type) 358 | 359 | if ngx.worker.id() == 0 then 360 | _M.shenyu_instances = {} 361 | _M.etcd_base_url = conf.etcd_base_url 362 | 363 | -- Start the etcd watcher 364 | local ok, err = ngx_timer_at(0, watch) 365 | if not ok then 366 | log(ERR, "failed to start watch: " .. err) 367 | end 368 | return 369 | end 370 | 371 | local ok, err = ngx_timer_at(2, sync) 372 | if not ok then 373 | log(ERR, "failed to start sync ", err) 374 | end 375 | end 376 | 377 | function _M.pick_and_set_peer(key) 378 | local server = _M.balancer:find(key) 379 | ngx_balancer.set_current_peer(server) 380 | end 381 | 382 | return _M 383 | -------------------------------------------------------------------------------- /lib/shenyu/register/eureka.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local _M = {} 19 | 20 | local http = require("resty.http") 21 | local json = require("cjson") 22 | 23 | 24 | local ngx = ngx 25 | 26 | local ngx_timer_at = ngx.timer.at 27 | local ngx_worker_exiting = ngx.worker.exiting 28 | 29 | 30 | local log = ngx.log 31 | local ERR = ngx.ERR 32 | local INFO = ngx.INFO 33 | 34 | 35 | function _M:get_server_list() 36 | 37 | local httpc = http:new() 38 | 39 | local res, err = httpc:request_uri(_M.base_url, { 40 | method = "GET", 41 | path = _M.path, 42 | headers = {["Accept"]="application/json"}, 43 | }) 44 | if not res then 45 | log(ERR, "failed to get server list from eureka. ", err) 46 | end 47 | 48 | local service = {} 49 | 50 | if res.status == 200 then 51 | 52 | local list_inst_resp = json.decode(res.body) 53 | local application = list_inst_resp.application 54 | for k, v in ipairs(application) do 55 | 56 | local instances = v["instance"] 57 | 58 | for i, instance in pairs(instances) do 59 | local status = instance["status"] 60 | if status == "UP" then 61 | local ipAddr = instance["ipAddr"] 62 | local port = instance["port"]["$"] 63 | log(INFO, "ipAddr: ", ipAddr) 64 | log(INFO, "port: ", port) 65 | service[i] = {ip=ipAddr, port=port} 66 | end 67 | end 68 | end 69 | end 70 | 71 | _M.storage:set("demo", json.encode(service)) 72 | 73 | 74 | end 75 | 76 | 77 | function _M:get_upstreams() 78 | local upstreams_str = _M.storage:get("demo"); 79 | local tmp_upstreams = json.decode(upstreams_str); 80 | return tmp_upstreams; 81 | end 82 | 83 | 84 | 85 | function _M.init(conf) 86 | _M.storage = conf.upstream_list 87 | _M.base_url = conf.base_url 88 | _M.path = conf.path 89 | 90 | end -------------------------------------------------------------------------------- /lib/shenyu/register/nacos.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local _M = {} 19 | 20 | local http = require("resty.http") 21 | local json = require("cjson.safe") 22 | local ngx_balancer = require("ngx.balancer") 23 | 24 | local balancer = require("shenyu.register.balancer") 25 | 26 | local ngx = ngx 27 | 28 | local ngx_timer_at = ngx.timer.at 29 | local ngx_worker_exiting = ngx.worker.exiting 30 | 31 | 32 | local log = ngx.log 33 | local ERR = ngx.ERR 34 | local INFO = ngx.INFO 35 | 36 | _M.access_token = nil 37 | 38 | local function login() 39 | local httpc = http.new() 40 | local res, err = httpc:request_uri(_M.nacos_base_url, { 41 | method = "POST", 42 | path = "/nacos/v1/auth/login", 43 | query = "username=" .. _M.username .. "&password=" .. _M.password, 44 | }) 45 | if not res then 46 | return nil, err 47 | end 48 | 49 | if res.status >= 400 then 50 | return nil, res.body 51 | end 52 | 53 | log(INFO, "login nacos in username: '" .. _M.username .. "' successfully.") 54 | return json.decode(res.body).accessToken 55 | end 56 | 57 | local function get_server_list() 58 | local httpc = http.new() 59 | local res, err = httpc:request_uri(_M.nacos_base_url, { 60 | method = "GET", 61 | path = "/nacos/v1/ns/instance/list", 62 | query = "serviceName=" .. _M.service_name 63 | .. "&groupName=" .. _M.group_name 64 | .. "&namespaceId=" .. _M.namespace 65 | .. "&clusters=" .. _M.clusters 66 | .. "&healthOnly=true" 67 | .. "accessToken=" .. _M.access_token 68 | }) 69 | if not res then 70 | log(ERR, "failed to get server list from nacos. ", err) 71 | return 72 | end 73 | 74 | if res.status == 200 then 75 | local server_list = {} 76 | local list_inst_resp = json.decode(res.body) 77 | 78 | local hosts = list_inst_resp.hosts 79 | if not hosts then 80 | return {}, 0, 0 81 | end 82 | 83 | for _, inst in pairs(hosts) do 84 | local key = inst.ip .. ":" .. inst.port 85 | server_list[key] = inst.weight 86 | end 87 | 88 | return server_list, list_inst_resp.lastRefTime, #hosts 89 | end 90 | log(ERR, res.body) 91 | return 92 | end 93 | 94 | local function subscribe(premature, initialized) 95 | if premature or ngx_worker_exiting() then 96 | return 97 | end 98 | 99 | if not initialized then 100 | local token, err = login() 101 | if not token then 102 | log(ERR, err) 103 | goto continue 104 | end 105 | _M.access_token = token 106 | 107 | local server_list, revision, servers_length = get_server_list() 108 | if not server_list or servers_length == 0 then 109 | goto continue 110 | end 111 | 112 | _M.balancer:init(server_list) 113 | _M.revision = revision 114 | _M.servers_length = servers_length 115 | 116 | local server_list_in_json = json.encode(server_list) 117 | log(INFO, "initialize upstream: " .. server_list_in_json .. " , revision: " .. revision) 118 | 119 | _M.storage:set("server_list", server_list_in_json) 120 | _M.storage:set("revision", revision) 121 | 122 | initialized = true 123 | else 124 | local server_list, revision, servers_length = get_server_list() 125 | if not server_list or servers_length == 0 then 126 | goto continue 127 | end 128 | 129 | local updated = true 130 | if _M.servers_length == servers_length then 131 | local services = _M.server_list 132 | for srv, weight in pairs(server_list) do 133 | if services[srv] ~= weight then 134 | break 135 | end 136 | end 137 | updated = false 138 | end 139 | 140 | if not updated then 141 | goto continue 142 | end 143 | 144 | _M.balancer:reinit(server_list) 145 | _M.revision = revision 146 | _M.server_list = server_list 147 | 148 | local server_list_in_json = json.encode(server_list) 149 | log(INFO, "update upstream: " .. server_list_in_json .. " , revision: " .. revision) 150 | 151 | _M.storage:set("server_list", server_list_in_json) 152 | _M.storage:set("revision", revision) 153 | _M.servers_length = servers_length 154 | end 155 | 156 | :: continue :: 157 | local ok, err = ngx_timer_at(2, subscribe, initialized) 158 | if not ok then 159 | log(ERR, "failed to subscribe: ", err) 160 | end 161 | return 162 | end 163 | 164 | local function sync(premature) 165 | if premature or ngx_worker_exiting() then 166 | return 167 | end 168 | 169 | local storage = _M.storage 170 | local ver = storage:get("revision") 171 | 172 | if ver > _M.revision then 173 | local server_list = storage:get("server_list") 174 | local servers = json.decode(server_list) 175 | if _M.revision < 1 then 176 | _M.balancer:init(servers) 177 | log(INFO, "initialize upstream in workers, upstream: " .. server_list) 178 | else 179 | _M.balancer:reinit(servers) 180 | log(INFO, "update upstream in workers, upstream: " .. server_list) 181 | end 182 | _M.revision = ver 183 | end 184 | 185 | local ok, err = ngx_timer_at(1, sync) 186 | if not ok then 187 | log(ERR, "failed to start sync: ", err) 188 | end 189 | end 190 | 191 | -- conf = { 192 | -- balance_type = "chash", 193 | -- nacos_base_url = "http://127.0.0.1:8848", 194 | -- username = "nacos", 195 | -- password = "nacos", 196 | -- namespace = "", 197 | -- service_name = "", 198 | -- group_name = "", 199 | -- clusters = "", 200 | -- } 201 | function _M.init(conf) 202 | _M.storage = conf.shenyu_storage 203 | _M.balancer = balancer.new(conf.balancer_type) 204 | 205 | _M.revision = 0 206 | 207 | if ngx.worker.id() == 0 then 208 | _M.nacos_base_url = conf.nacos_base_url 209 | _M.username = conf.username 210 | _M.password = conf.password 211 | _M.namespace = conf.namespace 212 | _M.group_name = conf.group_name 213 | _M.service_name = conf.service_name 214 | _M.clusters = conf.clusters 215 | 216 | if not conf.clusters then 217 | _M.clusters = "" 218 | end 219 | if not conf.namespace then 220 | _M.namespace = "" 221 | end 222 | if not conf.group_name then 223 | _M.group_name = "DEFAULT_GROUP" 224 | end 225 | if not conf.service_name then 226 | _M.service_name = "shenyu-instances" 227 | end 228 | 229 | _M.server_list = {} 230 | _M.storage:set("revision", 0) 231 | 232 | -- subscribed by polling, privileged 233 | local ok, err = ngx_timer_at(0, subscribe) 234 | if not ok then 235 | log(ERR, "failed to start watch: " .. err) 236 | end 237 | return 238 | end 239 | 240 | -- synchronize server_list from privileged processor to workers 241 | local ok, err = ngx_timer_at(2, sync) 242 | if not ok then 243 | log(ERR, "failed to start sync ", err) 244 | end 245 | end 246 | 247 | function _M.pick_and_set_peer(key) 248 | local server = _M.balancer:find(key) 249 | ngx_balancer.set_current_peer(server) 250 | end 251 | 252 | return _M 253 | -------------------------------------------------------------------------------- /lib/shenyu/register/zookeeper.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local zk_cluster = require("shenyu.register.zookeeper.zk_cluster") 18 | local const = require("shenyu.register.zookeeper.zk_const") 19 | local balancer = require("shenyu.register.balancer") 20 | local ngx_balancer = require("ngx.balancer") 21 | local ngx_timer_at = ngx.timer.at 22 | local xpcall = xpcall 23 | local ngx_log = ngx.log 24 | local math = string.match 25 | local zc 26 | local _M = { 27 | isload = 0, 28 | nodes = {} 29 | } 30 | 31 | local function watch_data(data) 32 | ---@type table 33 | local server_lists = {} 34 | -- body 35 | for index, value in ipairs(data) do 36 | if math(value, ":") then 37 | server_lists[value] = 1 38 | end 39 | end 40 | ---@type table 41 | local s_nodes = _M.nodes 42 | for host, index in pairs(server_lists) do 43 | if not s_nodes[host] then 44 | ngx_log(ngx.INFO, "add shenyu server:" .. host) 45 | end 46 | end 47 | for host, index in pairs(s_nodes) do 48 | if not server_lists[host] then 49 | ngx_log(ngx.INFO, "remove shenyu server:" .. host) 50 | end 51 | end 52 | if (_M.isload > 1) then 53 | _M.balancer:reinit(server_lists) 54 | else 55 | _M.balancer:init(server_lists) 56 | end 57 | _M.isload = _M.isload + 1 58 | _M.nodes = server_lists 59 | end 60 | 61 | local function watch(premature, path) 62 | local ok, err = zc:connect() 63 | if ok then 64 | ok, err = 65 | xpcall( 66 | zc:add_watch(path, watch_data), 67 | function(err) 68 | ngx_log(ngx.ERR, "zookeeper start watch error..." .. tostring(err)) 69 | end 70 | ) 71 | end 72 | return ok, err 73 | end 74 | 75 | function _M.init(config) 76 | _M.storage = config.shenyu_storage 77 | _M.balancer = balancer.new(config.balancer_type) 78 | zc = zk_cluster:new(config) 79 | if ngx.worker.id() == 0 then 80 | -- Start the zookeeper watcher 81 | local ok, err = ngx_timer_at(2, watch, const.ZK_WATCH_PATH) 82 | if not ok then 83 | ngx_log(ngx.ERR, "failed to start watch: " .. err) 84 | end 85 | return 86 | end 87 | end 88 | 89 | function _M.pick_and_set_peer(key) 90 | local server = _M.balancer:find(key) 91 | if not server then 92 | ngx_log(ngx.ERR, "not find shenyu server..") 93 | return 94 | end 95 | ngx_balancer.set_current_peer(server) 96 | end 97 | return _M 98 | -------------------------------------------------------------------------------- /lib/shenyu/register/zookeeper/connection.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local proto = require("shenyu.register.zookeeper.zk_proto") 18 | local struct = require("shenyu.register.core.struct") 19 | local tcp = ngx.socket.tcp 20 | local pack = struct.pack 21 | local unpack = struct.unpack 22 | local tbunpack = struct.tbunpack 23 | local ngx_log = ngx.log 24 | local _timeout = 60 * 1000 25 | local _M = {} 26 | local mt = { __index = _M } 27 | 28 | function _M.new(self) 29 | local sock, err = tcp() 30 | if not tcp then 31 | return nil, err 32 | end 33 | return setmetatable({ sock = sock, timeout = _timeout }, mt) 34 | end 35 | 36 | function _M.connect(self, ip, port) 37 | -- body 38 | local sock = self.sock 39 | if not sock then 40 | return nil, "not initialized tcp." 41 | end 42 | local ok, err = sock:connect(ip, port) 43 | if not ok then 44 | ngx_log(ngx.DEBUG, "connect host:" .. ip .. err) 45 | return ok, err 46 | end 47 | return ok, nil 48 | end 49 | 50 | function _M.write(self, req) 51 | -- body 52 | local sock = self.sock 53 | if not sock then 54 | return nil, "not initialized tpc." 55 | end 56 | return sock:send(req) 57 | end 58 | 59 | function _M.read(self, len) 60 | -- body 61 | local sock = self.sock 62 | if not sock then 63 | return nil, "not initialized tpc." 64 | end 65 | return sock:receive(len) 66 | end 67 | 68 | function _M.read_len(self) 69 | local b, err = self:read(4) 70 | if not b then 71 | return nil, "error" 72 | end 73 | local len = tbunpack(unpack(">i", b)) 74 | return len 75 | end 76 | 77 | function _M.read_header(self) 78 | local len = self:read_len() 79 | local b, err = self:read(len) 80 | if not b then 81 | return nil, "error" 82 | end 83 | local h, end_index = proto.reply_header:unpack(b, 1) 84 | if not h then 85 | return nil, nil, 0 86 | end 87 | return h, b, end_index 88 | end 89 | 90 | function _M.close(self) 91 | -- body 92 | local sock = self.sock 93 | if not sock then 94 | return nil, "not initialized tpc." 95 | end 96 | sock.close() 97 | end 98 | 99 | function _M.set_timeout(self, timeout) 100 | -- body 101 | local sock = self.sock 102 | if not sock then 103 | return nil, "not initialized" 104 | end 105 | sock:settimeout(timeout) 106 | end 107 | 108 | function _M.set_keepalive(self, ...) 109 | local sock = self.sock 110 | if not sock then 111 | return nil, "not initialized" 112 | end 113 | 114 | return sock:setkeepalive(...) 115 | end 116 | 117 | function _M.set_timeouts(self, connect_timeout, send_timeout, read_timeout) 118 | local sock = self.sock 119 | if not sock then 120 | return nil, "not initialized" 121 | end 122 | 123 | sock:settimeouts(connect_timeout, send_timeout, read_timeout) 124 | end 125 | 126 | return _M 127 | -------------------------------------------------------------------------------- /lib/shenyu/register/zookeeper/zk_client.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local util = require("shenyu.register.core.utils") 18 | local const = require("shenyu.register.zookeeper.zk_const") 19 | local proto = require("shenyu.register.zookeeper.zk_proto") 20 | local connection = require("shenyu.register.zookeeper.connection") 21 | local ngx_log = ngx.log 22 | local now = ngx.now 23 | local exiting = ngx.worker.exiting 24 | local sleep = ngx.sleep 25 | local strlen = string.len 26 | local _timeout = 60 * 1000 27 | local _M = { 28 | } 29 | local mt = { __index = _M } 30 | 31 | function _M.new(self) 32 | --- @type table 33 | local conn_, err = connection:new() 34 | if not conn_ then 35 | return nil, "initialized connection error" .. err 36 | end 37 | conn_:set_timeout(_timeout) 38 | conn_:set_keepalive() 39 | return setmetatable({ conn = conn_, children_listener = {} }, mt) 40 | end 41 | 42 | function _M.connect(self, host) 43 | -- body 44 | local conn = self.conn 45 | ---@type table 46 | local iptables = util.paras_host(host, ":") 47 | local ip = iptables[1] 48 | local port = iptables[2] 49 | local byt = conn:connect(ip, port) 50 | if not byt then 51 | return nil, "connection error" .. host 52 | end 53 | local bytes = proto:serialize(proto.request_header, proto.connect_request) 54 | local b, err = conn:write(bytes) 55 | if not b then 56 | return nil, "connection error " .. ip + ":" .. port 57 | end 58 | local len = conn:read_len() 59 | if not len then 60 | return nil, "error" 61 | end 62 | local bytes = conn:read(len) 63 | if not bytes then 64 | return nil, "connection read error" 65 | end 66 | local rsp = proto.connect_response:unpack(bytes, 1) 67 | if not rsp then 68 | return nil, "read connection response error" 69 | end 70 | self.xid = 0 71 | local t = rsp.timeout 72 | self.session_timeout = rsp.timeout 73 | self.ping_time = (t / 3) / 1000 74 | self.host = host 75 | self.session_id = rsp.session_id 76 | local tostring = "proto_ver:" .. rsp.proto_ver .. "," .. "timeout:" .. rsp.timeout .. "," .. "session_id:" .. util.long_to_hex_string(rsp.session_id) 77 | ngx_log(ngx.INFO, tostring) 78 | return true, nil 79 | end 80 | 81 | function _M.get_children(self, path) 82 | return self:_get_children(path, 0) 83 | end 84 | 85 | function _M._get_children(self, path, is_watch) 86 | local conn = self.conn 87 | if not conn then 88 | return nil, "not initialized connection" 89 | end 90 | local xid = self.xid + 1 91 | local h = proto.request_header 92 | h.xid = xid 93 | h.type = const.ZOO_GET_CHILDREN 94 | local r = proto.get_children_request 95 | r.path = path 96 | r.watch = is_watch 97 | local req = proto:serialize(h, r) 98 | local bytes, err = conn:write(req) 99 | if not bytes then 100 | return bytes, "write bytes error" 101 | end 102 | -- If other data is received, it means that the data of the _get_children command has not been received 103 | :: continue :: 104 | local rsp_header, bytes, end_index = conn:read_header() 105 | if not rsp_header then 106 | return nil, "read headler error" 107 | end 108 | if rsp_header.err ~= 0 then 109 | ngx_log(ngx.ERR, "zookeeper remote error: " .. const.get_err_msg(rsp_header.err) .. "," .. path) 110 | return nil, const.get_err_msg(rsp_header.err) 111 | end 112 | if strlen(bytes) > 16 and rsp_header.xid > 0 then 113 | self.xid = rsp_header.xid + 1 114 | local get_children_response = proto.get_children_response:unpack(bytes, end_index) 115 | return { 116 | xid = rsp_header.xid, 117 | zxid = rsp_header.zxid, 118 | path = get_children_response.paths 119 | } 120 | end 121 | if rsp_header.xid == const.XID_PING then 122 | goto 123 | continue 124 | end 125 | return nil, "get_children error" 126 | end 127 | 128 | function _M.add_watch(self, path, listener) 129 | -- body 130 | local d, e = self:_get_children(path, 1) 131 | if not d then 132 | return d, e 133 | end 134 | self.watch = true 135 | if not self.children_listener[path] then 136 | self.children_listener[path] = listener 137 | end 138 | return d, nil 139 | end 140 | 141 | local function watch_event(self, event) 142 | if not event then 143 | return 144 | end 145 | local type = event.type 146 | local path = event.paths[1] 147 | if type == const.WATCH_NODE_CHILDREN_CHANGE 148 | or type == const.WATCH_NODE_CREATED 149 | or type == const.WATCH_NODE_DELETED then 150 | local listener = self.children_listener[path] 151 | if listener then 152 | local d, e = self:add_watch(path, listener) 153 | if d then 154 | listener(d.path) 155 | end 156 | end 157 | end 158 | end 159 | 160 | local function reply_read(self) 161 | local conn = self.conn 162 | local h = proto.request_header 163 | h.xid = const.XID_PING 164 | h.type = const.ZOO_PING_OP 165 | local req = proto:serialize(h, proto.ping_request) 166 | local ok, err = conn:write(req) 167 | if ok then 168 | local h, bytes, end_start = conn:read_header() 169 | if h.xid == const.XID_PING then 170 | ngx_log( 171 | ngx.DEBUG, 172 | "Got ping zookeeper response host:" .. 173 | self.host .. " for sessionId:" .. util.long_to_hex_string(self.session_id) 174 | ) 175 | elseif h.xid == const.XID_WATCH_EVENT then 176 | --decoding 177 | local data = proto.watch_event:unpack(bytes, end_start) 178 | watch_event(self, data) 179 | end 180 | end 181 | return ok, err 182 | end 183 | 184 | function _M.watch_receive(self) 185 | local last_time = 0 186 | while true do 187 | if exiting() then 188 | self.conn.close() 189 | return true 190 | end 191 | local can_ping = now() - last_time > self.ping_time 192 | if can_ping then 193 | local ok, err = reply_read(self) 194 | if err then 195 | return nil, err 196 | end 197 | last_time = now() 198 | end 199 | sleep(0.2) 200 | end 201 | end 202 | 203 | return _M 204 | -------------------------------------------------------------------------------- /lib/shenyu/register/zookeeper/zk_cluster.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations un 16 | -- 17 | local zkclient = require("shenyu.register.zookeeper.zk_client") 18 | local ngx_log = ngx.log 19 | local ipairs = ipairs 20 | local _M = {} 21 | local mt = {__index = _M} 22 | 23 | function _M.new(self, zk_config) 24 | -- body 25 | return setmetatable({servers = zk_config.servers}, mt) 26 | end 27 | 28 | function _M.connect(self) 29 | local servers = self.servers 30 | if not servers then 31 | return nil, "servers is null" 32 | end 33 | -- initialize 34 | ---@type 35 | local client, err = zkclient:new() 36 | if not client then 37 | ngx_log(ngx.ERR, "Failed to initialize zk Client" .. err) 38 | return nil, err 39 | end 40 | for _, _host in ipairs(servers) do 41 | ngx_log(ngx.INFO, "try to connect to zookeeper host : " .. _host) 42 | local ok, err = client:connect(_host) 43 | if not ok then 44 | ngx_log(ngx.INFO, "Failed to connect to zookeeper host : " .. _host .. err) 45 | else 46 | ngx_log(ngx.INFO, "Successful connection to zookeeper host : " .. _host) 47 | self.client = client 48 | return client 49 | end 50 | end 51 | ngx_log(ngx.ERR, "Failed to connect to zookeeper") 52 | return nil 53 | end 54 | 55 | function _M.get_children(self, path) 56 | local client = self.client 57 | if not client then 58 | ngx_log(ngx.ERR, "conn not initialized") 59 | end 60 | local data, error = client:get_children(path) 61 | if not data then 62 | return nil, error 63 | end 64 | return data, nil 65 | end 66 | 67 | local function _watch_receive(self) 68 | local client = self.client 69 | if not client then 70 | ngx_log(ngx.ERR, "conn not initialized") 71 | end 72 | return client:watch_receive() 73 | end 74 | 75 | function _M.add_watch(self, path, listener) 76 | local client = self.client 77 | if not client then 78 | ngx_log(ngx.ERR, "conn not initialized") 79 | end 80 | local data, err = client:add_watch(path,listener) 81 | if data then 82 | listener(data.path) 83 | return _watch_receive(self) 84 | end 85 | return data, err 86 | end 87 | 88 | return _M 89 | -------------------------------------------------------------------------------- /lib/shenyu/register/zookeeper/zk_const.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"), you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local _M = {} 19 | 20 | -- XID 21 | _M.XID_WATCH_EVENT = -1 22 | _M.XID_PING = -2 23 | _M.XID_SET_WATCHES = -8 24 | --op code 25 | _M.ZOO_GET_CHILDREN = 8 26 | _M.ZOO_PING_OP = 11 27 | _M.ZOO_GET_CHILDREN2 = 12 28 | _M.ZOO_SET_WATCHES = 101 29 | _M.ZOO_ADD_WATCH = 106 30 | --watch type 31 | _M.WATCH_NONE = -1 32 | _M.WATCH_NODE_CREATED = 1 33 | _M.WATCH_NODE_DELETED = 2 34 | _M.WATCH_NODE_DATA_CHANGE = 3 35 | _M.WATCH_NODE_CHILDREN_CHANGE = 4 36 | _M.WATCH_DATA_WATCH_REMOVE = 5 37 | _M.WATCH_CHILD_WATCH_REMOVE = 6 38 | --cus const. 39 | _M.ZK_WATCH_PATH = "/shenyu/register/instance"; 40 | --Definition of error codes. 41 | local error_code = { 42 | "0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-100", 43 | "-101", "-102", "-103", "-108", "-110", "-111", "-112", 44 | "-113", "-114", "-115", "-118", "-119" } 45 | --err message. 46 | local error_msg = { 47 | err0 = "Ok", 48 | err1 = "System error", 49 | err2 = "Runtime inconsistency", 50 | err3 = "Data inconsistency", 51 | err4 = "Connection loss", 52 | err5 = "Marshalling error", 53 | err6 = "Operation is unimplemented", 54 | err7 = "Operation timeout", 55 | err8 = "Invalid arguments", 56 | err9 = "API errors.", 57 | err101 = "Node does not exist", 58 | err102 = "Not authenticated", 59 | err103 = "Version conflict", 60 | err108 = "Ephemeral nodes may not have children", 61 | err110 = "The node already exists", 62 | err111 = "The node has children", 63 | err112 = "The session has been expired by the server", 64 | err113 = "Invalid callback specified", 65 | err114 = "Invalid ACL specified", 66 | err115 = "Client authentication failed", 67 | err118 = "Session moved to another server, so operation is ignored", 68 | err119 = "State-changing request is passed to read-only server", 69 | } 70 | for i = 1, #error_code do 71 | local cmd = "err" .. (error_code[i] * -1) 72 | _M[cmd] = error_msg.cmd 73 | end 74 | 75 | function _M.get_err_msg(code) 76 | if not code then 77 | return "unknown" 78 | end 79 | return error_msg["err" .. (code * -1)] 80 | end 81 | 82 | return _M -------------------------------------------------------------------------------- /lib/shenyu/register/zookeeper/zk_proto.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an"AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local struct = require("shenyu.register.core.struct") 18 | local pack = struct.pack 19 | local unpack = struct.unpack 20 | local tbunpack = struct.tbunpack 21 | local strbyte = string.byte 22 | local strlen = string.len 23 | local strsub = string.sub 24 | 25 | local _M = {} 26 | 27 | local _base = { 28 | new = function(self, o) 29 | o = o or {} 30 | setmetatable(o, self) 31 | return o 32 | end 33 | } 34 | 35 | local _request_header = 36 | _base:new { 37 | xid = 0, 38 | type = 0, 39 | pack = function(self) 40 | return pack(">II", self.xid, self.type) 41 | end 42 | } 43 | 44 | local _get_children_request = 45 | _base:new { 46 | path = "", 47 | watch = 0, 48 | pack = function(self) 49 | local path_len = strlen(self.path) 50 | return pack(">ic" .. path_len .. "b", path_len, self.path, strbyte(self.watch)) 51 | end 52 | } 53 | 54 | local _ping_request = 55 | _base:new { 56 | pack = function(self) 57 | local stream = {} 58 | return table.concat(stream) 59 | end 60 | } 61 | 62 | local _connect_request = 63 | _base:new { 64 | protocol_version = 0, 65 | last_zxid_seen = 0, 66 | timeout = 0, 67 | session_id = 0, 68 | password = "", 69 | pack = function(self) 70 | return pack(">lilic16", 0, 0, 0, 0, "") 71 | end 72 | } 73 | 74 | function _M.serialize(self, h, request) 75 | local h_bytes = h:pack() 76 | local r_bytes = request:pack() 77 | local len = h_bytes:len() + r_bytes:len() 78 | local len_bytes = pack(">i", len) 79 | return len_bytes .. h_bytes .. r_bytes 80 | end 81 | 82 | _M.get_children_request = _get_children_request 83 | _M.request_header = _request_header 84 | _M.ping_request = _ping_request 85 | _M.connect_request = _connect_request 86 | 87 | --basics of response. 88 | local _reply_header = 89 | _base:new { 90 | xid = 0, -- int 91 | zxid = 0, -- long 92 | err = 0, -- int 93 | unpack = function(self, bytes, start_index) 94 | local vars, end_index = unpack(">ili", bytes, start_index) 95 | self.xid, self.zxid, self.err = tbunpack(vars) 96 | return self, end_index 97 | end 98 | } 99 | 100 | local _connect_response = 101 | _base:new { 102 | proto_ver = 0, 103 | timeout = 0, 104 | session_id = 0, 105 | password = "", 106 | unpack = function(self, bytes, start_index) 107 | local vars, end_index = unpack(">iilS", bytes, start_index) 108 | self.proto_ver, self.timeout, self.session_id, self.password = tbunpack(vars) 109 | return self, end_index 110 | end 111 | } 112 | 113 | local function unpack_strings(str) 114 | local size = strlen(str) 115 | local pos = 0 116 | local str_set = {} 117 | local index = 1 118 | while size > pos do 119 | local vars = unpack(">i", strsub(str, 1 + pos, 4 + pos)) 120 | local len = tbunpack(vars) 121 | vars = unpack(">c" .. len, strsub(str, 5 + pos, 5 + pos + len - 1)) 122 | local s = tbunpack(vars) 123 | str_set[index] = s 124 | index = index + 1 125 | pos = pos + len + 4 126 | end 127 | return str_set 128 | end 129 | 130 | local _get_children_response = 131 | _base:new { 132 | paths = {}, 133 | unpack = function(self, bytes, start_index) 134 | self.paths = unpack_strings(strsub(bytes, 21)) 135 | return self 136 | end 137 | } 138 | 139 | local _watch_event = 140 | _base:new { 141 | type = 0, 142 | state = 0, 143 | paths = {}, 144 | unpack = function(self, bytes, start_index) 145 | local vars = unpack(">ii", bytes, start_index) 146 | self.type, self.state = tbunpack(vars) 147 | self.paths = unpack_strings(strsub(bytes, 25)) 148 | return self 149 | end 150 | } 151 | 152 | _M.reply_header = _reply_header 153 | _M.connect_response = _connect_response 154 | _M.get_children_response = _get_children_response 155 | _M.watch_event = _watch_event 156 | 157 | return _M 158 | -------------------------------------------------------------------------------- /rockspec/shenyu-nginx-1.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | package = "shenyu-nginx" 19 | version = "1.0.0-1" 20 | source = { 21 | url = "https://github.com/apache/shenyu-nginx", 22 | branch = "main", 23 | } 24 | 25 | description = { 26 | summary = "Discovery Apache Shenyu servers for Nginx", 27 | homepage = "https://github.com/apache/shenyu-nginx", 28 | license = "Apache License 2.0" 29 | } 30 | 31 | dependencies = { 32 | "lua-resty-balancer >= 0.04", 33 | "lua-resty-http >= 0.15", 34 | "lua-cjson = 2.1.0.6-1", 35 | "stringy = 0.7-0", 36 | } 37 | 38 | build = { 39 | type = "builtin", 40 | modules = { 41 | ["shenyu.register.etcd"] = "lib/shenyu/register/etcd.lua", 42 | ["shenyu.register.nacos"] = "lib/shenyu/register/nacos.lua", 43 | ["shenyu.register.balancer"] = "lib/shenyu/register/balancer.lua", 44 | ["shenyu.register.consul"] = "lib/shenyu/register/consul.lua", 45 | ["shenyu.register.zookeeper"] = "lib/shenyu/register/zookeeper.lua", 46 | ["shenyu.register.zookeeper.connection"] = "lib/shenyu/register/zookeeper/connection.lua", 47 | ["shenyu.register.zookeeper.zk_client"] = "lib/shenyu/register/zookeeper/zk_client.lua", 48 | ["shenyu.register.zookeeper.zk_cluster"] = "lib/shenyu/register/zookeeper/zk_cluster.lua", 49 | ["shenyu.register.zookeeper.zk_const"] = "lib/shenyu/register/zookeeper/zk_const.lua", 50 | ["shenyu.register.zookeeper.zk_proto"] = "lib/shenyu/register/zookeeper/zk_proto.lua", 51 | ["shenyu.register.core.string"] = "lib/shenyu/register/core/string.lua", 52 | ["shenyu.register.core.struct"] = "lib/shenyu/register/core/struct.lua", 53 | ["shenyu.register.core.utils"] = "lib/shenyu/register/core/utils.lua", 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rockspec/shenyu-nginx-1.0.0-2.rockspec: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | package = "shenyu-nginx" 19 | version = "1.0.0-2" 20 | source = { 21 | url = "https://github.com/apache/shenyu-nginx", 22 | branch = "main", 23 | } 24 | 25 | description = { 26 | summary = "Discovery Apache Shenyu servers for Nginx", 27 | homepage = "https://github.com/apache/shenyu-nginx", 28 | license = "Apache License 2.0" 29 | } 30 | 31 | dependencies = { 32 | "lua-resty-balancer >= 0.04", 33 | "lua-resty-http >= 0.15", 34 | "lua-cjson = 2.1.0.6-1", 35 | "stringy = 0.7-0", 36 | } 37 | 38 | build = { 39 | type = "builtin", 40 | modules = { 41 | ["shenyu.register.etcd"] = "lib/shenyu/register/etcd.lua", 42 | ["shenyu.register.nacos"] = "lib/shenyu/register/nacos.lua", 43 | ["shenyu.register.balancer"] = "lib/shenyu/register/balancer.lua", 44 | ["shenyu.register.consul"] = "lib/shenyu/register/consul.lua", 45 | ["shenyu.register.zookeeper"] = "lib/shenyu/register/zookeeper.lua", 46 | ["shenyu.register.zookeeper.connection"] = "lib/shenyu/register/zookeeper/connection.lua", 47 | ["shenyu.register.zookeeper.zk_client"] = "lib/shenyu/register/zookeeper/zk_client.lua", 48 | ["shenyu.register.zookeeper.zk_cluster"] = "lib/shenyu/register/zookeeper/zk_cluster.lua", 49 | ["shenyu.register.zookeeper.zk_const"] = "lib/shenyu/register/zookeeper/zk_const.lua", 50 | ["shenyu.register.zookeeper.zk_proto"] = "lib/shenyu/register/zookeeper/zk_proto.lua", 51 | ["shenyu.register.core.string"] = "lib/shenyu/register/core/string.lua", 52 | ["shenyu.register.core.struct"] = "lib/shenyu/register/core/struct.lua", 53 | ["shenyu.register.core.utils"] = "lib/shenyu/register/core/utils.lua", 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/it/case/etcd/conf/gateway.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 4; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | 26 | env ETCD_SERVER_URL; 27 | 28 | http { 29 | lua_shared_dict shenyu_storage 1m; 30 | 31 | init_worker_by_lua_block { 32 | local register = require("shenyu.register.etcd") 33 | register.init({ 34 | shenyu_storage = ngx.shared.shenyu_storage, 35 | balancer_type = "roundrobin", 36 | etcd_base_url = os.getenv("ETCD_SERVER_URL"), 37 | }) 38 | } 39 | 40 | upstream shenyu { 41 | server 0.0.0.1; 42 | balancer_by_lua_block { 43 | require("shenyu.register.etcd").pick_and_set_peer() 44 | } 45 | } 46 | 47 | server { 48 | listen 8080; 49 | 50 | location ~ /* { 51 | proxy_pass http://shenyu; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/it/case/etcd/conf/mock-shenyu.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 1; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | 26 | env APP_NAME; 27 | env ETCD_SERVER_URL; 28 | 29 | http { 30 | server { 31 | listen 9090; 32 | location ~ /get { 33 | content_by_lua_block { 34 | ngx.say(ngx.var.server_addr) 35 | } 36 | } 37 | 38 | location = /register { 39 | content_by_lua_block { 40 | local json = require("cjson.safe") 41 | local httpc = require("resty.http").new() 42 | local encode = ngx.encode_base64 43 | httpc:request_uri(os.getenv("ETCD_SERVER_URL") .. "/v3/kv/put", { 44 | method = "POST", 45 | body = json.encode({ 46 | key = encode("/shenyu/register/instance/" .. ngx.var.server_addr .. ":9090"), 47 | value = encode(json.encode({ 48 | host = ngx.var.server_addr, 49 | port = 9090, 50 | appName = os.getenv("APP_NAME"), 51 | })), 52 | }), 53 | }) 54 | ngx.say("register successful") 55 | } 56 | } 57 | 58 | location = /unregister { 59 | content_by_lua_block { 60 | local json = require("cjson.safe") 61 | local httpc = require("resty.http").new() 62 | local encode = ngx.encode_base64 63 | httpc:request_uri(os.getenv("ETCD_SERVER_URL") .. "/v3/kv/deleterange", { 64 | method = "POST", 65 | body = json.encode({ 66 | key = encode("/shenyu/register/instance/" .. ngx.var.server_addr .. ":9090"), 67 | range_end = encode("/shenyu/register/instance/" .. ngx.var.server_addr .. ":9090a"), 68 | }), 69 | }) 70 | ngx.say("unregister successful") 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/it/case/etcd/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | version: "2.3" 17 | 18 | services: 19 | etcd: 20 | image: bitnami/etcd:3.4 21 | expose: 22 | - 2379 23 | environment: 24 | - ALLOW_NONE_AUTHENTICATION=yes 25 | - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 26 | restart: on-failure 27 | healthcheck: 28 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/2379" ] 29 | interval: 5s 30 | timeout: 60s 31 | retries: 120 32 | networks: 33 | shenyu: 34 | ipv4_address: 172.16.238.10 35 | 36 | instance1: 37 | build: 38 | context: ../../mock-shenyu 39 | expose: 40 | - 9090 41 | environment: 42 | - APP_NAME=mock-shenyu-instance-1 43 | - ETCD_SERVER_URL=http://172.16.238.10:2379 44 | volumes: 45 | - ./conf/mock-shenyu.conf:/var/run/nginx.conf 46 | entrypoint: ["openresty", "-c", "/var/run/nginx.conf"] 47 | restart: on-failure 48 | healthcheck: 49 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ] 50 | interval: 5s 51 | timeout: 60s 52 | retries: 120 53 | depends_on: 54 | etcd: 55 | condition: service_healthy 56 | networks: 57 | shenyu: 58 | ipv4_address: 172.16.238.11 59 | 60 | instance2: 61 | build: 62 | context: ../../mock-shenyu 63 | expose: 64 | - 9090 65 | environment: 66 | - APP_NAME=mock-shenyu-instance-2 67 | - ETCD_SERVER_URL=http://172.16.238.10:2379 68 | volumes: 69 | - ./conf/mock-shenyu.conf:/var/run/nginx.conf 70 | entrypoint: ["openresty", "-c", "/var/run/nginx.conf"] 71 | restart: on-failure 72 | healthcheck: 73 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ] 74 | interval: 5s 75 | timeout: 60s 76 | retries: 120 77 | depends_on: 78 | etcd: 79 | condition: service_healthy 80 | networks: 81 | shenyu: 82 | ipv4_address: 172.16.238.12 83 | 84 | gateway: 85 | build: 86 | context: ../../../.. 87 | dockerfile: ./test/it/gateway/Dockerfile 88 | environment: 89 | - ETCD_SERVER_URL=http://172.16.238.10:2379 90 | volumes: 91 | - ./conf/gateway.conf:/var/run/nginx.conf 92 | entrypoint: ["openresty", "-c", "/var/run/nginx.conf"] 93 | restart: on-failure 94 | healthcheck: 95 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080" ] 96 | interval: 5s 97 | timeout: 60s 98 | retries: 120 99 | depends_on: 100 | etcd: 101 | condition: service_healthy 102 | networks: 103 | shenyu: 104 | ipv4_address: 172.16.238.13 105 | 106 | consumer: 107 | build: 108 | context: ../../../.. 109 | dockerfile: ./test/it/consumer/Dockerfile 110 | depends_on: 111 | gateway: 112 | condition: service_healthy 113 | instance1: 114 | condition: service_healthy 115 | instance2: 116 | condition: service_healthy 117 | networks: 118 | shenyu: 119 | ipv4_address: 172.16.238.20 120 | 121 | networks: 122 | shenyu: 123 | driver: bridge 124 | ipam: 125 | driver: default 126 | config: 127 | - subnet: 172.16.238.0/24 128 | gateway: 172.16.238.1 129 | -------------------------------------------------------------------------------- /test/it/case/nacos/conf/gateway.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 4; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | 26 | env NACOS_SERVER_URL; 27 | 28 | http { 29 | lua_shared_dict shenyu_storage 10m; 30 | 31 | init_worker_by_lua_block { 32 | local register = require("shenyu.register.nacos") 33 | register.init({ 34 | shenyu_storage = ngx.shared.shenyu_storage, 35 | balancer_type = "roundrobin", 36 | nacos_base_url = os.getenv("NACOS_SERVER_URL"), 37 | username = "nacos", 38 | password = "nacos", 39 | }) 40 | } 41 | 42 | upstream shenyu { 43 | server 0.0.0.1; 44 | balancer_by_lua_block { 45 | require("shenyu.register.nacos").pick_and_set_peer() 46 | } 47 | } 48 | 49 | server { 50 | listen 8080; 51 | 52 | location ~ /* { 53 | proxy_pass http://shenyu; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/it/case/nacos/conf/mock-shenyu.conf: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | worker_processes 1; 19 | daemon off; 20 | error_log /dev/stdout debug; 21 | 22 | events { 23 | worker_connections 1024; 24 | } 25 | 26 | env APP_NAME; 27 | env NACOS_SERVER_URL; 28 | 29 | http { 30 | lua_shared_dict status 1m; 31 | 32 | server { 33 | listen 9090; 34 | location ~ /get { 35 | content_by_lua_block { 36 | ngx.say(ngx.var.server_addr) 37 | } 38 | } 39 | 40 | location = /register { 41 | content_by_lua_block { 42 | local json = require("cjson.safe") 43 | local httpc = require("resty.http").new() 44 | local base_url = os.getenv("NACOS_SERVER_URL") 45 | 46 | local resp, err = httpc:request_uri(base_url, { 47 | method = "POST", 48 | path = "/nacos/v1/auth/login", 49 | query = "username=nacos&password=nacos", 50 | }) 51 | if not resp then 52 | ngx.say(err) 53 | ngx.exit(500) 54 | end 55 | ngx.log(ngx.INFO, resp.body) 56 | if resp.status ~= 200 then 57 | ngx.say(resp.body) 58 | ngx.exit(500) 59 | end 60 | local token = json.decode(resp.body).accessToken 61 | 62 | local resp, err = httpc:request_uri(base_url, { 63 | method = "POST", 64 | path = "/nacos/v1/ns/service", 65 | query = "serviceName=shenyu-instances&groupName=DEFAULT_GROUP&namespaceId=", 66 | }) 67 | if not resp then 68 | ngx.say(err) 69 | ngx.exit(500) 70 | end 71 | ngx.log(ngx.INFO, resp.body) 72 | 73 | local instance = "serviceName=shenyu-instances&groupName=DEFAULT_GROUP&namespaceId=&ip=" .. ngx.var.server_addr .. "&port=9090" 74 | local resp, err = httpc:request_uri(base_url, { 75 | method = "POST", 76 | path = "/nacos/v1/ns/instance", 77 | query = instance, 78 | }) 79 | if not resp then 80 | ngx.say(err) 81 | ngx.exit(500) 82 | end 83 | ngx.log(ngx.INFO, resp.body) 84 | if resp.status ~= 200 then 85 | ngx.say(resp.body) 86 | ngx.exit(500) 87 | end 88 | local shdict = ngx.shared.status 89 | if shdict:get("beat") then 90 | ngx.say("registered") 91 | return 92 | end 93 | shdict:set("beat", true) 94 | 95 | local function beat(premature) 96 | local shdict = ngx.shared.status 97 | local flag = shdict:get("beat") 98 | if not flag or premature or ngx.worker.exiting() then 99 | return 100 | end 101 | local httpc = require("resty.http").new() 102 | local resp, err = httpc:request_uri(base_url, { 103 | method = "PUT", 104 | path = "/nacos/v1/ns/instance/beat", 105 | query = instance .. "&beat=" 106 | }) 107 | if not resp then 108 | ngx.say(err) 109 | return 110 | end 111 | ngx.log(ngx.INFO, resp.body) 112 | 113 | local ok, err = ngx.timer.at(3, beat) 114 | if not ok then 115 | ngx.log(ngx.ERR, "failed to create timer: ", err) 116 | return 117 | end 118 | end 119 | 120 | local ok, err = ngx.timer.at(0, beat) 121 | if not ok then 122 | ngx.log(ngx.ERR, "failed to create timer: ", err) 123 | end 124 | ngx.say("register successful") 125 | } 126 | } 127 | 128 | location = /unregister { 129 | content_by_lua_block { 130 | local shdict = ngx.shared.status 131 | shdict:delete("beat") 132 | ngx.say("unregister successful") 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /test/it/case/nacos/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | version: "2.3" 17 | 18 | services: 19 | nacos: 20 | image: nacos/nacos-server:2.0.2 21 | expose: 22 | - 8848 23 | - 8849 24 | - 9848 25 | - 9849 26 | environment: 27 | - MODE=standalone 28 | restart: on-failure 29 | healthcheck: 30 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8848" ] 31 | interval: 5s 32 | timeout: 60s 33 | retries: 120 34 | networks: 35 | shenyu-nacos: 36 | ipv4_address: 172.16.236.10 37 | 38 | instance1: 39 | build: 40 | context: ../../mock-shenyu 41 | expose: 42 | - 9090 43 | environment: 44 | - APP_NAME=mock-shenyu-instance-1 45 | - NACOS_SERVER_URL=http://172.16.236.10:8848 46 | volumes: 47 | - ./conf/mock-shenyu.conf:/var/run/nginx.conf 48 | entrypoint: ["openresty", "-c", "/var/run/nginx.conf"] 49 | restart: on-failure 50 | healthcheck: 51 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ] 52 | interval: 5s 53 | timeout: 60s 54 | retries: 120 55 | depends_on: 56 | nacos: 57 | condition: service_healthy 58 | networks: 59 | shenyu-nacos: 60 | ipv4_address: 172.16.236.11 61 | 62 | instance2: 63 | build: 64 | context: ../../mock-shenyu 65 | expose: 66 | - 9090 67 | environment: 68 | - APP_NAME=mock-shenyu-instance-2 69 | - NACOS_SERVER_URL=http://172.16.236.10:8848 70 | volumes: 71 | - ./conf/mock-shenyu.conf:/var/run/nginx.conf 72 | entrypoint: ["openresty", "-c", "/var/run/nginx.conf"] 73 | restart: on-failure 74 | healthcheck: 75 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ] 76 | interval: 5s 77 | timeout: 60s 78 | retries: 120 79 | depends_on: 80 | nacos: 81 | condition: service_healthy 82 | networks: 83 | shenyu-nacos: 84 | ipv4_address: 172.16.236.12 85 | 86 | gateway: 87 | build: 88 | context: ../../../.. 89 | dockerfile: ./test/it/gateway/Dockerfile 90 | environment: 91 | - NACOS_SERVER_URL=http://172.16.236.10:8848 92 | volumes: 93 | - ./conf/gateway.conf:/var/run/nginx.conf 94 | entrypoint: ["openresty", "-c", "/var/run/nginx.conf"] 95 | restart: on-failure 96 | healthcheck: 97 | test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080" ] 98 | interval: 5s 99 | timeout: 60s 100 | retries: 120 101 | depends_on: 102 | nacos: 103 | condition: service_healthy 104 | networks: 105 | shenyu-nacos: 106 | ipv4_address: 172.16.236.13 107 | 108 | consumer: 109 | build: 110 | context: ../../../.. 111 | dockerfile: ./test/it/consumer/Dockerfile 112 | depends_on: 113 | gateway: 114 | condition: service_healthy 115 | instance1: 116 | condition: service_healthy 117 | instance2: 118 | condition: service_healthy 119 | networks: 120 | shenyu-nacos: 121 | ipv4_address: 172.16.236.20 122 | 123 | networks: 124 | shenyu-nacos: 125 | driver: bridge 126 | ipam: 127 | driver: default 128 | config: 129 | - subnet: 172.16.236.0/24 130 | gateway: 172.16.236.1 131 | -------------------------------------------------------------------------------- /test/it/consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | FROM alpine 18 | 19 | WORKDIR /consumer 20 | 21 | RUN apk add --no-cache curl 22 | 23 | COPY ./test/it/consumer/bin/entrypoint.sh entrypoint.sh 24 | RUN chmod +x /consumer/entrypoint.sh 25 | 26 | ENTRYPOINT /bin/sh -c /consumer/entrypoint.sh 27 | -------------------------------------------------------------------------------- /test/it/consumer/bin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License 18 | 19 | # 1: url, 2: condition, 3: message 20 | send() { 21 | echo "send request to $1" 22 | curl -s -D ./head -o response $1 23 | rst=$(grep "$2" ./head) 24 | if [[ -z "rst" ]]; then 25 | echo "send $1 error: $3" 26 | cat ./head 27 | cat ./response 28 | exit 100 29 | fi 30 | } 31 | 32 | register() { 33 | inst=$1 34 | echo "${inst} registering..." 35 | inst_addr=$(curl -s http://${inst}:9090/register) 36 | if [[ -z "$inst_addr" ]]; then 37 | echo "failed to start ${inst}." 38 | exit 129 39 | fi 40 | echo "${inst} registered." 41 | } 42 | 43 | send "http://gateway:8080/get" "HTTP/1.1 500" "failed to set new environment for testing." 44 | 45 | register "instance1" 46 | register "instance2" 47 | 48 | sleep 5 49 | 50 | send "http://instance1:9090/get" "HTTP/1.1 200" "failed to request instance1." 51 | send "http://instance2:9090/get" "HTTP/1.1 200" "failed to request instance2." 52 | 53 | send "http://gateway:8080/get" "HTTP/1.1 200" "shenyu nginx module did not work." 54 | 55 | echo "send requests to gateway." 56 | inst1=$(curl -s http://gateway:8080/get) 57 | inst2=$(curl -s http://gateway:8080/get) 58 | if [[ "$inst1" == "$inst2" ]]; then 59 | echo "validation failed, inst1: ${inst1}, inst2: ${inst2}" 60 | exit 128 61 | fi 62 | 63 | # remove instance 1 64 | send "http://instance1:9090/unregister" "HTTP/1.1 200" "failed to unregister instance1 to register center." 65 | 66 | sleep 5 67 | 68 | send "http://gateway:8080/get" "HTTP/1.1 200" "shenyu nginx module did not work right" 69 | 70 | rst=$(curl -s http://gateway:8080/get) 71 | if [[ "$rst" == "$inst_addr_1" ]]; then 72 | echo "nginx module did remove unregister instance 1." 73 | exit 128 74 | fi 75 | rst=$(curl -s http://gateway:8080/get) 76 | if [[ "$rst" == "$inst_addr_1" ]]; then 77 | echo "nginx module did remove unregister instance 1." 78 | exit 128 79 | fi 80 | 81 | echo "validation successful" 82 | 83 | exit 200 84 | -------------------------------------------------------------------------------- /test/it/gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | FROM openresty/openresty:1.17.8.2-5-alpine-fat 18 | 19 | WORKDIR /shenyu-nginx 20 | 21 | COPY ./lib lib 22 | COPY ./rockspec rockspec 23 | 24 | RUN luarocks make ./rockspec/shenyu-nginx-1.0.0-1.rockspec 25 | -------------------------------------------------------------------------------- /test/it/mock-shenyu/Dockerfile: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | FROM openresty/openresty:1.17.8.2-5-alpine-fat 18 | 19 | RUN luarocks install lua-resty-http 20 | --------------------------------------------------------------------------------