This is an automated email from the ASF dual-hosted git repository.

xiaoyu pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-shenyu-client-dotnet.git


The following commit(s) were added to refs/heads/main by this push:
     new c2e9175  support register client via zookeeper (#9)
c2e9175 is described below

commit c2e91756f0e3970c9f7528c4257b015c0d7f1ac2
Author: Han Gao <[email protected]>
AuthorDate: Thu May 12 19:38:21 2022 +0800

    support register client via zookeeper (#9)
---
 .../Services/ShenyuStartupService.cs               |   4 +-
 .../Utils/ShenyuCollectionExtensions.cs            |  26 ++-
 .../Apache.ShenYu.Client.csproj                    |   1 +
 .../Apache.ShenYu.Client/Options/ShenyuOptions.cs  |  11 +-
 .../Registers/IShenyuRegister.cs                   |   2 +-
 .../Registers/ShenyuHttpRegister.cs                |  25 +--
 .../Registers/ShenyuZookeeperRegister.cs           | 196 +++++++++++++++++++++
 .../Utils/CollectionExtentions.cs                  |  69 ++++++++
 client/Apache.ShenYu.Client/Utils/Constants.cs     |  31 +++-
 .../Utils/RegisterTypeEnums.cs                     |   4 +-
 10 files changed, 350 insertions(+), 19 deletions(-)

diff --git a/client/Apache.ShenYu.AspNetCore/Services/ShenyuStartupService.cs 
b/client/Apache.ShenYu.AspNetCore/Services/ShenyuStartupService.cs
index 9801cac..2388afc 100644
--- a/client/Apache.ShenYu.AspNetCore/Services/ShenyuStartupService.cs
+++ b/client/Apache.ShenYu.AspNetCore/Services/ShenyuStartupService.cs
@@ -28,6 +28,8 @@ using Apache.ShenYu.Client.Options;
 using Apache.ShenYu.Client.Registers;
 using Microsoft.AspNetCore.Hosting.Server;
 using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc.ApplicationParts;
 using Microsoft.AspNetCore.Mvc.Controllers;
@@ -190,4 +192,4 @@ namespace Apache.ShenYu.AspNetCore.Services
             return shenyuClientAttribute != null ? shenyuClientAttribute.Path 
: routePath;
         }
     }
-}
+}
\ No newline at end of file
diff --git 
a/client/Apache.ShenYu.AspNetCore/Utils/ShenyuCollectionExtensions.cs 
b/client/Apache.ShenYu.AspNetCore/Utils/ShenyuCollectionExtensions.cs
index 2dbff1f..fc47a3b 100644
--- a/client/Apache.ShenYu.AspNetCore/Utils/ShenyuCollectionExtensions.cs
+++ b/client/Apache.ShenYu.AspNetCore/Utils/ShenyuCollectionExtensions.cs
@@ -19,6 +19,7 @@ using System;
 using Apache.ShenYu.AspNetCore.Services;
 using Apache.ShenYu.Client.Options;
 using Apache.ShenYu.Client.Registers;
+using Apache.ShenYu.Client.Utils;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 
@@ -40,9 +41,32 @@ namespace Apache.ShenYu.AspNetCore.Utils
                 throw new ArgumentNullException(nameof(services));
             }
 
+            var shenyuOptions = new ShenyuOptions();
+            configuration.GetSection(ShenyuOptions.Shenyu).Bind(shenyuOptions);
+
             
services.Configure<ShenyuOptions>(configuration.GetSection(ShenyuOptions.Shenyu));
             services.AddHostedService<ShenyuStartupService>();
-            services.AddSingleton<IShenyuRegister, ShenyuHttpRegister>();
+
+            if (shenyuOptions.Register.RegisterType.Equals(""))
+            {
+                services.AddSingleton<IShenyuRegister, ShenyuHttpRegister>();
+            }
+
+            switch (shenyuOptions.Register.RegisterType)
+            {
+                case Constants.RegisterType.Http:
+                {
+                    services.AddSingleton<IShenyuRegister, 
ShenyuHttpRegister>();
+                    break;
+                }
+                case Constants.RegisterType.Zookeeper:
+                {
+                    services.AddSingleton<IShenyuRegister, 
ShenyuZookeeperRegister>();
+                    break;
+                }
+                default:
+                    throw new Exception($"not supported type 
{shenyuOptions.Register.RegisterType}");
+            }
         }
     }
 }
diff --git a/client/Apache.ShenYu.Client/Apache.ShenYu.Client.csproj 
b/client/Apache.ShenYu.Client/Apache.ShenYu.Client.csproj
index 1ccd434..1ac1d3b 100644
--- a/client/Apache.ShenYu.Client/Apache.ShenYu.Client.csproj
+++ b/client/Apache.ShenYu.Client/Apache.ShenYu.Client.csproj
@@ -46,5 +46,6 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" 
Version="2.2.0" />
     <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
+    <PackageReference Include="ZooKeeperNetEx" Version="3.4.12.4" />
   </ItemGroup>
 </Project>
diff --git a/client/Apache.ShenYu.Client/Options/ShenyuOptions.cs 
b/client/Apache.ShenYu.Client/Options/ShenyuOptions.cs
index aa9504c..2d24bc5 100644
--- a/client/Apache.ShenYu.Client/Options/ShenyuOptions.cs
+++ b/client/Apache.ShenYu.Client/Options/ShenyuOptions.cs
@@ -15,6 +15,8 @@
  * limitations under the License.
  */
 
+using System.Collections.Generic;
+
 namespace Apache.ShenYu.Client.Options
 {
     public class ShenyuOptions
@@ -26,10 +28,15 @@ namespace Apache.ShenYu.Client.Options
 
         public class RegisterOptions
         {
+            /// <summary>
+            /// register types, including http, zookeeper.
+            /// </summary>
             public string RegisterType { get; set; } = "http";
             public string ServerList { get; set; }
-            public string UserName { get; set; }
-            public string Password { get; set; }
+            
+            public Dictionary<string, string> Props { get; set; }
+            
+            
         }
 
         public class ClientOptions
diff --git a/client/Apache.ShenYu.Client/Registers/IShenyuRegister.cs 
b/client/Apache.ShenYu.Client/Registers/IShenyuRegister.cs
index ddd01af..361597e 100644
--- a/client/Apache.ShenYu.Client/Registers/IShenyuRegister.cs
+++ b/client/Apache.ShenYu.Client/Registers/IShenyuRegister.cs
@@ -44,6 +44,6 @@ namespace Apache.ShenYu.Client.Registers
         /// <summary>
         /// Close.
         /// </summary>
-        void Close();
+        Task Close();
     }
 }
diff --git a/client/Apache.ShenYu.Client/Registers/ShenyuHttpRegister.cs 
b/client/Apache.ShenYu.Client/Registers/ShenyuHttpRegister.cs
index 6a67a51..71786ec 100644
--- a/client/Apache.ShenYu.Client/Registers/ShenyuHttpRegister.cs
+++ b/client/Apache.ShenYu.Client/Registers/ShenyuHttpRegister.cs
@@ -32,9 +32,7 @@ namespace Apache.ShenYu.Client.Registers
 {
     public class ShenyuHttpRegister : IShenyuRegister
     {
-        public string UserName { get; set; }
-        public string Password { get; set; }
-        public List<string> ServerList { get; set; }
+        private List<string> ServerList { get; set; }
 
         private Dictionary<string, string> AccessTokens { get; set; }
 
@@ -42,6 +40,8 @@ namespace Apache.ShenYu.Client.Registers
         private URIRegisterDTO _uriRegisterDto;
         private ShenyuOptions _shenyuOptions;
         private readonly ILogger<ShenyuHttpRegister> _logger;
+        private string _userName;
+        private string _password;
 
         public ShenyuHttpRegister(ILogger<ShenyuHttpRegister> logger)
         {
@@ -51,8 +51,8 @@ namespace Apache.ShenYu.Client.Registers
         public async Task Init(ShenyuOptions shenyuOptions)
         {
             this._shenyuOptions = shenyuOptions;
-            this.UserName = this._shenyuOptions.Register.UserName;
-            this.Password = this._shenyuOptions.Register.Password;
+            
this._shenyuOptions.Register.Props.TryGetValue(Constants.RegisterConstants.UserName,
 out this._userName);
+            
this._shenyuOptions.Register.Props.TryGetValue(Constants.RegisterConstants.Password,
 out this._password);
             this.ServerList = 
this._shenyuOptions.Register.ServerList?.Split(',').ToList();
             this.AccessTokens = new Dictionary<string, string>();
             this._client = new HttpClient();
@@ -61,16 +61,16 @@ namespace Apache.ShenYu.Client.Registers
 
         public async Task PersistInterface(MetaDataRegisterDTO metadata)
         {
-            await this.DoRegister(metadata, Constants.MetaPath, 
RegisterTypeEnums.MetaData);
+            await this.DoRegister(metadata, Constants.MetaPath, 
Constants.MetaType);
         }
 
         public async Task PersistURI(URIRegisterDTO registerDTO)
         {
-            await this.DoRegister(registerDTO, Constants.URIPath, 
RegisterTypeEnums.Uri);
+            await this.DoRegister(registerDTO, Constants.UriPath, 
Constants.UriType);
             this._uriRegisterDto = registerDTO;
         }
 
-        public void Close()
+        public Task Close()
         {
             throw new NotImplementedException();
         }
@@ -95,7 +95,10 @@ namespace Apache.ShenYu.Client.Registers
         private async Task SetAccessToken(string server)
         {
             var builder = new UriBuilder(server)
-                { Path = Constants.LoginPath, Query = 
$"userName={this.UserName}&password={this.Password}" };
+            {
+                Path = Constants.LoginPath,
+                Query = $"userName={this._userName}&password={this._password}"
+            };
             var resp = await this._client.GetStringAsync(builder.ToString());
             var jObject = JObject.Parse(resp);
             var token = jObject["data"]?["token"].ToString();
@@ -103,7 +106,7 @@ namespace Apache.ShenYu.Client.Registers
             this.AccessTokens.Add(server, token);
         }
 
-        private async Task DoRegister<T>(T t, string path, RegisterTypeEnums 
type)
+        private async Task DoRegister<T>(T t, string path, string type)
         {
             foreach (var server in this.ServerList)
             {
@@ -131,7 +134,7 @@ namespace Apache.ShenYu.Client.Registers
                 }
                 else
                 {
-                    this._logger.LogWarning("failed to register type: {}, 
content: {}", type, content);
+                    this._logger.LogWarning("failed to register type: {}, 
content: {}", type, content, resp);
                 }
             }
         }
diff --git a/client/Apache.ShenYu.Client/Registers/ShenyuZookeeperRegister.cs 
b/client/Apache.ShenYu.Client/Registers/ShenyuZookeeperRegister.cs
new file mode 100644
index 0000000..1539918
--- /dev/null
+++ b/client/Apache.ShenYu.Client/Registers/ShenyuZookeeperRegister.cs
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Apache.ShenYu.Client.Models.DTO;
+using Apache.ShenYu.Client.Options;
+using Apache.ShenYu.Client.Utils;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using org.apache.zookeeper;
+
+namespace Apache.ShenYu.Client.Registers
+{
+    public class ShenyuZookeeperRegister : IShenyuRegister
+    {
+        private readonly ILogger<ShenyuZookeeperRegister> _logger;
+        private string _serverList;
+        private ShenyuOptions _shenyuOptions;
+        private int _sessionTimeout;
+        private ZooKeeper _zkClient;
+        private Dictionary<string, string> _nodeDataMap = new 
Dictionary<string, string>();
+
+        public ShenyuZookeeperRegister(ILogger<ShenyuZookeeperRegister> logger)
+        {
+            _logger = logger;
+        }
+
+        public Task Init(ShenyuOptions shenyuOptions)
+        {
+            this._shenyuOptions = shenyuOptions;
+            this._serverList = shenyuOptions.Register.ServerList;
+            this._sessionTimeout =
+                Convert.ToInt32(
+                    
this._shenyuOptions.Register.Props.GetValueOrDefault(Constants.RegisterConstants.SessionTimeout,
+                        "3000"));
+            this._zkClient = CreateClient(this._serverList, 
this._sessionTimeout, new StatWatcher(this));
+            return Task.CompletedTask;
+        }
+
+        public async Task PersistInterface(MetaDataRegisterDTO metadata)
+        {
+            // build metadata path
+            string rpcType = metadata.rpcType;
+            string contextPath = BuildContextNodePath(metadata.contextPath, 
metadata.appName);
+
+            // create parent node
+            string parentPath = 
$"/shenyu/register/metadata/{metadata.rpcType}/{contextPath}";
+            await _zkClient.CreateWithParentAsync(parentPath, null, 
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+
+            // create or set metadata node
+            string nodeName = BuildMetadataNodeName(metadata);
+            string nodePath = $"{parentPath}/{nodeName}";
+
+            var existStat = await _zkClient.existsAsync(nodePath);
+            // create node
+            var metadataStr = JsonConvert.SerializeObject(metadata, 
Formatting.None, new JsonSerializerSettings
+            {
+                NullValueHandling = NullValueHandling.Ignore
+            });
+            if (existStat == null)
+            {
+                await _zkClient.createAsync(nodePath, 
Encoding.UTF8.GetBytes(metadataStr), ZooDefs.Ids.OPEN_ACL_UNSAFE,
+                    CreateMode.PERSISTENT);
+            }
+            else
+            {
+                // update node
+                await _zkClient.setDataAsync(nodePath, 
Encoding.UTF8.GetBytes(metadataStr));
+            }
+        }
+
+        public async Task PersistURI(URIRegisterDTO registerDTO)
+        {
+            // build uri path
+            string contextPath = BuildContextNodePath(registerDTO.contextPath, 
registerDTO.appName);
+            string parentPath =
+                $"/shenyu/register/uri/{registerDTO.rpcType}/{contextPath}";
+            await _zkClient.CreateWithParentAsync(parentPath, null, 
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+
+            string nodePath = 
$"{parentPath}/{registerDTO.host}:{registerDTO.port}";
+
+            // create ephemeral node
+            var existStat = await _zkClient.existsAsync(nodePath, null);
+            if (existStat == null)
+            {
+                var uriRegString = JsonConvert.SerializeObject(registerDTO, 
Formatting.None, new JsonSerializerSettings
+                {
+                    NullValueHandling = NullValueHandling.Ignore
+                });
+                _nodeDataMap[nodePath] = uriRegString;
+                await _zkClient.createAsync(nodePath, 
Encoding.UTF8.GetBytes(uriRegString), ZooDefs.Ids.OPEN_ACL_UNSAFE,
+                    CreateMode.EPHEMERAL);
+            }
+        }
+
+        public async Task Close()
+        {
+            await this._zkClient.closeAsync();
+        }
+
+        private ZooKeeper CreateClient(string connString, int sessionTimeout, 
Watcher watcher, string chroot = null)
+        {
+            var zk = new ZooKeeper(connString, sessionTimeout, watcher);
+
+            // waiting for connection finished
+            Thread.Sleep(1000);
+            while (zk.getState() == ZooKeeper.States.CONNECTING)
+            {
+                Thread.Sleep(1000);
+            }
+
+            var state = zk.getState();
+            if (state != ZooKeeper.States.CONNECTED && state != 
ZooKeeper.States.CONNECTEDREADONLY)
+            {
+                throw new Exception($"failed to connect to zookeeper endpoint: 
{connString}");
+            }
+
+            return zk;
+        }
+
+        private string BuildContextNodePath(string contextPath, string appName)
+        {
+            return string.IsNullOrEmpty(contextPath)
+                ? appName
+                : (contextPath.StartsWith("/")
+                    ? contextPath.Substring(1)
+                    : contextPath);
+        }
+
+        private string BuildMetadataNodeName(MetaDataRegisterDTO metadata)
+        {
+            string nodeName;
+            string rpcType = metadata.rpcType;
+
+            if (Constants.RegisterRpcType.Http.Equals(rpcType) || 
Constants.RegisterRpcType.SpringCloud.Equals(rpcType))
+            {
+                nodeName = string.Join(Constants.SelectorJoinRule, 
metadata.contextPath,
+                    metadata.ruleName.Replace(Constants.PathSeparator, 
Constants.SelectorJoinRule));
+            }
+            else
+            {
+                nodeName = string.Join(".", metadata.serviceName, 
metadata.methodName);
+            }
+
+            return nodeName.StartsWith(Constants.PathSeparator) ? 
nodeName.Substring(1) : nodeName;
+        }
+
+        class StatWatcher : Watcher
+        {
+            private readonly ShenyuZookeeperRegister _register;
+
+            public StatWatcher(ShenyuZookeeperRegister register)
+            {
+                _register = register;
+            }
+
+            public override async Task process(WatchedEvent @event)
+            {
+                var state = @event.getState();
+
+                if (Event.KeeperState.SyncConnected.Equals(state))
+                {
+                    foreach (var node in this._register._nodeDataMap)
+                    {
+                        var existStat = await 
this._register._zkClient.existsAsync(node.Key);
+                        if (existStat != null)
+                        {
+                            await 
this._register._zkClient.CreateWithParentAsync(node.Key,
+                                Encoding.UTF8.GetBytes(node.Value),
+                                ZooDefs.Ids.OPEN_ACL_UNSAFE, 
CreateMode.EPHEMERAL);
+                            this._register._logger.LogInformation("zookeeper 
client register success: {}", node.Value);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/client/Apache.ShenYu.Client/Utils/CollectionExtentions.cs 
b/client/Apache.ShenYu.Client/Utils/CollectionExtentions.cs
new file mode 100644
index 0000000..e44c1d7
--- /dev/null
+++ b/client/Apache.ShenYu.Client/Utils/CollectionExtentions.cs
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using org.apache.zookeeper;
+using org.apache.zookeeper.data;
+
+namespace Apache.ShenYu.Client.Utils
+{
+    public static class CollectionExtentions
+    {
+        public static TValue GetValueOrDefault<TKey, TValue>(
+            this IDictionary<TKey, TValue> dictionary,
+            TKey key,
+            TValue defaultValue)
+        {
+            return dictionary.TryGetValue(key, out var value) ? value : 
defaultValue;
+        }
+
+        public static async Task CreateWithParentAsync(
+            this ZooKeeper zooKeeper,
+            string path,
+            byte[] data,
+            List<ACL> acl,
+            CreateMode createMode
+            )
+        {
+            var paths = path.Split('/');
+            var cur = "";
+            foreach (var item in paths)
+            {
+                if (string.IsNullOrEmpty(item))
+                {
+                    continue;
+                }
+                cur += $"/{item}";
+                var existStat = await zooKeeper.existsAsync(cur, null);
+                if (existStat != null)
+                {
+                    continue;
+                }
+
+                if (cur.Equals(path))
+                {
+                    await zooKeeper.createAsync(cur, data, acl, createMode);
+                }
+                else
+                {
+                    await zooKeeper.createAsync(cur, null, acl, createMode);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/client/Apache.ShenYu.Client/Utils/Constants.cs 
b/client/Apache.ShenYu.Client/Utils/Constants.cs
index f8e5885..684631a 100644
--- a/client/Apache.ShenYu.Client/Utils/Constants.cs
+++ b/client/Apache.ShenYu.Client/Utils/Constants.cs
@@ -21,7 +21,36 @@ namespace Apache.ShenYu.Client.Utils
     {
         public const string LoginPath = "/platform/login";
         public const string XAccessToken = "X-Access-Token";
-        public const string URIPath = "/shenyu-client/register-uri";
+
+        public const string UriPath = "/shenyu-client/register-uri";
         public const string MetaPath = "/shenyu-client/register-metadata";
+
+        public const string MetaType = "metadata";
+        public const string UriType = "uri";
+        
+        public const string SelectorJoinRule = "-";
+        public const string PathSeparator = "/";
+        public const string DotSeparator = ".";
+
+        public class RegisterType
+        {
+            public const string Http = "http";
+            public const string Zookeeper = "zookeeper";
+        }
+        
+        public class RegisterRpcType
+        {
+            public const string Http = "http";
+            public const string SpringCloud = "springCloud";
+        }
+        
+        public class RegisterConstants
+        {
+            public const string UserName = "UserName";
+            public const string Password = "Password";
+            
+            public const string SessionTimeout = "SessionTimeout";
+            public const string ConnectionTimeout = "SessionTimeout";
+        }
     }
 }
diff --git a/client/Apache.ShenYu.Client/Utils/RegisterTypeEnums.cs 
b/client/Apache.ShenYu.Client/Utils/RegisterTypeEnums.cs
index 9ac9325..395bdd2 100644
--- a/client/Apache.ShenYu.Client/Utils/RegisterTypeEnums.cs
+++ b/client/Apache.ShenYu.Client/Utils/RegisterTypeEnums.cs
@@ -19,7 +19,7 @@ namespace Apache.ShenYu.Client.Utils
 {
     public enum RegisterTypeEnums
     {
-        MetaData,
-        Uri
+        Http,
+        Zookeeper,
     }
 }
\ No newline at end of file

Reply via email to