 
 typealias By = (_ request: RouterRequest) -> String
 typealias NextFunc = () -> Void
+typealias WhiteList = (_ request: RouterRequest) -> Bool
 
 private func now() -> Int { // ms since epoch
     return Int(Date().timeIntervalSince1970 * 1000)

    let defaultLimitJSON: JSON
    let redis: Redis
    let ignoreErrors: Bool

    // TODO: whitelist, onRedisError, skipHeaders/setHeaders functions

    // 150 req/hour default
    public init(redis: Redis, by: @escaping By = defaultByFunc, total: Int = 5, expire: Int = 1000 * 60, onRateLimited: @escaping RouterHandler = defaultOnRateLimited, ignoreErrors: Bool = false) {
        self.redis = redis
        self.by = by
        self.total = total
        self.expire = expire
        self.onRateLimited = onRateLimited
        self.ignoreErrors = ignoreErrors
        self.defaultLimitJSON = JSON([
            "total": total,
            "remaining": total,
            "reset": now() + self.expire
        ])
    }

    public func handle(request: RouterRequest, response: RouterResponse, next: @escaping NextFunc) throws {
        let key = "ratelimit:\(self.by(request))"

        redis.get(key) { (string: RedisString?, redisError: NSError?) in

                    if let _ = redisError {
                        if ignoreErrors {
                            return next()
                        } else {
                            return try! onRedisError(request, response, next)
                        }
                    } else {

                        if !skipHeaders {
                            let lim = json["total"].int!
                            let rs = ceil((Double(json["reset"].int!) / 1000))
                            let rem = max(json["remaining"].int!, 0)

                            response.headers.append("X-RateLimit-Limit", value: "\(lim)")
                            response.headers.append("X-RateLimit-Reset", value: "\(rs)")
                            response.headers.append("X-RateLimit-Remaining", value: "\(rem)")
                        }

                        if json["remaining"].int! >= 0 {
                            return next()
                        } else {

                            if !skipHeaders {
                                let after = (json["reset"].int! - now()) / 1000
                                response.headers.append("Retry-After", value: "\(after)")
                            }

                            return try! onRateLimited(request, response, next)
                        }
                    }
                }
            }
        }
