open Microsoft.Extensions.Options open Microsoft.Extensions.Caching.Distributed open Microsoft.Extensions.Caching.Memory open MBrace.FsPickler type IDistributedCache with member self.SetValue(serializer, key, value, options) = self.Set(key, serializer value, options) member self.GetValue<'T>(deserializer: byte[] -> 'T, key) = match self.Get(key) with | null -> None | obj -> Some (deserializer obj) member self.SetValueAsync(serializer, key, value, options, token) = self.SetAsync(key, serializer value, options, token) member self.GetValueAsync<'T>(deserializer: byte[] -> 'T, key, token) = task { match! self.GetAsync(key, token) with | null -> return None | obj -> return Some (deserializer(obj)) } member self.SetAsyncValue(serializer, key, value, options) = async { let! token = Async.CancellationToken return! Async.AwaitTask (self.SetValueAsync(serializer, key, value, options, token)) } member self.GetAsyncValue<'T>(deserializer, key) = async { let! token = Async.CancellationToken return! Async.AwaitTask (self.GetValueAsync<'T>(deserializer, key, token)) } type Throttler (cache: IDistributedCache, cacheDuration, maxRequests) = let serializer = FsPickler.CreateBinarySerializer() let agent = MailboxProcessor.Start(fun agent -> let rec loop () = async { let! (cacheKey, reply: AsyncReplyChannel) = agent.Receive() let! value = cache.GetAsyncValue(serializer.UnPickle, cacheKey) let count, start, expiration = match value with | Some (start: System.DateTimeOffset, count) -> let count' = count + 1 let expiration = start.Add(cacheDuration) count', start, expiration | None -> let now = System.DateTimeOffset.UtcNow let expiration = now.Add(cacheDuration) 1, now, expiration do! cache.SetAsyncValue( serializer.Pickle, cacheKey, (start, count), DistributedCacheEntryOptions( AbsoluteExpiration=expiration ) ) reply.Reply(not (count > maxRequests)) return! loop () } loop () ) member _.Get(cacheKey) = agent.PostAndReply(fun channel -> (cacheKey, channel)) member _.GetAsync(cacheKey) = agent.PostAndAsyncReply(fun channel -> (cacheKey, channel)) (* Example *) // The cache is normally provided by DI in ASP.NET Core let options = Options.Create(MemoryDistributedCacheOptions()) let cache = new MemoryDistributedCache(options) let cacheDuration = System.TimeSpan.FromSeconds(1) let maxRequests = 4 let throttler = Throttler(cache, cacheDuration, maxRequests) // The cache key could be the IP address or the username or anything you want to provide as the limiting factor for the request. for _ in 0..10 do throttler.Get("hello") |> printfn "%A" System.Threading.Thread.Sleep 120 // Result: //true //true //true //true //false //false //false //false //true //true //true