如何限制每个客户端的WCF服务 [英] How to throttle WCF service per client

查看:268
本文介绍了如何限制每个客户端的WCF服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一项服务,该服务仅在少数几个选定的客户端上可以通过Internet公开.但是,我不希望一个客户端能够如此频繁地调用服务,以至于它们阻止了另一个客户端调用该服务或在合理的时间内得到响应.我意识到WCF内置了许多限制配置设置,但我知道这些仅适用于整个服务.

I'm developing a service that will be exposed on the internet to only a few select clients. However I don't want one client to be able to call the service so often that they prevent another client calling the service, or being responded to within a reasonable time. I realise that WCF has a number of throttling configuration settings built in, but I understand that these are only for the service as a whole.

是否有任何内置机制可以使我配置服务,使得单个客户端只能执行(例如)10个并发呼叫或类似呼叫?

Is there any built in mechanism that will enable me to configure the service such that a single client can only carry out (eg.) 10 concurrent calls or similar?

这个问题与这里的另一个问题有关:

This question is related to another question here:

最佳方法用很少的客户端保护互联网上的WCF服务

我仍在尝试确定是否需要以及如何确定单个客户.

where I'm still trying to determine if I need to, and how I will identify individual clients.

推荐答案

首先,如果您使用某种负载均衡器,则最好在此处实现它.例如,NGINX具有速率限制功能: http://nginx.org/en/docs/http/ngx_http_limit_req_module.html .

Firstly if you are using some kind of load balancer it is best to implement it there. For instance NGINX have rate limiting capabilities: http://nginx.org/en/docs/http/ngx_http_limit_req_module.html .

第二,您应该考虑使用称为动态IP限制的内置IIS速率限制功能:

Secondly you should consider using built in IIS rate limiting capabilities called dynamic IP restrictions: http://www.iis.net/learn/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-address-restrictions .

如果这两种方法都不够用,因为您需要为此使用自定义逻辑,则可以始终在应用程序级别上实现它.这可以通过多种方式完成.

If both of this are not enough for you because you need custom logic for this you can always implement it on the application level. This can be done in multiple ways.

让我们从一些可重用的速率限制逻辑开始:

Let's start with some reusable rate limiting logic:

public interface IRateLimiter
{
    bool ShouldLimit(string key);

    HttpStatusCode LimitStatusCode { get; }
}

public interface IRateLimiterConfiguration
{
    int Treshhold { get; set; }
    TimeSpan TimePeriod { get; set; }
    HttpStatusCode LimitStatusCode { get; set; }
}

public class RateLimiterConfiguration : System.Configuration.ConfigurationSection, IRateLimiterConfiguration
{
    private const string TimePeriodConst = "timePeriod";
    private const string LimitStatusCodeConst = "limitStatusCode";
    private const string TreshholdConst = "treshhold";
    private const string RateLimiterTypeConst = "rateLimiterType";

    [ConfigurationProperty(TreshholdConst, IsRequired = true, DefaultValue = 10)]
    public int Treshhold
    {
        get { return (int)this[TreshholdConst]; }
        set { this[TreshholdConst] = value; }
    }

    [ConfigurationProperty(TimePeriodConst, IsRequired = true)]
    [TypeConverter(typeof(TimeSpanConverter))]
    public TimeSpan TimePeriod
    {
        get { return (TimeSpan)this[TimePeriodConst]; }
        set { this[TimePeriodConst] = value; }
    }

    [ConfigurationProperty(LimitStatusCodeConst, IsRequired = false, DefaultValue = HttpStatusCode.Forbidden)]
    public HttpStatusCode LimitStatusCode
    {
        get { return (HttpStatusCode)this[LimitStatusCodeConst]; }
        set { this[LimitStatusCodeConst] = value; }
    }

    [ConfigurationProperty(RateLimiterTypeConst, IsRequired = true)]
    [TypeConverter(typeof(TypeNameConverter))]
    public Type RateLimiterType
    {
        get { return (Type)this[RateLimiterTypeConst]; }
        set { this[RateLimiterTypeConst] = value; }
    }
}

public class RateLimiter : IRateLimiter
{
    private readonly IRateLimiterConfiguration _configuration;        
    private static readonly MemoryCache MemoryCache = MemoryCache.Default;

    public RateLimiter(IRateLimiterConfiguration configuration)
    {
        _configuration = configuration;
    }

    public virtual bool ShouldLimit(string key)
    {
        if (!string.IsNullOrEmpty(key))
        {
            Counter counter = new Counter {Count = 1};
            counter = MemoryCache.AddOrGetExisting(key, new Counter { Count = 1 }, DateTimeOffset.Now.Add(_configuration.TimePeriod)) as Counter ?? counter;
            lock (counter.LockObject)
            {
                if (counter.Count < _configuration.Treshhold)
                {
                    counter.Count++;
                }
                else
                {
                    return true;
                }
            }
        }

        return false;
    }

    public HttpStatusCode LimitStatusCode
    {
        get { return _configuration.LimitStatusCode; }
    }

    private class Counter
    {
        public volatile int Count;
        public readonly object LockObject = new object();
    }
}

public class RateLimiterFactory
{
    public IRateLimiter CreateRateLimiter()
    {
        var configuration = GetConfiguration();
        return (IRateLimiter)Activator.CreateInstance(configuration.RateLimiterType, configuration);
    }

    public static RateLimiterConfiguration GetConfiguration()
    {
        return ConfigurationManager.GetSection("rateLimiter") as RateLimiterConfiguration ?? new RateLimiterConfiguration();
    }
}

static class GetClientIpExtensions
{
    private const string XForwardedForHeaderName = "X-Forwarded-For";
    private const string HttpXForwardedForServerVariableName = "HTTP_X_FORWARDED_FOR";
    private const string HttpRemoteAddressServerVariableName = "REMOTE_ADDR";

    public static string GetClientIp(this Message message)
    {
        return GetClientIp(message.Properties);
    }

    public static string GetClientIp(this OperationContext context)
    {
        return GetClientIp(context.IncomingMessageProperties);
    }

    public static string GetClientIp(this MessageProperties messageProperties)
    {
        var endpointLoadBalancer = messageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
        if (endpointLoadBalancer != null && endpointLoadBalancer.Headers[XForwardedForHeaderName] != null)
        {
            return endpointLoadBalancer.Headers[XForwardedForHeaderName];
        }
        else
        {
            var endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
            return (endpointProperty == null) ? string.Empty : endpointProperty.Address;
        }
    }

    public static string GetClientIp(this HttpRequest request)
    {
        string ipList = request.ServerVariables[HttpXForwardedForServerVariableName];
        return !string.IsNullOrEmpty(ipList) ? ipList.Split(',')[0] : request.ServerVariables[HttpRemoteAddressServerVariableName];
    }
}

这使用配置,使用接口的适当隔离和默认的MemoryCache.您可以轻松地更改实现以抽象出缓存.例如,这将允许使用不同的缓存提供程序,例如redis.如果要为运行相同服务的多台服务器提供分布式缓存,这可能很有用.

This uses configuration, proper segregation using interfaces and default MemoryCache. You can easily change the implementation to abstract the cache. This would allow to use different cache providers like redis for example. This could be useful if you want to have a distributed cache for multiple servers running same service.

现在以该代码为基础,我们可以使用它添加一些实现.我们可以添加一个IHttpModule:

Now having this code as a base we can add some implementations using it. We can add a IHttpModule:

public class RateLimiterHttpModule : IHttpModule
{
    private readonly IRateLimiter _rateLimiter;

    public RateLimiterHttpModule()
    {
        _rateLimiter = new RateLimiterFactory().CreateRateLimiter();
    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += OnBeginRequest;
    }

    private void OnBeginRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        string ip = application.Context.Request.GetClientIp();
        if (_rateLimiter.ShouldLimit(ip))
        {
            TerminateRequest(application.Context.Response);
        }
    }

    private void TerminateRequest(HttpResponse httpResponse)
    {
        httpResponse.StatusCode = (int)_rateLimiter.LimitStatusCode;
        httpResponse.SuppressContent = true;
        httpResponse.End();
    }

    public void Dispose()
    {
    }
}

或仅适用于WCF的实现方式可在任何传输级别使用:

Or a WCF only implementation that will work for any transport level:

public class RateLimiterDispatchMessageInspector : IDispatchMessageInspector
{
    private readonly IRateLimiter _rateLimiter;

    public RateLimiterDispatchMessageInspector(IRateLimiter rateLimiter)
    {
        _rateLimiter = rateLimiter;
    }

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        if (_rateLimiter.ShouldLimit(request.GetClientIp()))
        {
            request = null;
            return _rateLimiter.LimitStatusCode;
        }
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        if (correlationState is HttpStatusCode)
        {
            HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
            reply.Properties["httpResponse"] = responseProperty;
            responseProperty.StatusCode = (HttpStatusCode)correlationState;
        }
    }
}

public class RateLimiterServiceBehavior : IServiceBehavior
{
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        var rateLimiterFactory = new RateLimiterFactory();

        foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
        {
            foreach (EndpointDispatcher epDisp in chDisp.Endpoints)
            {
                epDisp.DispatchRuntime.MessageInspectors.Add(new RateLimiterDispatchMessageInspector(rateLimiterFactory.CreateRateLimiter()));
            }
        }
    }
}

public class RateLimiterBehaviorExtensionElement : BehaviorExtensionElement
{
    protected override object CreateBehavior()
    {
        return new RateLimiterServiceBehavior();
    }

    public override Type BehaviorType
    {
        get { return typeof(RateLimiterServiceBehavior); }
    }
}

您可以类似地为ASP.NET MCV做一个动作过滤器.在此处查看:如何进行在ASP.NET MVC网站中实施速率限制?.

You can similarly do an action filter for ASP.NET MCV. Check it out here: How do I implement rate limiting in an ASP.NET MVC site? .

这篇关于如何限制每个客户端的WCF服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆