Merge branch 'main' into impactCn-patch-1
diff --git a/.asf.yaml b/.asf.yaml
index 0fca6ac..b1fe08b 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -16,7 +16,7 @@
 #
 
 github:
-  description: ShenYu is High-Performance Java API Gateway.
+  description: Apache ShenYu Nginx.
   homepage: https://shenyu.apache.org/
   labels:
     - shenyu
diff --git a/.github/workflows/it.yaml b/.github/workflows/it.yaml
index e769f3b..3153b06 100644
--- a/.github/workflows/it.yaml
+++ b/.github/workflows/it.yaml
@@ -25,6 +25,28 @@
       - 'v*'
 
 jobs:
+  nacos:
+    runs-on: ubuntu-latest
+    timeout-minutes: 20
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: true
+      - name: run testcase
+        run: |
+          set -x
+          docker-compose -f ./test/it/case/nacos/docker-compose.yml run -d consumer --build
+          status=$(docker wait $(docker ps -qa --filter Name=nacos_consumer))
+          if [[ $status -ne 200 ]]; then
+            docker-compose -f ./test/it/case/nacos/docker-compose.yml logs
+          fi
+          docker logs $(docker ps -qa --filter Name=nacos_consumer)
+          docker-compose -f ./test/it/case/nacos/docker-compose.yml kill
+          docker-compose -f ./test/it/case/nacos/docker-compose.yml rm -f
+          if [[ $status -ne 200 ]]; then
+            exit 1
+          fi
+
   etcd:
     runs-on: ubuntu-latest
     timeout-minutes: 20
@@ -32,15 +54,17 @@
       - uses: actions/checkout@v3
         with:
           submodules: true
-      - uses: actions/setup-java@v1
-        with:
-          java-version: 8
-      - run: |
-          docker-compose -f ./test/it/docker-compose.yml run -d consumer
-          docker wait $(docker ps -qa --filter Name=it_consumer)
-          status="$?"
-          docker-compose -f ./test/it/docker-compose.yml logs
-          if [ $status -eq 0 ]; then
-            docker-compose -f test/it/docker-compose.yml logs consumer
+      - name: run testcase
+        run: |
+          set -x
+          docker-compose -f ./test/it/case/etcd/docker-compose.yml run -d consumer --build
+          status=$(docker wait $(docker ps -qa --filter Name=etcd_consumer))
+          if [[ $status -ne 200 ]]; then
+            docker-compose -f ./test/it/case/etcd/docker-compose.yml logs
           fi
-          docker-compose -f ./test/it/docker-compose.yml kill
+          docker logs $(docker ps -qa --filter Name=etcd_consumer)
+          docker-compose -f ./test/it/case/etcd/docker-compose.yml kill
+          docker-compose -f ./test/it/case/etcd/docker-compose.yml rm -f
+          if [[ $status -ne 200 ]]; then
+            exit 1
+          fi
diff --git a/.gitignore b/.gitignore
index 276ba56..af1c99a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,6 @@
 *.diff
 *.patch
 *.tmp
+
+/out/
+/.vscode/
diff --git a/README.md b/README.md
index 101e512..504f61d 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,15 @@
-incubator-shenyu-nginx
+Apache ShenYu Nginx Module(Experimental)
 ---
 
-ShenYu(Incubator) is High-Performance Java API Gateway. 
+This module provided SDK to watch available ShenYu instance list as upstream nodes by Service Register Center for OpenResty.
+1. [ETCD](#greeting-etcd) (Supported)
+2. [Nacos](#greeting-nacos) (Supported)
+3. [Zookeeper](#greeting-zookeeper) (Supported)
+4. Consul (TODO)
 
-ShenYu Nginx is an Nginx Upstream module for ShenYu instances discovery. The module will discover ShenYu instances to be upstream server of Nginx throght watching register, such as Etcd(supported), Apache Zookeeper(Todo), Nacos(Todo), and others.
+In the cluster mode, Apache ShenYu supports the deployment of multiple ShenYu instances, which may have new instances joining or leaving at any time.
+Hence, Apache ShenYu introduces Service Discovery modules to help client to detect the available instances. 
+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. 
 
 ## Getting Started
 
@@ -13,7 +19,7 @@
 
 ### Build from source
 
-The first, clone the source from Github.
+The first, clone the source from GitHub.
 ```shell
 git clone https://github.com/apache/incubator-shenyu-nginx
 ```
@@ -24,7 +30,13 @@
 luarocks make rockspec/shenyu-nginx-main-0.rockspec
 ```
 
-Modify the Nginx configure, create and initialize the ShenYu Register to connect to targed register center.  Here is an example for Etcd.
+### Greeting ETCD
+
+Modify the Nginx configure, create and initialize the ShenYu Register to connect to the target register center. 
+The module will fetch the all of ShenYu instances which are registered to Etcd in the same cluster.
+It works like Etcd client to watch(based on long polling) ShenYu instance lists. 
+
+Here is an example for Etcd.
 ```
 init_worker_by_lua_block {
     local register = require("shenyu.register.etcd")
@@ -36,9 +48,9 @@
 ```
 
 1. `balancer_type` specify the balancer. It has supported `chash` and `round robin`.
-2. `etcd_base_url` specify the Etcd server.
+2. `etcd_base_url` specify the Etcd server.(Currently, authentication is not supported.)
 
-Modify the `upstream` to enable to update upstream servers dynamically. This case will synchorinze the ShenYu instance list with register center. 
+Add an `upstream block` for ShenYu and 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.
 ```
 upstream shenyu {
@@ -56,6 +68,79 @@
 ```
 
 Here provides a completed [examples](https://github.com/apache/incubator-shenyu-nginx/tree/main/example).
+=======
+Here is a completed [example](https://github.com/apache/incubator-shenyu-nginx/blob/main/example/etcd/nginx.conf) working with ETCD.
+
+### Greeting Nacos
+
+Modify the Nginx configure, create and initialize the ShenYu Register to connect to target register center.  Here is an example for Nacos.
+```
+init_worker_by_lua_block {
+    local register = require("shenyu.register.nacos")
+    register.init({
+        shenyu_storage = ngx.shared.shenyu_storage,
+        balancer_type = "chash",
+        nacos_base_url = "http://127.0.0.1:8848",
+        username = "nacos",
+        password = "naocs",
+    })
+}
+```
+
+1. `balancer_type` specify the balancer. It has supported `chash` and `round robin`.
+2. `nacos_base_url` specify the Nacos server address.
+3. `username` specify the username to log in Nacos. (it is only required when Nacos auth enable)
+4. `password` specify the password to log in Nacos.
+
+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.
+```
+upstream shenyu {
+    server 0.0.0.1; -- bad 
+    
+    balancer_by_lua_block {
+        require("shenyu.register.nacos").pick_and_set_peer()
+    }
+}
+```
+
+Finally, restart OpenResty.
+```shell
+openresty -s reload
+```
+
+Here is a completed [example](https://github.com/apache/incubator-shenyu-nginx/blob/main/example/nacos/nginx.conf) working with Nacos.
+
+## Greeting Zookeeper 
+Modify the Nginx configure, create and initialize the ShenYu register to connect to target register center.
+Listen for changes to the node via the zookeeper watch event. Here is an example of the zookeeper configuration.
+```shell
+init_worker_by_lua_block {
+        local register = require("shenyu.register.zookeeper")
+        register.init({
+           servers = {"127.0.0.1:2181","127.0.0.1:2182"},
+           shenyu_storage = ngx.shared.shenyu_storage,
+           balancer_type = "roundrobin"
+        });
+    }
+```
+1. `servers` zookeeper cluster address.
+2. ``balancer_type`` specify the balancer. It has supported `chash` and `round robin`.
+
+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.
+```shell
+ upstream shenyu {
+        server 0.0.0.1;
+        balancer_by_lua_block {
+            require("shenyu.register.zookeeper").pick_and_set_peer()
+        }
+    }
+```
+Finally, restart OpenResty.
+```shell
+openresty -s reload
+```
+Here is a completed [example](https://github.com/apache/incubator-shenyu-nginx/blob/main/example/zookeeper/nginx.conf) working with Zookeeper.
 
 ## Contributor and Support
 
diff --git a/example/etcd/nginx.conf b/example/etcd/nginx.conf
index ef2c410..41b7a18 100644
--- a/example/etcd/nginx.conf
+++ b/example/etcd/nginx.conf
@@ -42,7 +42,7 @@
     }
 
     server {
-        listen 80;
+        listen 8080;
 
         location ~ /* {
             proxy_pass http://shenyu;
diff --git a/example/eureka/nginx.conf b/example/eureka/nginx.conf
new file mode 100644
index 0000000..9004569
--- /dev/null
+++ b/example/eureka/nginx.conf
@@ -0,0 +1,49 @@
+lua_package_path "/usr/local/openresty/lualib/?.lua;;";
+lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
+
+lua_shared_dict upstream_list 10m;
+
+# 第一次初始化
+init_by_lua_block {
+    local eureka = require "eureka";
+    eureka.init({
+        upstream_list = ngx.shared.upstream_list,
+        base_url = "http://192.168.9.252:8761",
+        path = "/eureka/apps/demo",
+
+    });
+}
+
+# 定时拉取配置
+init_worker_by_lua_block {
+    local eureka = require "eureka";
+    local handle = nil;
+
+    handle = function ()
+        --TODO:控制每次只有一个worker执行
+        eureka.get_server_list();
+        ngx.timer.at(5, handle);
+    end
+    ngx.timer.at(5, handle);
+}
+
+upstream api_server {
+    server 0.0.0.1 down; #占位server
+
+    balancer_by_lua_block {
+        local balancer = require "ngx.balancer";
+        local eureka = require "eureka";
+        local tmp_upstreams = eureka.get_upstreams();
+        local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))];
+        balancer.set_current_peer(ip_port.ip, ip_port.port);
+    }
+}
+
+server {
+    listen       12000;
+    server_name  localhost;
+    charset utf-8;
+    location / {
+         proxy_pass http://api_server;
+    }
+}
\ No newline at end of file
diff --git a/example/nacos/nginx.conf b/example/nacos/nginx.conf
index d954917..167306e 100644
--- a/example/nacos/nginx.conf
+++ b/example/nacos/nginx.conf
@@ -32,7 +32,7 @@
             balancer_type = "chash",
             nacos_base_url = "http://127.0.0.1:8848",
             username = "nacos",
-            password = "naocs",
+            password = "nacos",
         })
     }
 
@@ -44,7 +44,7 @@
     }
 
     server {
-        listen 80;
+        listen 8080;
 
         location ~ /* {
             proxy_pass http://shenyu;
diff --git a/test/it/gateway/conf/nginx.conf b/example/zookeeper/nginx.conf
similarity index 73%
copy from test/it/gateway/conf/nginx.conf
copy to example/zookeeper/nginx.conf
index dc5795f..a53b238 100644
--- a/test/it/gateway/conf/nginx.conf
+++ b/example/zookeeper/nginx.conf
@@ -15,37 +15,41 @@
 # specific language governing permissions and limitations
 # under the License.
 
-worker_processes  4;
+worker_processes  2;
 daemon off;
 error_log /dev/stdout debug;
 
 events {
     worker_connections 1024;
 }
-
-env ETCD_SERVER_URL;
-
 http {
+    lua_shared_dict shenyu_storage 1m;
+
+#     lua_package_path "$prefix/lib/?.lua;;";
+
     init_worker_by_lua_block {
-        local register = require("shenyu.register.etcd")
+        local register = require("shenyu.register.zookeeper")
         register.init({
-            balancer_type = "chash",
-            etcd_base_url = os.getenv("ETCD_SERVER_URL"),
-        })
+           servers = {"127.0.0.1:2181"},
+           shenyu_storage = ngx.shared.shenyu_storage,
+           balancer_type = "chash"
+        });
     }
 
     upstream shenyu {
         server 0.0.0.1;
         balancer_by_lua_block {
-            require("shenyu.register.etcd").pick_and_set_peer()
+            require("shenyu.register.zookeeper").pick_and_set_peer()
         }
     }
 
     server {
-        listen 8080;
+        listen 80;
 
         location ~ /* {
             proxy_pass http://shenyu;
         }
     }
 }
+
+	
diff --git a/lib/shenyu/register/balancer.lua b/lib/shenyu/register/balancer.lua
index 39d37ad..5407bc8 100644
--- a/lib/shenyu/register/balancer.lua
+++ b/lib/shenyu/register/balancer.lua
@@ -14,7 +14,6 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-
 local _M = {}
 local str_null = string.char(0)
 
@@ -25,7 +24,6 @@
             local servers, nodes = {}, {}
             for serv, weight in pairs(server_list) do
                 local id = string.gsub(serv, ":", str_null)
-
                 servers[id] = serv
                 nodes[id] = weight
             end
diff --git a/lib/shenyu/register/core/string.lua b/lib/shenyu/register/core/string.lua
new file mode 100644
index 0000000..5d63c29
--- /dev/null
+++ b/lib/shenyu/register/core/string.lua
@@ -0,0 +1,30 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local table_insert          = table.insert
+local _M                    ={}
+
+function _M.split(str, delimiter)
+    if not str or str == "" then return {} end
+    if not delimiter or delimiter == "" then return { str } end
+    local result = {}
+    for match in (str .. delimiter):gmatch("(.-)" .. delimiter) do
+        table_insert(result, match)
+    end
+    return result
+end
+
+return _M
\ No newline at end of file
diff --git a/lib/shenyu/register/core/struct.lua b/lib/shenyu/register/core/struct.lua
new file mode 100644
index 0000000..d26d350
--- /dev/null
+++ b/lib/shenyu/register/core/struct.lua
@@ -0,0 +1,209 @@
+--[[
+ * Copyright (c) 2015-2020 Iryont <https://github.com/iryont/lua-struct>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+]]
+local unpack = table.unpack or _G.unpack
+local struct = {}
+
+function struct.pack(format, ...)
+  local stream = {}
+  local vars = {...}
+  local endianness = true
+
+  for i = 1, format:len() do
+    local opt = format:sub(i, i)
+
+    if opt == "<" then
+      endianness = true
+    elseif opt == ">" then
+      endianness = false
+    elseif opt:find("[bBhHiIlL]") then
+      local n = opt:find("[hH]") and 2 or opt:find("[iI]") and 4 or opt:find("[lL]") and 8 or 1
+      local val = tonumber(table.remove(vars, 1))
+
+      local bytes = {}
+      for j = 1, n do
+        table.insert(bytes, string.char(val % (2 ^ 8)))
+        val = math.floor(val / (2 ^ 8))
+      end
+
+      if not endianness then
+        table.insert(stream, string.reverse(table.concat(bytes)))
+      else
+        table.insert(stream, table.concat(bytes))
+      end
+    elseif opt:find("[fd]") then
+      local val = tonumber(table.remove(vars, 1))
+      local sign = 0
+
+      if val < 0 then
+        sign = 1
+        val = -val
+      end
+
+      local mantissa, exponent = math.frexp(val)
+      if val == 0 then
+        mantissa = 0
+        exponent = 0
+      else
+        mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == "d") and 53 or 24)
+        exponent = exponent + ((opt == "d") and 1022 or 126)
+      end
+
+      local bytes = {}
+      if opt == "d" then
+        val = mantissa
+        for i = 1, 6 do
+          table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
+          val = math.floor(val / (2 ^ 8))
+        end
+      else
+        table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8)))
+        val = math.floor(mantissa / (2 ^ 8))
+        table.insert(bytes, string.char(math.floor(val) % (2 ^ 8)))
+        val = math.floor(val / (2 ^ 8))
+      end
+
+      table.insert(bytes, string.char(math.floor(exponent * ((opt == "d") and 16 or 128) + val) % (2 ^ 8)))
+      val = math.floor((exponent * ((opt == "d") and 16 or 128) + val) / (2 ^ 8))
+      table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8)))
+      val = math.floor((sign * 128 + val) / (2 ^ 8))
+
+      if not endianness then
+        table.insert(stream, string.reverse(table.concat(bytes)))
+      else
+        table.insert(stream, table.concat(bytes))
+      end
+    elseif opt == "s" then
+      table.insert(stream, tostring(table.remove(vars, 1)))
+      table.insert(stream, string.char(0))
+    elseif opt == "c" then
+      local n = format:sub(i + 1):match("%d+")
+      local str = tostring(table.remove(vars, 1))
+      local len = tonumber(n)
+      if len <= 0 then
+        len = str:len()
+      end
+      if len - str:len() > 0 then
+        str = str .. string.rep(" ", len - str:len())
+      end
+      table.insert(stream, str:sub(1, len))
+      i = i + n:len()
+    end
+  end
+
+  return table.concat(stream)
+end
+
+function struct.unpack(format, stream, pos)
+  local vars = {}
+  local iterator = pos or 1
+  local endianness = true
+
+  for i = 1, format:len() do
+    local opt = format:sub(i, i)
+
+    if opt == "<" then
+      endianness = true
+    elseif opt == ">" then
+      endianness = false
+    elseif opt:find("[bBhHiIlL]") then
+      local n = opt:find("[hH]") and 2 or opt:find("[iI]") and 4 or opt:find("[lL]") and 8 or 1
+      local signed = opt:lower() == opt
+
+      local val = 0
+      for j = 1, n do
+        local byte = string.byte(stream:sub(iterator, iterator))
+        if endianness then
+          val = val + byte * (2 ^ ((j - 1) * 8))
+        else
+          val = val + byte * (2 ^ ((n - j) * 8))
+        end
+        iterator = iterator + 1
+      end
+
+      if signed and val >= 2 ^ (n * 8 - 1) then
+        val = val - 2 ^ (n * 8)
+      end
+
+      table.insert(vars, math.floor(val))
+    elseif opt:find("[fd]") then
+      local n = (opt == "d") and 8 or 4
+      local x = stream:sub(iterator, iterator + n - 1)
+      iterator = iterator + n
+
+      if not endianness then
+        x = string.reverse(x)
+      end
+
+      local sign = 1
+      local mantissa = string.byte(x, (opt == "d") and 7 or 3) % ((opt == "d") and 16 or 128)
+      for i = n - 2, 1, -1 do
+        mantissa = mantissa * (2 ^ 8) + string.byte(x, i)
+      end
+
+      if string.byte(x, n) > 127 then
+        sign = -1
+      end
+
+      local exponent =
+        (string.byte(x, n) % 128) * ((opt == "d") and 16 or 2) +
+        math.floor(string.byte(x, n - 1) / ((opt == "d") and 16 or 128))
+      if exponent == 0 then
+        table.insert(vars, 0.0)
+      else
+        mantissa = (math.ldexp(mantissa, (opt == "d") and -52 or -23) + 1) * sign
+        table.insert(vars, math.ldexp(mantissa, exponent - ((opt == "d") and 1023 or 127)))
+      end
+    elseif opt == "s" then
+      local bytes = {}
+      for j = iterator, stream:len() do
+        if stream:sub(j, j) == string.char(0) or stream:sub(j) == "" then
+          break
+        end
+
+        table.insert(bytes, stream:sub(j, j))
+      end
+
+      local str = table.concat(bytes)
+      iterator = iterator + str:len() + 1
+      table.insert(vars, str)
+    elseif opt == "c" then
+      local n = format:sub(i + 1):match("%d+")
+      local len = tonumber(n)
+      if len <= 0 then
+        len = table.remove(vars)
+      end
+
+      table.insert(vars, stream:sub(iterator, iterator + len - 1))
+      iterator = iterator + len
+      i = i + n:len()
+    end
+  end
+
+  return vars, iterator
+end
+
+function struct.tbunpack(vars)
+  -- body
+  return unpack(vars)
+end
+
+return struct
diff --git a/lib/shenyu/register/core/utils.lua b/lib/shenyu/register/core/utils.lua
new file mode 100644
index 0000000..65774d9
--- /dev/null
+++ b/lib/shenyu/register/core/utils.lua
@@ -0,0 +1,39 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local str = require("shenyu.register.core.string")
+local _M = {}
+
+--Delimited string
+function _M.paras_host(host, delimiter)
+    return str.split(host, delimiter)
+end
+
+function _M.long_to_hex_string(long)
+    return string.format("0x%06x", long)
+end
+
+-- table len
+function _M.table_len(args)
+    -- body
+    local n = 0
+    if args then
+        n = #args
+    end
+    return n
+end
+
+return _M
diff --git a/lib/shenyu/register/etcd.lua b/lib/shenyu/register/etcd.lua
index 370d647..da20d13 100644
--- a/lib/shenyu/register/etcd.lua
+++ b/lib/shenyu/register/etcd.lua
@@ -83,7 +83,7 @@
         host = m[2],
         port = tonumber(m[3]),
         base_url = base_url,
-        prefix = detect_etcd_version(base_url),
+        prefix = detect_etcd_version(base_url)
     }
 end
 
@@ -95,14 +95,18 @@
 local function fetch_shenyu_instances(conf)
     local range_request = {
         key = encode_base64(_M.start_key),
-        range_end = encode_base64(_M.end_key),
+        range_end = encode_base64(_M.end_key)
     }
 
     local httpc = http.new()
-    local res, err = httpc:request_uri(conf.base_url .. conf.prefix .. "/kv/range", {
-        method = "POST",
-        body = json.encode(range_request),
-    })
+    local res, err =
+        httpc:request_uri(
+        conf.base_url .. conf.prefix .. "/kv/range",
+        {
+            method = "POST",
+            body = json.encode(range_request)
+        }
+    )
     if not res then
         return nil, "failed to list shenyu instances from etcd, " .. (err or "unknown")
     end
@@ -221,7 +225,7 @@
     local lock = storage:get("_lock")
     local ver = storage:get("revision")
 
-    if lock and ver > _M.revision then
+    if not lock and ver > _M.revision then
         local server_list = storage:get("server_list")
         local servers = json.decode(server_list)
         if _M.revision <= 1 then
@@ -246,7 +250,7 @@
 
     if not watching then
         if not _M.etcd_conf then
-            _M.storage:set("_lock", false)
+            _M.storage:set("_lock", true)
 
             local conf, err = parse_base_url(_M.etcd_base_url)
             if not conf then
@@ -262,16 +266,19 @@
             _M.time_at = 3
         else
             watching = true
-            _M.storage:set("_lock", true)
+            _M.storage:set("_lock", false)
         end
     else
         local conf = _M.etcd_conf
         local httpc = http.new()
-        local ok, err = httpc:connect({
-            scheme = conf.scheme,
-            host = conf.host,
-            port = tonumber(conf.port),
-        })
+        local ok, err =
+            httpc:connect(
+            {
+                scheme = conf.scheme,
+                host = conf.host,
+                port = tonumber(conf.port)
+            }
+        )
         if not ok then
             log(ERR, "failed to connect to etcd server", err)
             _M.time_at = 3
@@ -292,15 +299,18 @@
             create_request = {
                 key = encode_base64(_M.start_key),
                 range_end = encode_base64(_M.end_key),
-                start_revision = _M.revision,
+                start_revision = _M.revision
             }
         }
 
-        local res, err = httpc:request({
-            path = "/v3/watch",
-            method = "POST",
-            body = json.encode(request),
-        })
+        local res, err =
+            httpc:request(
+            {
+                path = "/v3/watch",
+                method = "POST",
+                body = json.encode(request)
+            }
+        )
         if not res then
             log(ERR, "failed to watch keys under '/shenyu/register/instance/'", err)
             _M.time_at = 3
@@ -330,7 +340,7 @@
         end
     end
 
-    :: continue ::
+    ::continue::
     local ok, err = ngx_timer_at(_M.time_at, watch, watching)
     if not ok then
         log(ERR, "failed to start watch: ", err)
diff --git a/lib/shenyu/register/eureka.lua b/lib/shenyu/register/eureka.lua
new file mode 100644
index 0000000..bcec77b
--- /dev/null
+++ b/lib/shenyu/register/eureka.lua
@@ -0,0 +1,73 @@
+local _M = {}
+
+local http = require("resty.http")
+local json = require("cjson")
+
+
+local ngx = ngx
+
+local ngx_timer_at = ngx.timer.at
+local ngx_worker_exiting = ngx.worker.exiting
+
+
+local log = ngx.log
+local ERR = ngx.ERR
+local INFO = ngx.INFO
+
+
+function  _M:get_server_list()
+
+    local httpc = http:new()
+
+    local res, err = httpc:request_uri(_M.base_url, {
+        method = "GET",
+        path = _M.path,
+        headers = {["Accept"]="application/json"},
+    })
+    if not res then
+        log(ERR, "failed to get server list from eureka. ", err)
+    end
+
+    local service = {}
+
+    if res.status == 200 then
+
+        local list_inst_resp = json.decode(res.body)
+        local application = list_inst_resp.application
+        for k, v in ipairs(application) do
+
+            local instances = v["instance"]
+
+            for i, instance in pairs(instances) do
+                local status = instance["status"]
+                if status == "UP" then
+                    local ipAddr = instance["ipAddr"]
+                    local port = instance["port"]["$"]
+                    log(INFO, "ipAddr: ", ipAddr)
+                    log(INFO, "port: ", port)
+                    service[i] = {ip=ipAddr, port=port}
+                end
+            end
+        end
+    end
+
+    _M.storage:set("demo", json.encode(service))
+
+
+end
+
+
+function _M:get_upstreams()
+    local upstreams_str = _M.storage:get("demo");
+    local tmp_upstreams = json.decode(upstreams_str);
+    return tmp_upstreams;
+end
+
+
+
+function _M.init(conf)
+    _M.storage = conf.upstream_list
+    _M.base_url = conf.base_url
+    _M.path = conf.path
+
+end
\ No newline at end of file
diff --git a/lib/shenyu/register/nacos.lua b/lib/shenyu/register/nacos.lua
index 5f6901b..c3f1dbc 100644
--- a/lib/shenyu/register/nacos.lua
+++ b/lib/shenyu/register/nacos.lua
@@ -35,11 +35,12 @@
 
 _M.access_token = nil
 
-local function login(username, password)
-    local res, err = httpc:request_uri(nacos_base, {
+local function login()
+    local httpc = http.new()
+    local res, err = httpc:request_uri(_M.nacos_base_url, {
         method = "POST",
         path = "/nacos/v1/auth/login",
-        query = "username=" .. username .. "&password=" .. password,
+        query = "username=" .. _M.username .. "&password=" .. _M.password,
     })
     if not res then
         return nil, err
@@ -49,82 +50,45 @@
         return nil, res.body
     end
 
+    log(INFO, "login nacos in username: '" .. _M.username .. "' successfully.")
     return json.decode(res.body).accessToken
 end
 
-local function get_server_list(serviceName, groupName, namespaceId, clusters)
-    if not namespaceId then
-        namespaceId = ""
-    end
-    if not groupName then
-        groupName = ""
-    end
-    if not clusters then
-        clusters = ""
-    end
-
-    local server_list = {}
-    local res, err = httpc:request_uri(nacos_base, {
+local function get_server_list()
+    local httpc = http.new()
+    local res, err = httpc:request_uri(_M.nacos_base_url, {
         method = "GET",
         path = "/nacos/v1/ns/instance/list",
-        query = "serviceName=" .. serviceName ..
-                "&groupName=" .. groupName ..
-                "&namespaceId=" .. namespaceId ..
-                "&clusters=" .. clusters ..
-                "&healthOnly=true",
-        headers = {
-            ["accessToken"] = _M.access_token,
-        }
+        query = "serviceName=" .. _M.service_name
+                .. "&groupName=" .. _M.group_name
+                .. "&namespaceId=" .. _M.namespace
+                .. "&clusters=" .. _M.clusters
+                .. "&healthOnly=true"
+                .. "accessToken=" .. _M.access_token
     })
-
     if not res then
-        return nil, nil, err
-    end
-
-    if res.status == 200 then
-        local list_inst_resp = json.encode(res.body)
-
-        local hosts = list_inst_resp.hosts
-        for inst in pairs(hosts) do
-            server_list[inst.instanceId] = inst.weight
-        end
-        server_list["_length_"] = #hosts
-
-        return server_list, list_inst_resp.lastRefTime
-    end
-    return nil, nil, res.body
-end
-
--- conf = {
---   balance_type = "chash",
---   nacos_base_url = "http://127.0.0.1:8848",
---   username = "nacos",
---   password = "nacos",
---   namespace = "",
---   service_name = "",
---   group_name = "",
--- }
-function _M.init(conf)
-    _M.storage = conf.shenyu_storage
-    _M.balancer = balancer.new(conf.balancer_type)
-
-    if ngx.worker.id() == 0 then
-        _M.shenyu_instances = {}
-        _M.nacos_base_url = conf.nacos_base_url
-
-        -- subscribed by polling, privileged
-        local ok, err = ngx_timer_at(0, subscribe)
-        if not ok then
-            log(ERR, "failed to start watch: " .. err)
-        end
+        log(ERR, "failed to get server list from nacos. ", err)
         return
     end
 
-    -- synchronize server_list from privileged processor to workers
-    local ok, err = ngx_timer_at(2, sync)
-    if not ok then
-        log(ERR, "failed to start sync ", err)
+    if res.status == 200 then
+        local server_list = {}
+        local list_inst_resp = json.decode(res.body)
+
+        local hosts = list_inst_resp.hosts
+        if not hosts then
+            return {}, 0, 0
+        end
+
+        for _, inst in pairs(hosts) do
+            local key = inst.ip .. ":" .. inst.port
+            server_list[key] = inst.weight
+        end
+
+        return server_list, list_inst_resp.lastRefTime, #hosts
     end
+    log(ERR, res.body)
+    return
 end
 
 local function subscribe(premature, initialized)
@@ -133,63 +97,60 @@
     end
 
     if not initialized then
-        local token, err = login(_M.username, _M.password)
+        local token, err = login()
         if not token then
             log(ERR, err)
             goto continue
         end
         _M.access_token = token
 
-        local server_list, revision, err = get_server_list(_M.service_name, _M.group_name, _M.namespace, _M.clusters)
-        if not server_list then
-            log(ERR, "", err)
+        local server_list, revision, servers_length = get_server_list()
+        if not server_list or servers_length == 0 then
             goto continue
         end
-        local servers_length = server_list["_length_"]
-        server_list["_length_"] = nil
-        _M.servers_length = servers_length
 
         _M.balancer:init(server_list)
         _M.revision = revision
+        _M.servers_length = servers_length
 
         local server_list_in_json = json.encode(server_list)
+        log(INFO, "initialize upstream: " .. server_list_in_json .. " , revision: " .. revision)
+
         _M.storage:set("server_list", server_list_in_json)
         _M.storage:set("revision", revision)
 
-        initialized = false
+        initialized = true
     else
-        local server_list, revision, err = get_server_list(_M.service_name, _M.group_name, _M.namespace, _M.clusters)
-        if not server_list then
-            log(ERR, "", err)
+        local server_list, revision, servers_length = get_server_list()
+        if not server_list or servers_length == 0 then
             goto continue
         end
 
-        local updated = false
-        local servers_length = server_list["_length_"]
-        server_list["_length_"] = nil
-
+        local updated = true
         if _M.servers_length == servers_length then
             local services = _M.server_list
             for srv, weight in pairs(server_list) do
                 if services[srv] ~= weight then
-                    updated = true
                     break
                 end
             end
-        else
-            updated = true
+            updated = false
         end
 
         if not updated then
-            goto contiue
+            goto continue
         end
 
         _M.balancer:reinit(server_list)
         _M.revision = revision
+        _M.server_list = server_list
 
         local server_list_in_json = json.encode(server_list)
+        log(INFO, "update upstream: " .. server_list_in_json .. " , revision: " .. revision)
+
         _M.storage:set("server_list", server_list_in_json)
         _M.storage:set("revision", revision)
+        _M.servers_length = servers_length
     end
 
     :: continue ::
@@ -211,14 +172,72 @@
     if ver > _M.revision then
         local server_list = storage:get("server_list")
         local servers = json.decode(server_list)
-        if _M.revision <= 1 then
+        if _M.revision < 1 then
             _M.balancer:init(servers)
+            log(INFO, "initialize upstream in workers, upstream: " .. server_list)
         else
             _M.balancer:reinit(servers)
+            log(INFO, "update upstream in workers, upstream: " .. server_list)
         end
         _M.revision = ver
     end
 
+    local ok, err = ngx_timer_at(1, sync)
+    if not ok then
+        log(ERR, "failed to start sync: ", err)
+    end
+end
+
+-- conf = {
+--   balance_type = "chash",
+--   nacos_base_url = "http://127.0.0.1:8848",
+--   username = "nacos",
+--   password = "nacos",
+--   namespace = "",
+--   service_name = "",
+--   group_name = "",
+--   clusters = "",
+-- }
+function _M.init(conf)
+    _M.storage = conf.shenyu_storage
+    _M.balancer = balancer.new(conf.balancer_type)
+
+    _M.revision = 0
+
+    if ngx.worker.id() == 0 then
+        _M.nacos_base_url = conf.nacos_base_url
+        _M.username = conf.username
+        _M.password = conf.password
+        _M.namespace = conf.namespace
+        _M.group_name = conf.group_name
+        _M.service_name = conf.service_name
+        _M.clusters = conf.clusters
+
+        if not conf.clusters then
+            _M.clusters = ""
+        end
+        if not conf.namespace then
+            _M.namespace = ""
+        end
+        if not conf.group_name then
+            _M.group_name = "DEFAULT_GROUP"
+        end
+        if not conf.service_name then
+            _M.service_name = "shenyu-instances"
+        end
+
+        _M.server_list = {}
+        _M.storage:set("revision", 0)
+
+        -- subscribed by polling, privileged
+        local ok, err = ngx_timer_at(0, subscribe)
+        if not ok then
+            log(ERR, "failed to start watch: " .. err)
+        end
+        return
+    end
+
+    -- synchronize server_list from privileged processor to workers
     local ok, err = ngx_timer_at(2, sync)
     if not ok then
         log(ERR, "failed to start sync ", err)
diff --git a/lib/shenyu/register/zookeeper.lua b/lib/shenyu/register/zookeeper.lua
new file mode 100644
index 0000000..2ec94e9
--- /dev/null
+++ b/lib/shenyu/register/zookeeper.lua
@@ -0,0 +1,97 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local zk_cluster = require("shenyu.register.zookeeper.zk_cluster")
+local const = require("shenyu.register.zookeeper.zk_const")
+local balancer = require("shenyu.register.balancer")
+local ngx_balancer = require("ngx.balancer")
+local ngx_timer_at = ngx.timer.at
+local xpcall = xpcall
+local ngx_log = ngx.log
+local math = string.match
+local zc
+local _M = {
+    isload = 0,
+    nodes = {}
+}
+
+local function watch_data(data)
+    ---@type table
+    local server_lists = {}
+    -- body
+    for index, value in ipairs(data) do
+        if math(value, ":") then
+            server_lists[value] = 1
+        end
+    end
+    ---@type table
+    local s_nodes = _M.nodes
+    for host, index in pairs(server_lists) do
+        if not s_nodes[host] then
+            ngx_log(ngx.INFO, "add shenyu server:" .. host)
+        end
+    end
+    for host, index in pairs(s_nodes) do
+        if not server_lists[host] then
+            ngx_log(ngx.INFO, "remove shenyu server:" .. host)
+        end
+    end
+    if (_M.isload > 1) then
+        _M.balancer:reinit(server_lists)
+    else
+        _M.balancer:init(server_lists)
+    end
+    _M.isload = _M.isload + 1
+    _M.nodes = server_lists
+end
+
+local function watch(premature, path)
+    local ok, err = zc:connect()
+    if ok then
+        ok, err =
+            xpcall(
+            zc:add_watch(path, watch_data),
+            function(err)
+                ngx_log(ngx.ERR, "zookeeper start watch error..." .. tostring(err))
+            end
+        )
+    end
+    return ok, err
+end
+
+function _M.init(config)
+    _M.storage = config.shenyu_storage
+    _M.balancer = balancer.new(config.balancer_type)
+    zc = zk_cluster:new(config)
+    if ngx.worker.id() == 0 then
+        -- Start the zookeeper watcher
+        local ok, err = ngx_timer_at(2, watch, const.ZK_WATCH_PATH)
+        if not ok then
+            ngx_log(ngx.ERR, "failed to start watch: " .. err)
+        end
+        return
+    end
+end
+
+function _M.pick_and_set_peer(key)
+    local server = _M.balancer:find(key)
+    if not server then
+        ngx_log(ngx.ERR, "not find shenyu server..")
+        return
+    end
+    ngx_balancer.set_current_peer(server)
+end
+return _M
diff --git a/lib/shenyu/register/zookeeper/connection.lua b/lib/shenyu/register/zookeeper/connection.lua
new file mode 100644
index 0000000..3298724
--- /dev/null
+++ b/lib/shenyu/register/zookeeper/connection.lua
@@ -0,0 +1,126 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local proto = require("shenyu.register.zookeeper.zk_proto")
+local struct = require("shenyu.register.core.struct")
+local tcp = ngx.socket.tcp
+local pack = struct.pack
+local unpack = struct.unpack
+local tbunpack = struct.tbunpack
+local ngx_log = ngx.log
+local _timeout = 60 * 1000
+local _M = {}
+local mt = { __index = _M }
+
+function _M.new(self)
+    local sock, err = tcp()
+    if not tcp then
+        return nil, err
+    end
+    return setmetatable({ sock = sock, timeout = _timeout }, mt)
+end
+
+function _M.connect(self, ip, port)
+    -- body
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized tcp."
+    end
+    local ok, err = sock:connect(ip, port)
+    if not ok then
+        ngx_log(ngx.DEBUG, "connect host:" .. ip .. err)
+        return ok, err
+    end
+    return ok, nil
+end
+
+function _M.write(self, req)
+    -- body
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized tpc."
+    end
+    return sock:send(req)
+end
+
+function _M.read(self, len)
+    -- body
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized tpc."
+    end
+    return sock:receive(len)
+end
+
+function _M.read_len(self)
+    local b, err = self:read(4)
+    if not b then
+        return nil, "error"
+    end
+    local len = tbunpack(unpack(">i", b))
+    return len
+end
+
+function _M.read_header(self)
+    local len = self:read_len()
+    local b, err = self:read(len)
+    if not b then
+        return nil, "error"
+    end
+    local h, end_index = proto.reply_header:unpack(b, 1)
+    if not h then
+        return nil, nil, 0
+    end
+    return h, b, end_index
+end
+
+function _M.close(self)
+    -- body
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized tpc."
+    end
+    sock.close()
+end
+
+function _M.set_timeout(self, timeout)
+    -- body
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+    sock:settimeout(timeout)
+end
+
+function _M.set_keepalive(self, ...)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+    
+    return sock:setkeepalive(...)
+end
+
+function _M.set_timeouts(self, connect_timeout, send_timeout, read_timeout)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+    
+    sock:settimeouts(connect_timeout, send_timeout, read_timeout)
+end
+
+return _M
diff --git a/lib/shenyu/register/zookeeper/zk_client.lua b/lib/shenyu/register/zookeeper/zk_client.lua
new file mode 100644
index 0000000..0da8366
--- /dev/null
+++ b/lib/shenyu/register/zookeeper/zk_client.lua
@@ -0,0 +1,203 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local util = require("shenyu.register.core.utils")
+local const = require("shenyu.register.zookeeper.zk_const")
+local proto = require("shenyu.register.zookeeper.zk_proto")
+local connection = require("shenyu.register.zookeeper.connection")
+local ngx_log = ngx.log
+local now = ngx.now
+local exiting = ngx.worker.exiting
+local sleep = ngx.sleep
+local strlen = string.len
+local _timeout = 60 * 1000
+local _M = {
+}
+local mt = { __index = _M }
+
+function _M.new(self)
+    --- @type table
+    local conn_, err = connection:new()
+    if not conn_ then
+        return nil, "initialized connection error" .. err
+    end
+    conn_:set_timeout(_timeout)
+    conn_:set_keepalive()
+    return setmetatable({ conn = conn_, children_listener = {} }, mt)
+end
+
+function _M.connect(self, host)
+    -- body
+    local conn = self.conn
+    ---@type table
+    local iptables = util.paras_host(host, ":")
+    local ip = iptables[1]
+    local port = iptables[2]
+    local byt = conn:connect(ip, port)
+    if not byt then
+        return nil, "connection error" .. host
+    end
+    local bytes = proto:serialize(proto.request_header, proto.connect_request)
+    local b, err = conn:write(bytes)
+    if not b then
+        return nil, "connection error " .. ip + ":" .. port
+    end
+    local len = conn:read_len()
+    if not len then
+        return nil, "error"
+    end
+    local bytes = conn:read(len)
+    if not bytes then
+        return nil, "connection read error"
+    end
+    local rsp = proto.connect_response:unpack(bytes, 1)
+    if not rsp then
+        return nil, "read connection response error"
+    end
+    self.xid = 0
+    local t = rsp.timeout
+    self.session_timeout = rsp.timeout
+    self.ping_time = (t / 3) / 1000
+    self.host = host
+    self.session_id = rsp.session_id
+    local tostring = "proto_ver:" .. rsp.proto_ver .. "," .. "timeout:" .. rsp.timeout .. "," .. "session_id:" .. util.long_to_hex_string(rsp.session_id)
+    ngx_log(ngx.INFO, tostring)
+    return true, nil
+end
+
+function _M.get_children(self, path)
+    return self:_get_children(path, 0)
+end
+
+function _M._get_children(self, path, is_watch)
+    local conn = self.conn
+    if not conn then
+        return nil, "not initialized connection"
+    end
+    local xid = self.xid + 1
+    local h = proto.request_header
+    h.xid = xid
+    h.type = const.ZOO_GET_CHILDREN
+    local r = proto.get_children_request
+    r.path = path
+    r.watch = is_watch
+    local req = proto:serialize(h, r)
+    local bytes, err = conn:write(req)
+    if not bytes then
+        return bytes, "write bytes error"
+    end
+    --  If other data is received, it means that the data of the _get_children command has not been received
+    :: continue ::
+    local rsp_header, bytes, end_index = conn:read_header()
+    if not rsp_header then
+        return nil, "read headler error"
+    end
+    if rsp_header.err ~= 0 then
+        ngx_log(ngx.ERR, "zookeeper remote error: " .. const.get_err_msg(rsp_header.err) .. "," .. path)
+        return nil, const.get_err_msg(rsp_header.err)
+    end
+    if strlen(bytes) > 16 and rsp_header.xid > 0 then
+        self.xid = rsp_header.xid + 1
+        local get_children_response = proto.get_children_response:unpack(bytes, end_index)
+        return {
+            xid = rsp_header.xid,
+            zxid = rsp_header.zxid,
+            path = get_children_response.paths
+        }
+    end
+    if rsp_header.xid == const.XID_PING then
+        goto
+        continue
+    end
+    return nil, "get_children error"
+end
+
+function _M.add_watch(self, path, listener)
+    -- body
+    local d, e = self:_get_children(path, 1)
+    if not d then
+        return d, e
+    end
+    self.watch = true
+    if not self.children_listener[path] then
+        self.children_listener[path] = listener
+    end
+    return d, nil
+end
+
+local function watch_event(self, event)
+    if not event then
+        return
+    end
+    local type = event.type
+    local path = event.paths[1]
+    if type == const.WATCH_NODE_CHILDREN_CHANGE
+            or type == const.WATCH_NODE_CREATED
+            or type == const.WATCH_NODE_DELETED then
+        local listener = self.children_listener[path]
+        if listener then
+            local d, e = self:add_watch(path, listener)
+            if d then
+                listener(d.path)
+            end
+        end
+    end
+end
+
+local function reply_read(self)
+    local conn = self.conn
+    local h = proto.request_header
+    h.xid = const.XID_PING
+    h.type = const.ZOO_PING_OP
+    local req = proto:serialize(h, proto.ping_request)
+    local ok, err = conn:write(req)
+    if ok then
+        local h, bytes, end_start = conn:read_header()
+        if h.xid == const.XID_PING then
+            ngx_log(
+                    ngx.DEBUG,
+                    "Got ping zookeeper response host:" ..
+                            self.host .. " for sessionId:" .. util.long_to_hex_string(self.session_id)
+            )
+        elseif h.xid == const.XID_WATCH_EVENT then
+            --decoding
+            local data = proto.watch_event:unpack(bytes, end_start)
+            watch_event(self, data)
+        end
+    end
+    return ok, err
+end
+
+function _M.watch_receive(self)
+    local last_time = 0
+    while true do
+        if exiting() then
+            self.conn.close()
+            return true
+        end
+        local can_ping = now() - last_time > self.ping_time
+        if can_ping then
+            local ok, err = reply_read(self)
+            if err then
+                return nil, err
+            end
+            last_time = now()
+        end
+        sleep(0.2)
+    end
+end
+
+return _M
diff --git a/lib/shenyu/register/zookeeper/zk_cluster.lua b/lib/shenyu/register/zookeeper/zk_cluster.lua
new file mode 100644
index 0000000..30afc7b
--- /dev/null
+++ b/lib/shenyu/register/zookeeper/zk_cluster.lua
@@ -0,0 +1,87 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations un
+local zkclient = require("shenyu.register.zookeeper.zk_client")
+local ngx_log = ngx.log
+local ipairs = ipairs
+local _M = {}
+local mt = {__index = _M}
+
+function _M.new(self, zk_config)
+    -- body
+    return setmetatable({servers = zk_config.servers}, mt)
+end
+
+function _M.connect(self)
+    local servers = self.servers
+    if not servers then
+        return nil, "servers is null"
+    end
+    -- initialize
+    ---@type
+    local client, err = zkclient:new()
+    if not client then
+        ngx_log(ngx.ERR, "Failed to initialize zk Client" .. err)
+        return nil, err
+    end
+    for _, _host in ipairs(servers) do
+        ngx_log(ngx.INFO, "try to connect to zookeeper host : " .. _host)
+        local ok, err = client:connect(_host)
+        if not ok then
+            ngx_log(ngx.INFO, "Failed to connect to zookeeper host : " .. _host .. err)
+        else
+            ngx_log(ngx.INFO, "Successful connection to zookeeper host : " .. _host)
+            self.client = client
+            return client
+        end
+    end
+    ngx_log(ngx.ERR, "Failed to connect to zookeeper")
+    return nil
+end
+
+function _M.get_children(self, path)
+    local client = self.client
+    if not client then
+        ngx_log(ngx.ERR, "conn not initialized")
+    end
+    local data, error = client:get_children(path)
+    if not data then
+        return nil, error
+    end
+    return data, nil
+end
+
+local function _watch_receive(self)
+    local client = self.client
+    if not client then
+        ngx_log(ngx.ERR, "conn not initialized")
+    end
+    return client:watch_receive()
+end
+
+function _M.add_watch(self, path, listener)
+    local client = self.client
+    if not client then
+        ngx_log(ngx.ERR, "conn not initialized")
+    end
+    local data, err = client:add_watch(path,listener)
+    if data then
+        listener(data.path)
+        return _watch_receive(self)
+    end
+    return data, err
+end
+
+return _M
diff --git a/lib/shenyu/register/zookeeper/zk_const.lua b/lib/shenyu/register/zookeeper/zk_const.lua
new file mode 100644
index 0000000..d3ec42e
--- /dev/null
+++ b/lib/shenyu/register/zookeeper/zk_const.lua
@@ -0,0 +1,82 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"), you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+local _M = {}
+
+-- XID
+_M.XID_WATCH_EVENT = -1
+_M.XID_PING = -2
+_M.XID_SET_WATCHES = -8
+--op code
+_M.ZOO_GET_CHILDREN = 8
+_M.ZOO_PING_OP = 11
+_M.ZOO_GET_CHILDREN2 = 12
+_M.ZOO_SET_WATCHES = 101
+_M.ZOO_ADD_WATCH = 106
+--watch type
+_M.WATCH_NONE = -1
+_M.WATCH_NODE_CREATED = 1
+_M.WATCH_NODE_DELETED = 2
+_M.WATCH_NODE_DATA_CHANGE = 3
+_M.WATCH_NODE_CHILDREN_CHANGE = 4
+_M.WATCH_DATA_WATCH_REMOVE = 5
+_M.WATCH_CHILD_WATCH_REMOVE = 6
+--cus const.
+_M.ZK_WATCH_PATH = "/shenyu/register/instance";
+--Definition of error codes.
+local error_code = {
+    "0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-100",
+    "-101", "-102", "-103", "-108", "-110", "-111", "-112",
+    "-113", "-114", "-115", "-118", "-119" }
+--err message.
+local error_msg = {
+    err0 = "Ok",
+    err1 = "System error",
+    err2 = "Runtime inconsistency",
+    err3 = "Data inconsistency",
+    err4 = "Connection loss",
+    err5 = "Marshalling error",
+    err6 = "Operation is unimplemented",
+    err7 = "Operation timeout",
+    err8 = "Invalid arguments",
+    err9 = "API errors.",
+    err101 = "Node does not exist",
+    err102 = "Not authenticated",
+    err103 = "Version conflict",
+    err108 = "Ephemeral nodes may not have children",
+    err110 = "The node already exists",
+    err111 = "The node has children",
+    err112 = "The session has been expired by the server",
+    err113 = "Invalid callback specified",
+    err114 = "Invalid ACL specified",
+    err115 = "Client authentication failed",
+    err118 = "Session moved to another server, so operation is ignored",
+    err119 = "State-changing request is passed to read-only server",
+}
+for i = 1, #error_code do
+    local cmd = "err" .. (error_code[i] * -1)
+    _M[cmd] = error_msg.cmd
+end
+
+function _M.get_err_msg(code)
+    if not code then
+        return "unknown"
+    end
+    return error_msg["err" .. (code * -1)]
+end
+
+return _M
\ No newline at end of file
diff --git a/lib/shenyu/register/zookeeper/zk_proto.lua b/lib/shenyu/register/zookeeper/zk_proto.lua
new file mode 100644
index 0000000..2eacefe
--- /dev/null
+++ b/lib/shenyu/register/zookeeper/zk_proto.lua
@@ -0,0 +1,157 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an"AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local struct = require("shenyu.register.core.struct")
+local pack = struct.pack
+local unpack = struct.unpack
+local tbunpack = struct.tbunpack
+local strbyte = string.byte
+local strlen = string.len
+local strsub = string.sub
+
+local _M = {}
+
+local _base = {
+    new = function(self, o)
+        o = o or {}
+        setmetatable(o, self)
+        return o
+    end
+}
+
+local _request_header =
+    _base:new {
+    xid = 0,
+    type = 0,
+    pack = function(self)
+        return pack(">II", self.xid, self.type)
+    end
+}
+
+local _get_children_request =
+    _base:new {
+    path = "",
+    watch = 0,
+    pack = function(self)
+        local path_len = strlen(self.path)
+        return pack(">ic" .. path_len .. "b", path_len, self.path, strbyte(self.watch))
+    end
+}
+
+local _ping_request =
+    _base:new {
+    pack = function(self)
+        local stream = {}
+        return table.concat(stream)
+    end
+}
+
+local _connect_request =
+    _base:new {
+    protocol_version = 0,
+    last_zxid_seen = 0,
+    timeout = 0,
+    session_id = 0,
+    password = "",
+    pack = function(self)
+        return pack(">lilic16", 0, 0, 0, 0, "")
+    end
+}
+
+function _M.serialize(self, h, request)
+    local h_bytes = h:pack()
+    local r_bytes = request:pack()
+    local len = h_bytes:len() + r_bytes:len()
+    local len_bytes = pack(">i", len)
+    return len_bytes .. h_bytes .. r_bytes
+end
+
+_M.get_children_request = _get_children_request
+_M.request_header = _request_header
+_M.ping_request = _ping_request
+_M.connect_request = _connect_request
+
+--basics of response.
+local _reply_header =
+    _base:new {
+    xid = 0, -- int
+    zxid = 0, -- long
+    err = 0, -- int
+    unpack = function(self, bytes, start_index)
+        local vars, end_index = unpack(">ili", bytes, start_index)
+        self.xid, self.zxid, self.err = tbunpack(vars)
+        return self, end_index
+    end
+}
+
+local _connect_response =
+    _base:new {
+    proto_ver = 0,
+    timeout = 0,
+    session_id = 0,
+    password = "",
+    unpack = function(self, bytes, start_index)
+        local vars, end_index = unpack(">iilS", bytes, start_index)
+        self.proto_ver, self.timeout, self.session_id, self.password = tbunpack(vars)
+        return self, end_index
+    end
+}
+
+local function unpack_strings(str)
+    local size = strlen(str)
+    local pos = 0
+    local str_set = {}
+    local index = 1
+    while size > pos do
+        local vars = unpack(">i", strsub(str, 1 + pos, 4 + pos))
+        local len = tbunpack(vars)
+        vars = unpack(">c" .. len, strsub(str, 5 + pos, 5 + pos + len - 1))
+        local s = tbunpack(vars)
+        str_set[index] = s
+        index = index + 1
+        pos = pos + len + 4
+    end
+    return str_set
+end
+
+local _get_children_response =
+    _base:new {
+    paths = {},
+    unpack = function(self, bytes, start_index)
+        self.paths = unpack_strings(strsub(bytes, 21))
+        return self
+    end
+}
+
+local _watch_event =
+    _base:new {
+    type = 0,
+    state = 0,
+    paths = {},
+    unpack = function(self, bytes, start_index)
+        local vars = unpack(">ii", bytes, start_index)
+        self.type, self.state = tbunpack(vars)
+        self.paths = unpack_strings(strsub(bytes, 25))
+        return self
+    end
+}
+
+_M.reply_header = _reply_header
+_M.connect_response = _connect_response
+_M.get_children_response = _get_children_response
+_M.watch_event = _watch_event
+
+return _M
diff --git a/rockspec/shenyu-nginx-main-0.rockspec b/rockspec/shenyu-nginx-main-0.rockspec
index f6ef077..df19e0d 100644
--- a/rockspec/shenyu-nginx-main-0.rockspec
+++ b/rockspec/shenyu-nginx-main-0.rockspec
@@ -15,6 +15,7 @@
     "lua-resty-balancer >= 0.04",
     "lua-resty-http >= 0.15",
     "lua-cjson = 2.1.0.6-1",
+    "stringy = 0.7-0",
 }
 
 build = {
@@ -23,5 +24,14 @@
       ["shenyu.register.etcd"] = "lib/shenyu/register/etcd.lua",
       ["shenyu.register.nacos"] = "lib/shenyu/register/nacos.lua",
       ["shenyu.register.balancer"] = "lib/shenyu/register/balancer.lua",
+      ["shenyu.register.zookeeper"] = "lib/shenyu/register/zookeeper.lua",
+      ["shenyu.register.zookeeper.connection"] = "lib/shenyu/register/zookeeper/connection.lua",
+      ["shenyu.register.zookeeper.zk_client"] = "lib/shenyu/register/zookeeper/zk_client.lua",
+      ["shenyu.register.zookeeper.zk_cluster"] = "lib/shenyu/register/zookeeper/zk_cluster.lua",
+      ["shenyu.register.zookeeper.zk_const"] = "lib/shenyu/register/zookeeper/zk_const.lua",
+      ["shenyu.register.zookeeper.zk_proto"] = "lib/shenyu/register/zookeeper/zk_proto.lua",
+      ["shenyu.register.core.string"] = "lib/shenyu/register/core/string.lua",
+      ["shenyu.register.core.struct"] = "lib/shenyu/register/core/struct.lua",
+      ["shenyu.register.core.utils"] = "lib/shenyu/register/core/utils.lua",
    }
 }
diff --git a/test/it/gateway/conf/nginx.conf b/test/it/case/etcd/conf/gateway.conf
similarity index 90%
rename from test/it/gateway/conf/nginx.conf
rename to test/it/case/etcd/conf/gateway.conf
index dc5795f..2d9863d 100644
--- a/test/it/gateway/conf/nginx.conf
+++ b/test/it/case/etcd/conf/gateway.conf
@@ -26,10 +26,13 @@
 env ETCD_SERVER_URL;
 
 http {
+    lua_shared_dict shenyu_storage 1m;
+
     init_worker_by_lua_block {
         local register = require("shenyu.register.etcd")
         register.init({
-            balancer_type = "chash",
+            shenyu_storage = ngx.shared.shenyu_storage,
+            balancer_type = "roundrobin",
             etcd_base_url = os.getenv("ETCD_SERVER_URL"),
         })
     }
diff --git a/test/it/mock-shenyu/conf/nginx.conf b/test/it/case/etcd/conf/mock-shenyu.conf
similarity index 100%
rename from test/it/mock-shenyu/conf/nginx.conf
rename to test/it/case/etcd/conf/mock-shenyu.conf
diff --git a/test/it/docker-compose.yml b/test/it/case/etcd/docker-compose.yml
similarity index 86%
rename from test/it/docker-compose.yml
rename to test/it/case/etcd/docker-compose.yml
index ae4944e..140001d 100644
--- a/test/it/docker-compose.yml
+++ b/test/it/case/etcd/docker-compose.yml
@@ -35,14 +35,15 @@
 
   instance1:
     build:
-      context: ./mock-shenyu
+      context: ../../mock-shenyu
     expose:
       - 9090
     environment:
       - APP_NAME=mock-shenyu-instance-1
       - ETCD_SERVER_URL=http://172.16.238.10:2379
-    ports:
-      - 9090:9090
+    volumes:
+      - ./conf/mock-shenyu.conf:/var/run/nginx.conf
+    entrypoint: ["openresty", "-c", "/var/run/nginx.conf"]
     restart: on-failure
     healthcheck:
       test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ]
@@ -58,12 +59,15 @@
 
   instance2:
     build:
-      context: ./mock-shenyu
+      context: ../../mock-shenyu
     expose:
       - 9090
     environment:
       - APP_NAME=mock-shenyu-instance-2
       - ETCD_SERVER_URL=http://172.16.238.10:2379
+    volumes:
+      - ./conf/mock-shenyu.conf:/var/run/nginx.conf
+    entrypoint: ["openresty", "-c", "/var/run/nginx.conf"]
     restart: on-failure
     healthcheck:
       test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ]
@@ -79,14 +83,13 @@
 
   gateway:
     build:
-      context: ../..
+      context: ../../../..
       dockerfile: ./test/it/gateway/Dockerfile
-    ports:
-      - 8080:8080
-    volumes:
-      - ./gateway/conf:/conf
     environment:
       - ETCD_SERVER_URL=http://172.16.238.10:2379
+    volumes:
+      - ./conf/gateway.conf:/var/run/nginx.conf
+    entrypoint: ["openresty", "-c", "/var/run/nginx.conf"]
     restart: on-failure
     healthcheck:
       test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080" ]
@@ -102,7 +105,7 @@
 
   consumer:
     build:
-      context: ../..
+      context: ../../../..
       dockerfile: ./test/it/consumer/Dockerfile
     depends_on:
       gateway:
diff --git a/test/it/gateway/conf/nginx.conf b/test/it/case/nacos/conf/gateway.conf
similarity index 73%
copy from test/it/gateway/conf/nginx.conf
copy to test/it/case/nacos/conf/gateway.conf
index dc5795f..88799d9 100644
--- a/test/it/gateway/conf/nginx.conf
+++ b/test/it/case/nacos/conf/gateway.conf
@@ -23,21 +23,26 @@
     worker_connections 1024;
 }
 
-env ETCD_SERVER_URL;
+env NACOS_SERVER_URL;
 
 http {
+    lua_shared_dict shenyu_storage 10m;
+
     init_worker_by_lua_block {
-        local register = require("shenyu.register.etcd")
+        local register = require("shenyu.register.nacos")
         register.init({
-            balancer_type = "chash",
-            etcd_base_url = os.getenv("ETCD_SERVER_URL"),
+            shenyu_storage = ngx.shared.shenyu_storage,
+            balancer_type = "roundrobin",
+            nacos_base_url = os.getenv("NACOS_SERVER_URL"),
+            username = "nacos",
+            password = "nacos",
         })
     }
 
     upstream shenyu {
         server 0.0.0.1;
         balancer_by_lua_block {
-            require("shenyu.register.etcd").pick_and_set_peer()
+            require("shenyu.register.nacos").pick_and_set_peer()
         }
     }
 
diff --git a/test/it/case/nacos/conf/mock-shenyu.conf b/test/it/case/nacos/conf/mock-shenyu.conf
new file mode 100644
index 0000000..87db981
--- /dev/null
+++ b/test/it/case/nacos/conf/mock-shenyu.conf
@@ -0,0 +1,136 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+worker_processes  1;
+daemon off;
+error_log /dev/stdout debug;
+
+events {
+    worker_connections 1024;
+}
+
+env APP_NAME;
+env NACOS_SERVER_URL;
+
+http {
+    lua_shared_dict status 1m;
+
+    server {
+        listen 9090;
+        location ~ /get {
+            content_by_lua_block {
+                ngx.say(ngx.var.server_addr)
+            }
+        }
+
+        location = /register {
+            content_by_lua_block {
+                local json = require("cjson.safe")
+                local httpc = require("resty.http").new()
+                local base_url = os.getenv("NACOS_SERVER_URL")
+
+                local resp, err = httpc:request_uri(base_url, {
+                    method = "POST",
+                    path = "/nacos/v1/auth/login",
+                    query = "username=nacos&password=nacos",
+                })
+                if not resp then
+                    ngx.say(err)
+                    ngx.exit(500)
+                end
+                ngx.log(ngx.INFO, resp.body)
+                if resp.status ~= 200 then
+                    ngx.say(resp.body)
+                    ngx.exit(500)
+                end
+                local token = json.decode(resp.body).accessToken
+
+                local resp, err = httpc:request_uri(base_url, {
+                    method = "POST",
+                    path = "/nacos/v1/ns/service",
+                    query = "serviceName=shenyu-instances&groupName=DEFAULT_GROUP&namespaceId=",
+                })
+                if not resp then
+                    ngx.say(err)
+                    ngx.exit(500)
+                end
+                ngx.log(ngx.INFO, resp.body)
+
+                local instance = "serviceName=shenyu-instances&groupName=DEFAULT_GROUP&namespaceId=&ip=" .. ngx.var.server_addr .. "&port=9090"
+                local resp, err = httpc:request_uri(base_url, {
+                    method = "POST",
+                    path = "/nacos/v1/ns/instance",
+                    query = instance,
+                })
+                if not resp then
+                    ngx.say(err)
+                    ngx.exit(500)
+                end
+                ngx.log(ngx.INFO, resp.body)
+                if resp.status ~= 200 then
+                    ngx.say(resp.body)
+                    ngx.exit(500)
+                end
+                local shdict = ngx.shared.status
+                if shdict:get("beat") then
+                    ngx.say("registered")
+                    return
+                end
+                shdict:set("beat", true)
+
+                local function beat(premature)
+                    local shdict = ngx.shared.status
+                    local flag = shdict:get("beat")
+                    if not flag or premature or ngx.worker.exiting() then
+                        return
+                    end
+                    local httpc = require("resty.http").new()
+                    local resp, err = httpc:request_uri(base_url, {
+                        method = "PUT",
+                        path = "/nacos/v1/ns/instance/beat",
+                        query = instance .. "&beat="
+                    })
+                    if not resp then
+                        ngx.say(err)
+                        return
+                    end
+                    ngx.log(ngx.INFO, resp.body)
+
+                    local ok, err = ngx.timer.at(3, beat)
+                    if not ok then
+                        ngx.log(ngx.ERR, "failed to create timer: ", err)
+                        return
+                    end
+                end
+
+                local ok, err = ngx.timer.at(0, beat)
+                if not ok then
+                    ngx.log(ngx.ERR, "failed to create timer: ", err)
+                end
+                ngx.say("register successful")
+            }
+        }
+
+        location = /unregister {
+            content_by_lua_block {
+                local shdict = ngx.shared.status
+                shdict:delete("beat")
+                ngx.say("unregister successful")
+            }
+        }
+    }
+}
diff --git a/test/it/docker-compose.yml b/test/it/case/nacos/docker-compose.yml
similarity index 67%
copy from test/it/docker-compose.yml
copy to test/it/case/nacos/docker-compose.yml
index ae4944e..395e18f 100644
--- a/test/it/docker-compose.yml
+++ b/test/it/case/nacos/docker-compose.yml
@@ -16,33 +16,36 @@
 version: "2.3"
 
 services:
-  etcd:
-    image: bitnami/etcd:3.4
+  nacos:
+    image: nacos/nacos-server:2.0.2
     expose:
-      - 2379
+      - 8848
+      - 8849
+      - 9848
+      - 9849
     environment:
-      - ALLOW_NONE_AUTHENTICATION=yes
-      - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
+      - MODE=standalone
     restart: on-failure
     healthcheck:
-      test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/2379" ]
+      test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8848" ]
       interval: 5s
       timeout: 60s
       retries: 120
     networks:
-      shenyu:
-        ipv4_address: 172.16.238.10
+      shenyu-nacos:
+        ipv4_address: 172.16.236.10
 
   instance1:
     build:
-      context: ./mock-shenyu
+      context: ../../mock-shenyu
     expose:
       - 9090
     environment:
       - APP_NAME=mock-shenyu-instance-1
-      - ETCD_SERVER_URL=http://172.16.238.10:2379
-    ports:
-      - 9090:9090
+      - NACOS_SERVER_URL=http://172.16.236.10:8848
+    volumes:
+      - ./conf/mock-shenyu.conf:/var/run/nginx.conf
+    entrypoint: ["openresty", "-c", "/var/run/nginx.conf"]
     restart: on-failure
     healthcheck:
       test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ]
@@ -50,20 +53,23 @@
       timeout: 60s
       retries: 120
     depends_on:
-      etcd:
+      nacos:
         condition: service_healthy
     networks:
-      shenyu:
-        ipv4_address: 172.16.238.11
+      shenyu-nacos:
+        ipv4_address: 172.16.236.11
 
   instance2:
     build:
-      context: ./mock-shenyu
+      context: ../../mock-shenyu
     expose:
       - 9090
     environment:
       - APP_NAME=mock-shenyu-instance-2
-      - ETCD_SERVER_URL=http://172.16.238.10:2379
+      - NACOS_SERVER_URL=http://172.16.236.10:8848
+    volumes:
+      - ./conf/mock-shenyu.conf:/var/run/nginx.conf
+    entrypoint: ["openresty", "-c", "/var/run/nginx.conf"]
     restart: on-failure
     healthcheck:
       test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9090" ]
@@ -71,22 +77,21 @@
       timeout: 60s
       retries: 120
     depends_on:
-      etcd:
+      nacos:
         condition: service_healthy
     networks:
-      shenyu:
-        ipv4_address: 172.16.238.12
+      shenyu-nacos:
+        ipv4_address: 172.16.236.12
 
   gateway:
     build:
-      context: ../..
+      context: ../../../..
       dockerfile: ./test/it/gateway/Dockerfile
-    ports:
-      - 8080:8080
-    volumes:
-      - ./gateway/conf:/conf
     environment:
-      - ETCD_SERVER_URL=http://172.16.238.10:2379
+      - NACOS_SERVER_URL=http://172.16.236.10:8848
+    volumes:
+      - ./conf/gateway.conf:/var/run/nginx.conf
+    entrypoint: ["openresty", "-c", "/var/run/nginx.conf"]
     restart: on-failure
     healthcheck:
       test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080" ]
@@ -94,15 +99,15 @@
       timeout: 60s
       retries: 120
     depends_on:
-      etcd:
+      nacos:
         condition: service_healthy
     networks:
-      shenyu:
-        ipv4_address: 172.16.238.13
+      shenyu-nacos:
+        ipv4_address: 172.16.236.13
 
   consumer:
     build:
-      context: ../..
+      context: ../../../..
       dockerfile: ./test/it/consumer/Dockerfile
     depends_on:
       gateway:
@@ -112,14 +117,14 @@
       instance2:
         condition: service_healthy
     networks:
-      shenyu:
-        ipv4_address: 172.16.238.20
+      shenyu-nacos:
+        ipv4_address: 172.16.236.20
 
 networks:
-  shenyu:
+  shenyu-nacos:
     driver: bridge
     ipam:
       driver: default
       config:
-        - subnet: 172.16.238.0/24
-          gateway: 172.16.238.1
+        - subnet: 172.16.236.0/24
+          gateway: 172.16.236.1
diff --git a/test/it/consumer/bin/entrypoint.sh b/test/it/consumer/bin/entrypoint.sh
index a78b8e3..c8749fb 100644
--- a/test/it/consumer/bin/entrypoint.sh
+++ b/test/it/consumer/bin/entrypoint.sh
@@ -16,68 +16,56 @@
 # See the License for the specific language governing permissions and
 # limitations under the License
 
+# 1: url, 2: condition, 3: message
+send() {
+  echo "send request to $1"
+  curl -s -D ./head -o response $1
+  rst=$(grep "$2" ./head)
+  if [[ -z "rst" ]]; then
+    echo "send $1 error: $3"
+    cat ./head
+    cat ./response
+    exit 100
+  fi
+}
 
-inst_addr_1=$(curl -s http://instance1:9090/get)
-if [[ -z "$inst_addr_1" ]]; then
-  echo "failed to start instance 1."
-  exit 129
-fi
+register() {
+  inst=$1
+  echo "${inst} registering..."
+  inst_addr=$(curl -s http://${inst}:9090/register)
+  if [[ -z "$inst_addr" ]]; then
+    echo "failed to start ${inst}."
+    exit 129
+  fi
+  echo "${inst} registered."
+}
 
-inst_addr_2=$(curl -s http://instance2:9090/get)
-if [[ -z "$inst_addr_2" ]]; then
-  echo "failed to start instance 2."
-  exit 129
-fi
+send "http://gateway:8080/get" "HTTP/1.1 500" "failed to set new environment for testing."
 
-rst=$(curl -Is http://gateway:8080/get | grep "HTTP/1.1 500")
-if [[ -z "$rst" ]]; then
-  echo "failed to set new environment for testing."
-  exit 128
-fi
-
-rst=$(curl -Is http://instance1:9090/register | grep "HTTP/1.1 200")
-echo $rst
-if [[ -z "$rst" ]]; then
-  echo "failed to register instance1 to etcd."
-  exit 128
-fi
-
-rst=$(curl -Is http://instance2:9090/register | grep "HTTP/1.1 200")
-echo $rst
-if [[ -z "$rst" ]]; then
-  echo "failed to register instance2 to etcd."
-  exit 128
-fi
+register "instance1"
+register "instance2"
 
 sleep 5
 
-curl -s http://gateway:8080/get
+send "http://instance1:9090/get" "HTTP/1.1 200" "failed to request instance1."
+send "http://instance2:9090/get" "HTTP/1.1 200" "failed to request instance2."
 
-rst=$(curl -Is http://gateway:8080/get | grep "HTTP/1.1 200")
-if [[ -z "$rst" ]]; then
-  echo "shenyu nginx module did not work."
-  exit 128
-fi
+send "http://gateway:8080/get" "HTTP/1.1 200" "shenyu nginx module did not work."
 
+echo "send requests to gateway."
 inst1=$(curl -s http://gateway:8080/get)
 inst2=$(curl -s http://gateway:8080/get)
-
-[[ "$inst1" == "$inst2" ]] || (echo "validation failed" && exit 128)
-
-# remove instance 1
-rst=$(curl -Is http://instance1:9090/unregister | grep "HTTP/1.1 200")
-if [[ -z "$rst" ]]; then
-  echo "failed to unregister instance1 to etcd."
+if [[ "$inst1" == "$inst2" ]]; then
+  echo "validation failed, inst1: ${inst1}, inst2: ${inst2}"
   exit 128
 fi
 
+# remove instance 1
+send "http://instance1:9090/unregister" "HTTP/1.1 200" "failed to unregister instance1 to register center."
+
 sleep 5
 
-rst=$(curl -Is http://gateway:8080/get | grep "HTTP/1.1 200")
-if [[ -z "$rst" ]]; then
-  echo "shenyu nginx module did not work right"
-  exit 128
-fi
+send "http://gateway:8080/get" "HTTP/1.1 200" "shenyu nginx module did not work right"
 
 rst=$(curl -s http://gateway:8080/get)
 if [[ "$rst" == "$inst_addr_1" ]]; then
@@ -91,3 +79,5 @@
 fi
 
 echo "validation successful"
+
+exit 200
diff --git a/test/it/gateway/Dockerfile b/test/it/gateway/Dockerfile
index 8942dd2..9975844 100644
--- a/test/it/gateway/Dockerfile
+++ b/test/it/gateway/Dockerfile
@@ -20,8 +20,5 @@
 
 COPY ./lib lib
 COPY ./rockspec rockspec
-COPY ./test/it/gateway/conf/nginx.conf conf/nginx.conf
 
 RUN luarocks make ./rockspec/shenyu-nginx-main-0.rockspec
-
-ENTRYPOINT openresty -c /shenyu-nginx/conf/nginx.conf
diff --git a/test/it/mock-shenyu/Dockerfile b/test/it/mock-shenyu/Dockerfile
index 46ca147..ff4c8ff 100644
--- a/test/it/mock-shenyu/Dockerfile
+++ b/test/it/mock-shenyu/Dockerfile
@@ -17,6 +17,3 @@
 FROM openresty/openresty:1.17.8.2-5-alpine-fat
 
 RUN luarocks install lua-resty-http
-COPY ./conf/nginx.conf /conf/nginx.conf
-
-ENTRYPOINT openresty -c /conf/nginx.conf