2015-02-11 4 views
3

Я разрабатываю услугу, которая будет доступна в Интернете только нескольким избранным клиентам. Тем не менее, я не хочу, чтобы один клиент мог так часто обращаться к службе, что они препятствовали тому, чтобы другой клиент вызывал эту услугу или отвечал в течение разумного срока. Я понимаю, что WCF имеет встроенные настройки настройки дросселирования, но я понимаю, что это только для службы в целом.Как отключить услугу WCF на одного клиента

Есть ли встроенный механизм, который позволит мне настроить службу таким образом, чтобы один клиент мог выполнять (например) 10 одновременных вызовов или аналогичных?

Этот вопрос связан с другим вопросом здесь:

Best way to secure a WCF service on the internet with few clients

, где я до сих пор пытаюсь определить, если мне нужно, и как я буду идентифицировать отдельные клиент.

+0

Я не думаю, что существует настраиваемый способ достижения желаемого. Вы можете получить IP-адрес клиента в wcf и заблокировать определенный ip от превышения лимита вызовов. – ziddarth

ответ

1

Фраза, которую вы ищете, это Rate limiting. И нет, нет встроенного способа ограничить скорость службы WCF. Как вы сказали, вы можете играть с набором функций WCF около service throttling, но это настройка уровня обслуживания, а не для каждого клиента.

Для того чтобы реализовать ограничение скорости, общее руководство, как представляется, заключается в использовании коллекции в памяти (или что-то вроде redis для сценариев масштабирования) для быстрого поиска по входящей пользовательской строке или IP-адресу. Затем вы можете определить некоторый ограничивающий алгоритм вокруг этой информации.

Дополнительная информация here и here.

0

Вы можете изменить конфигурацию следующим образом:

ConcurrencyMode:=ConcurrencyMode.Single 
InstanceContextMode:=InstanceContextMode.Single 

Затем в коде созданы две переменные уровня обслуживания:

  • один строковую переменную для хранения идентификатора последнего запроса
  • одна целочисленная переменная для количества посещений.

С каждым запросом, где идентификатор входящего пользователя == последнему сохраненному пользователю, приращение +1 ваша целая переменная. После запроса 10 вернуть отказ пользователю. Если пользователь отличается, сбросьте переменные и обработайте запрос.

Это не конфигурационное решение - это конфигурация и код, но это сработает.

+1

Один экземпляр службы, который выполняет только один вызов за один раз ?! Это лучшая реализация «одного клиента, способного предотвратить использование другим клиентом службы», которую я когда-либо видел. – ErnieL

+0

Да, но это не помешает пользователю вернуть 1/1000 секунды позже с другим запросом. В принципе, это просто очередь запросов. – Brian

+0

Для каждого клиента можно разместить синглтон. Когда вы получаете нового клиента, просто запустите другой экземпляр службы. Отличная стратегия. Подождите .... –

0

Во-первых, если вы используете какой-либо балансировщик нагрузки, лучше всего его реализовать. Например, у NGINX есть возможности ограничения скорости: http://nginx.org/en/docs/http/ngx_http_limit_req_module.html.

Во-вторых, вы должны использовать встроенные возможности ограничения скорости IIS, называемые динамическими ограничениями IP: http://www.iis.net/learn/get-started/whats-new-in-iis-8/iis-80-dynamic-ip-address-restrictions.

Если вам этого недостаточно, потому что для этого вам нужна настраиваемая логика, вы всегда можете реализовать его на уровне приложения. Это можно сделать несколькими способами.

Начнет с некоторой предельной логикой многоразовых скоростей:

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. Это может быть полезно, если вы хотите иметь распределенный кеш для нескольких серверов, работающих на одном сервисе.

Теперь, имея этот код в качестве базы, мы можем добавить некоторые реализации, используя его. Мы можем добавить 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() 
    { 
    } 
} 

Или ФОС только реализации, который будет работать на любом транспортном уровне:

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. Посмотрите здесь: How do I implement rate limiting in an ASP.NET MVC site?.

+0

С точки зрения обзора кода, не является «TerminateRequest» немного жестоким способом продолжения. Разве «ожидание Task.Delay» не имеет смысла? – Aron

+0

Что бы достигло Task.Delay? –

+0

Это «задерживает» ответ, следовательно, скорость, не убивая запрос. Конечно, вам понадобится реализовать какую-то логику проверки параллелизма/очередности ... Но это похоже на правильный способ ограничения скорости. – Aron

Смежные вопросы