• YARP网关集成Steeltoe


        Yarp是微软开源的一个用.net实现的反向代理工具包 , 仓库地址 https://github.com/microsoft/reverse-proxy 

        官方默认使用配置文件配置路由,有老哥把它集成到数据库中做成了可配置热更新的方式 :  https://www.cnblogs.com/fanshaoO/p/14603159.html 

       由于部门使用spring cloud gateway作为网关,故想实现一个类似的功能 , 废话不多说,直接上代码 :

        SteeltoeLookupMiddleware :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Steeltoe.Discovery;
    using Yarp.ReverseProxy.Middleware;
    
    namespace Yarp.ReverseProxy.Steeltoe
    {
        internal class SteeltoeLookupMiddleware
        {
            private readonly RequestDelegate _next;
            private readonly ILogger _logger;
            private readonly SteeltoeProvider _steeltoeProvider;
            public SteeltoeLookupMiddleware(
            RequestDelegate next,
            ILogger<SteeltoeLookupMiddleware> logger,
            IDiscoveryClient discoveryClient = null)
            {
                _next = next ?? throw new ArgumentNullException(nameof(next));
                _logger = logger ?? throw new ArgumentNullException(nameof(logger));
                _steeltoeProvider = new SteeltoeProvider(discoveryClient);
            }
    
            public Task Invoke(HttpContext context)
            {
                var proxyFeature = context.GetRequiredProxyFeature();
    
                var cluster = proxyFeature.ClusterSnapshot.Options;
                if (cluster.Metadata == null || !cluster.Metadata.TryGetValue("ServiceId", out var serviceId))
                {
                    return _next(context);
                }
                var result = _steeltoeProvider.FindAffinitizedDestinations(context, serviceId);
                if (result.Status == Yarp.ReverseProxy.Service.SessionAffinity.AffinityStatus.OK)
                {
                    proxyFeature.AvailableDestinations = result.Destinations;
                }
                return _next(context);
            }
    
        }
    }

    SteeltoeProvider :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http;
    using Steeltoe.Common.Discovery;
    using Steeltoe.Discovery;
    using Yarp.ReverseProxy.Abstractions;
    using Yarp.ReverseProxy.RuntimeModel;
    using Yarp.ReverseProxy.Service.SessionAffinity;
    
    namespace Yarp.ReverseProxy.Steeltoe
    {
        public class SteeltoeProvider
        {
            public SteeltoeProvider(IDiscoveryClient discoveryClient = null)
            {
                _discoveryClient = discoveryClient;
            }
    
            private readonly IDiscoveryClient _discoveryClient;
    
    
            public AffinityResult FindAffinitizedDestinations(HttpContext context, string serviceId)
            {
                var instances = _discoveryClient?.GetInstances(serviceId);
    
                if (instances == null)
                {
                    return new AffinityResult(null, AffinityStatus.AffinityKeyExtractionFailed);
                }
    
                if (instances.Count == 0)
                {
                    return new AffinityResult(null, AffinityStatus.AffinityKeyNotSet);
                }
    
                var destinationInfos = instances.Select(CreateDestinationInfo).ToList();
    
                return new AffinityResult(destinationInfos, AffinityStatus.OK);
            }
    
            private DestinationInfo CreateDestinationInfo(IServiceInstance serviceInstance)
            {
                var destinationId = GetInstanceId(serviceInstance) ?? serviceInstance.ServiceId;
                var destinationInfo = new DestinationInfo(destinationId);
                var destination = new Destination
                {
                    Address = serviceInstance.Uri.ToString()
                };
                var config = new DestinationConfig(destination);
                SetDestinationConfig(destinationInfo, config);
                return destinationInfo;
            }
    
            private void SetDestinationConfig(DestinationInfo destinationInfo, DestinationConfig config)
            {
                _setDestinationConfigInvoker.Invoke(destinationInfo, config);
            }
    
            private readonly Action<DestinationInfo, DestinationConfig> _setDestinationConfigInvoker = CreateDestinationConfigInvoker();
    
            private static Action<DestinationInfo, DestinationConfig> CreateDestinationConfigInvoker()
            {
                var destinationInfoInstance = Expression.Parameter(typeof(DestinationInfo), "destinationInfo");
                var destinationConfigInstance = Expression.Parameter(typeof(DestinationConfig), "destinationConfig");
                var property = typeof(DestinationInfo).GetProperty("Config");
                var body = Expression.Assign(Expression.Property(destinationInfoInstance, property), destinationConfigInstance);
                return Expression.Lambda<Action<DestinationInfo, DestinationConfig>>(body, destinationInfoInstance, destinationConfigInstance).Compile();
            }
    
            private readonly System.Collections.Concurrent.ConcurrentDictionary<Type, Func<IServiceInstance, string>> _getInstanceIdMap = new System.Collections.Concurrent.ConcurrentDictionary<Type, Func<IServiceInstance, string>>();
    
            private string GetInstanceId(IServiceInstance serviceInstance)
            {
                if (_getInstanceIdMap.TryGetValue(serviceInstance.GetType(), out var invoker))
                {
                    return invoker?.Invoke(serviceInstance);
                }
                invoker = _getInstanceIdMap.GetOrAdd(serviceInstance.GetType(), type =>
                  {
                      var property = type.GetProperty("InstanceId");
                      if (property == null || !property.CanRead)
                      {
                          return null;
                      }
                      var instance = Expression.Parameter(typeof(IServiceInstance), "instance");
                      var convertInstance = Expression.Convert(instance, type);
                      var body = Expression.Property(convertInstance, property);
                      return Expression.Lambda<Func<IServiceInstance, string>>(body, instance).Compile();
    
                  });
                return invoker?.Invoke(serviceInstance);
            }
    
        }
    }

    使用方式也很简单 : 

                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapReverseProxy(proxyPipeline =>
                    {
                        proxyPipeline.UseAffinitizedDestinationLookup();
                        proxyPipeline.UseMiddleware<SteeltoeLookupMiddleware>();
                        proxyPipeline.UseProxyLoadBalancing();
                        proxyPipeline.UsePassiveHealthChecks();
                    })
                   ;
                });

    需要注意几点:

    1.为了避免所有集群都走注册中心,需要显式的配置一下 ServiceId在MetaData中

    2.如果不配置 Destinations节点 , 返回的集群为null

    {
      "ReverseProxy": {
        "Routes": [
          {
            "serviceApiInvoke": {
              "ClusterId": "serviceApiInvoke",
              "Match": {
                "Path": "/service-api-invoke/{*any}"
              },
              "Transforms": {
                { "PathRemovePrefix": "/service-api-invoke" }
              }
            }
          }
        ],
        "Clusters": {
          "serviceApiInvoke": {
            "Destinations": {
              "cluster1/destination1": {
                "Address": "https://example.com/"
              },
              "MetaData": {
                "ServiceId": "service-api-invoke"
              }
            }
          }
        }
      }
    }

    这样能初步替换spring cloud gateway了``

  • 相关阅读:
    Hexo+Github 搭建一个自己的博客
    vue中sessionStorage存储的用法和问题
    vue 页面刷新
    vue渲染完页面后div滚动条定位在底部
    vue 定义全局函数
    vue filter过滤器用法
    vue中bus.$on事件被多次绑定
    vue中引入jQuery的方法
    vue2.0传值方式:父传子、子传父、非父子组件、路由跳转传参
    vue打包后显示为空白页的解决办法
  • 原文地址:https://www.cnblogs.com/pokemon/p/14677024.html
Copyright © 2020-2023  润新知