我正在开发一项服务,该服务将在互联网上暴露给少数精选客户。但是,我不希望一个客户端能够经常调用该服务,以致它们阻止另一个客户端调用该服务,或者在合理的时间内响应。我意识到WCF内置了许多限制配置设置,但据我所知,这些仅适用于整个服务。
是否有任何内置机制可以让我配置服务,使单个客户端只能执行(例如)10个并发呼叫或类似的呼叫?
这个问题与另一个问题有关:
Best way to secure a WCF service on the internet with few clients
我还在努力确定是否需要,以及如何识别个人客户。
答案 0 :(得分:2)
您在这里寻找的短语是 Rate limiting 。而且,不,没有内置的方法来限制WCF服务。如您所说,您可以使用service throttling周围的WCF功能集,但这是服务级别设置,而不是每个客户端。
为了实现速率限制,一般指导似乎是使用内存中的集合(或类似于redis用于横向扩展方案)来对传入的用户字符串或IP地址执行快速查找。然后,您可以围绕该信息定义一些限制算法。
答案 1 :(得分:1)
首先,如果您使用某种负载均衡器,最好在那里实施。例如,NGINX具有速率限制功能:http://nginx.org/en/docs/http/ngx_http_limit_req_module.html。
其次,您应该考虑使用称为动态IP限制的内置IIS速率限制功能: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()
{
}
}
或者仅适用于任何传输级别的WCF实现:
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?。
答案 2 :(得分:0)
您可以改变您的配置:
ConcurrencyMode:=ConcurrencyMode.Single
InstanceContextMode:=InstanceContextMode.Single
然后,在代码中,设置两个服务级变量:
对于每个请求,其中入站用户的ID ==最后保存的用户,请将整数变量增加+1。在请求10之后,向用户返回拒绝。如果用户不同,则重置变量并处理请求。
这不是配置解决方案 - 它的配置和代码,但它可以工作。