简体   繁体   中英

Restrict calls to WCF service per minute, per method, per IP

I'd like to restrict the number of calls to a particular method on my WCF service, by any distinct IP, to x calls per timeframe y .

So for example, if IP 10.0.1.1 calls the method register more than 5 times in a particular minute (call it minute x ), when it it tries to call that method a sixth time in that minute it is blocked until minute ( x + 1 ).

This is because the only non-token authorized call on my system is the register call. I am worried that attempts to flood this method with calls will my server to struggle under load. There is quite a lot of processing behind this method, and it is designed to only be called occasionally.

I have looked into adding ServiceThrottlingBehavior to the config file, but this is global to the service, rather than local to a service method.

Is there a good / standardized way to do this, whether programatically or in a configuration file?

One way to do this is by having a ServiceBehavior that adds an instance that implements IInstanceContextInitializer .

My implementation looks like this:

public class PerOperationThrottle: IInstanceContextInitializer
{
    static MemoryCache cache = new MemoryCache("foo", null);

    public void Initialize(InstanceContext instanceContext, Message message)
    {
        RemoteEndpointMessageProperty  ep = message.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
        // which action do we want to throttle
        if (message.Headers.Action.EndsWith("register") &&
            ep != null && 
            ep.Address != null)
        {
            // get the IP address 
            var item = cache[ep.Address];
            if (item == null)
            {
                // not found, so init
                cache.Add(
                    ep.Address,
                    new Counter { Count = 0 },
                    new CacheItemPolicy
                    {
                        SlidingExpiration = new TimeSpan(0, 1, 0) // 1 minute
                    });
            }
            else
            {
                // how many calls?
                var count = (Counter)item;
                if (count.Count > 5)
                {
                    instanceContext.Abort();
                    // not sure if this the best way to break
                    throw new Exception("throttle");
                }
                // add one call
                count.Count++;
            }
        }
    }
}

I use a somewhat naïve MemoryCache implementation that holds an instance per IP address of a my custom Counter class:

public class Counter
{
    public int Count;
}

To wire an instance of PerOperationThrottle to the service I have an helper class that combines the implementation for a IServiceBehavior and IEndpointBehavior :

public class PerOperationThrottleBehaviorAttribute : Attribute, IServiceBehavior,IEndpointBehavior
{
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach(var ep in serviceDescription.Endpoints)
        {
            // add the EndpointBehavior
            ep.EndpointBehaviors.Add(this);
        }      
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        // our PerOperationThrottle gets created and wired
        endpointDispatcher.
            DispatchRuntime.
            InstanceContextInitializers.
            Add(new PerOperationThrottle());
    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {   
    }
    public void Validate(ServiceEndpoint endpoint)
    {   
    }
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    { 
    }
}

The empty methods belong to the interfaces but don't need any implementation. Make sure to remove the throw new NotImplementedException(); though.

Finally we annotate the Service implementation class with our custom attribute PerOperationThrottleBehavior

[PerOperationThrottleBehavior]
public class Service1 : IService1
{
    public string register(int value)
    {
        return string.Format("You entered: {0}", value);
    }
}

If the register operation gets called more than 5 times within a minute, the service throws an exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM