diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Maxim Kulkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/Network/Wai/Middleware/Throttler.hs b/Network/Wai/Middleware/Throttler.hs
new file mode 100644
--- /dev/null
+++ b/Network/Wai/Middleware/Throttler.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Network.Wai.Middleware.Throttler
+  ( ThrottleCache(..)
+  , newMemoryThrottleCache
+  , throttle
+  , throttlePath
+  ) where
+
+import Control.Concurrent.MVar (MVar, newMVar, takeMVar, putMVar, readMVar)
+import Data.Map (Map)
+import qualified Data.Map as Map (empty, insert, lookup)
+import Data.Maybe (fromMaybe)
+import Data.ByteString as B (ByteString, take, length)
+import Data.ByteString.Char8 as B8 (pack)
+import Data.Time.Clock (getCurrentTime, UTCTime, NominalDiffTime)
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)
+import Network.Wai (Request, Response, Middleware, rawPathInfo, responseLBS)
+import Network.HTTP.Types.Status (status403)
+
+class ThrottleCache c where
+  cacheCount :: c -> ByteString -> IO Int
+
+
+data MemoryThrottleCache = MemoryThrottleCache Int NominalDiffTime (MVar (Map ByteString (UTCTime, Int)))
+
+newMemoryThrottleCache :: Int -> NominalDiffTime -> IO MemoryThrottleCache
+newMemoryThrottleCache limit period = fmap (MemoryThrottleCache limit period) $ newMVar Map.empty
+
+instance ThrottleCache MemoryThrottleCache where
+  cacheCount (MemoryThrottleCache limit period v) key = do
+    map <- takeMVar v
+    (t', c') <- case Map.lookup key map of
+                  Nothing -> do
+                    now <- getCurrentTime
+                    return $ (alignTime now period, 1)
+                  Just (t, c) -> do
+                    case c + 1 > limit of
+                      True -> do
+                        now <- getCurrentTime
+                        let alignedNow = alignTime now period
+                        case alignedNow == t of
+                          True -> return $ (t, c + 1)
+                          False -> return $ (alignedNow, 1)
+                      False -> return $ (t, c + 1)
+    putMVar v $ Map.insert key (t', c') map
+    return c'
+
+    where alignTime :: UTCTime -> NominalDiffTime -> UTCTime
+          alignTime time period = posixSecondsToUTCTime . fromIntegral $ a - (a `mod` b)
+            where a = floor . utcTimeToPOSIXSeconds $ time
+                  b = floor period
+
+
+throttlePath :: ThrottleCache c => ByteString -> c -> Int -> (Request -> Maybe ByteString) -> Middleware
+throttlePath path cache limit getKey app req = do
+  case pathMatches path req of
+    False -> app req
+    True -> throttle cache limit getKey app req
+
+  where pathMatches :: ByteString -> Request -> Bool
+        pathMatches path request = B.take (B.length path) (rawPathInfo request) == path
+
+throttle :: ThrottleCache c => c -> Int -> (Request -> Maybe ByteString) -> Middleware
+throttle cache limit getKey app req = do
+  case getKey req of
+    Nothing -> app req
+    Just key -> do
+      count <- cacheCount cache key
+      if count > limit then return (throttledResponse count limit) else app req
+
+  where throttledResponse :: Int -> Int -> Response
+        throttledResponse count limit =
+          responseLBS status403
+                      [ ("X-RateLimit-Limit", bs limit)
+                      , ("X-RateLimit-Remaining", bs remaining)
+                      ]
+                      ""
+          where bs = B8.pack . show
+                remaining = max 0 (limit - count)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/wai-throttler.cabal b/wai-throttler.cabal
new file mode 100644
--- /dev/null
+++ b/wai-throttler.cabal
@@ -0,0 +1,28 @@
+-- Initial wai-throttler.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                wai-throttler
+version:             0.1.0.0
+synopsis:            Wai middleware for request throttling
+-- description:         
+license:             MIT
+license-file:        LICENSE
+author:              Maxim Kulkin
+maintainer:          maxim.kulkin@gmail.com
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.10
+
+source-repository head
+  type:                git
+  location:            https://github.com/maximkulkin/wai-throttler
+
+library
+  exposed-modules:     Network.Wai.Middleware.Throttler
+  build-depends:       base >=4.6 && <4.7
+                     , bytestring == 0.10.*
+                     , containers == 0.5.*
+                     , time == 1.4.*
+                     , wai == 2.*
+                     , http-types == 0.8.*
+  default-language:    Haskell2010
