From 2a46c3e7ef7d0d9c636bfb23839b5e42679a4c45 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 19:54:34 +0200 Subject: [PATCH 01/14] Change Package for SPM 4.2 --- Package.resolved | 160 ++++++++++++++++++++++ Package.swift | 20 ++- Sources/{ => Gatekeeper}/Gatekeeper.swift | 0 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 Package.resolved rename Sources/{ => Gatekeeper}/Gatekeeper.swift (100%) diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..0a41dc7 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,160 @@ +{ + "object": { + "pins": [ + { + "package": "Console", + "repositoryURL": "https://github.com/vapor/console.git", + "state": { + "branch": null, + "revision": "74cfbea629d4aac34a97cead2447a6870af1950b", + "version": "3.1.1" + } + }, + { + "package": "Core", + "repositoryURL": "https://github.com/vapor/core.git", + "state": { + "branch": null, + "revision": "2731f8ba0cf274a61c9bd6ab43550f692ffaf879", + "version": "3.9.0" + } + }, + { + "package": "Crypto", + "repositoryURL": "https://github.com/vapor/crypto.git", + "state": { + "branch": null, + "revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c", + "version": "3.3.3" + } + }, + { + "package": "DatabaseKit", + "repositoryURL": "https://github.com/vapor/database-kit.git", + "state": { + "branch": null, + "revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4", + "version": "1.3.3" + } + }, + { + "package": "HTTP", + "repositoryURL": "https://github.com/vapor/http.git", + "state": { + "branch": null, + "revision": "254a0a0cbf22a02b697a075a0d2ddbb448bb7c87", + "version": "3.2.0" + } + }, + { + "package": "Multipart", + "repositoryURL": "https://github.com/vapor/multipart.git", + "state": { + "branch": null, + "revision": "f919a01c4d10a281d6236a21b0b1d1759a72b8eb", + "version": "3.0.4" + } + }, + { + "package": "Routing", + "repositoryURL": "https://github.com/vapor/routing.git", + "state": { + "branch": null, + "revision": "626190ddd2bd9f967743b60ba6adaf90bbd2651c", + "version": "3.0.2" + } + }, + { + "package": "Service", + "repositoryURL": "https://github.com/vapor/service.git", + "state": { + "branch": null, + "revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb", + "version": "1.0.2" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", + "version": "1.14.1" + } + }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", + "version": "1.4.0" + } + }, + { + "package": "swift-nio-ssl-support", + "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", + "state": { + "branch": null, + "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", + "version": "1.0.0" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" + } + }, + { + "package": "TemplateKit", + "repositoryURL": "https://github.com/vapor/template-kit.git", + "state": { + "branch": null, + "revision": "121ae51433df94cf6e15c09e1f1b0f7c77ff8d5c", + "version": "1.2.0" + } + }, + { + "package": "URLEncodedForm", + "repositoryURL": "https://github.com/vapor/url-encoded-form.git", + "state": { + "branch": null, + "revision": "82d8d63bdb76b6dd8febe916c639ab8608dbbaed", + "version": "1.0.6" + } + }, + { + "package": "Validation", + "repositoryURL": "https://github.com/vapor/validation.git", + "state": { + "branch": null, + "revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6", + "version": "2.1.1" + } + }, + { + "package": "Vapor", + "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": null, + "revision": "c86ada59b31c69f08a6abd4f776537cba48d5df6", + "version": "3.3.0" + } + }, + { + "package": "WebSocket", + "repositoryURL": "https://github.com/vapor/websocket.git", + "state": { + "branch": null, + "revision": "d85e5b6dce4d04065865f77385fc3324f98178f6", + "version": "1.1.2" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index a2f9395..67cecd6 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,24 @@ +// swift-tools-version:4.2 import PackageDescription let package = Package( name: "Gatekeeper", + products: [ + .library( + name: "Gatekeeper", + targets: ["Gatekeeper"]), + ], dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2) + .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + ], + targets: [ + .target( + name: "Gatekeeper", + dependencies: [ + "Vapor" + ]), + .testTarget( + name: "GateKeeperTests", + dependencies: ["Gatekeeper"]), ] -) +) \ No newline at end of file diff --git a/Sources/Gatekeeper.swift b/Sources/Gatekeeper/Gatekeeper.swift similarity index 100% rename from Sources/Gatekeeper.swift rename to Sources/Gatekeeper/Gatekeeper.swift From f268cd5ac507e6e02b7050889e01c010fc37158f Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 20:51:04 +0200 Subject: [PATCH 02/14] Update Gatekeeper for Vapor-3 --- Sources/Gatekeeper/Gatekeeper.swift | 145 ++++++++---------- Sources/Gatekeeper/GatekeeperConfig.swift | 32 ++++ Sources/Gatekeeper/GatekeeperMiddleware.swift | 25 +++ Sources/Gatekeeper/GatekeeperProvider.swift | 33 ++++ 4 files changed, 156 insertions(+), 79 deletions(-) create mode 100644 Sources/Gatekeeper/GatekeeperConfig.swift create mode 100644 Sources/Gatekeeper/GatekeeperMiddleware.swift create mode 100644 Sources/Gatekeeper/GatekeeperProvider.swift diff --git a/Sources/Gatekeeper/Gatekeeper.swift b/Sources/Gatekeeper/Gatekeeper.swift index 49f3f5a..815513c 100644 --- a/Sources/Gatekeeper/Gatekeeper.swift +++ b/Sources/Gatekeeper/Gatekeeper.swift @@ -1,90 +1,77 @@ -import HTTP -import Cache import Vapor -import Foundation -public struct Rate { - public enum Interval { - case second - case minute - case hour - case day - } - - public let limit: Int - public let interval: Interval - - public init(_ limit: Int, per interval: Interval) { - self.limit = limit - self.interval = interval - } - - internal var refreshInterval: Double { - switch interval { - case .second: - return 1 - case .minute: - return 60 - case .hour: - return 3_600 - case .day: - return 86_400 - } - } -} +public struct Gatekeeper: Service { + + internal let config: GatekeeperConfig + internal let cacheFactory: ((Container) throws -> KeyedCache) -public struct Gatekeeper: Middleware { - internal var cache: CacheProtocol - - internal let limit: Int - internal let refreshInterval: Double - - public init(rate: Rate, cache: CacheProtocol = MemoryCache()) { - self.cache = cache - self.limit = rate.limit - self.refreshInterval = rate.refreshInterval + public init( + config: GatekeeperConfig, + cacheFactory: @escaping ((Container) throws -> KeyedCache) = { container in try container.make() } + ) { + self.config = config + self.cacheFactory = cacheFactory } - - public func respond(to request: Request, chainingTo next: Responder) throws -> Response { - guard let peer = request.peerHostname else { + + public func accessEndpoint( + on request: Request + ) throws -> Future { + + guard let peerHostName = request.http.remotePeer.hostname else { throw Abort( .forbidden, - metadata: nil, - reason: "Unable to verify peer." + reason: "Unable to verify peer" ) } - - var entry = try cache.get(peer) - var createdAt = entry?["createdAt"]?.double ?? Date().timeIntervalSince1970 - var requestsLeft = entry?["requestsLeft"]?.int ?? limit - - let now = Date().timeIntervalSince1970 - if now - createdAt >= refreshInterval { - createdAt = now - requestsLeft = limit - } - - defer { - do { - try cache.set(peer, Node(node: [ - "createdAt": createdAt, - "requestsLeft": requestsLeft - ])) - } catch { - print("WARNING: cache failed: \(error)") + + let peerCacheKey = cacheKey(for: peerHostName) + let cache = try cacheFactory(request) + + return cache.get(peerCacheKey, as: Entry.self) + .map(to: Entry.self) { entry in + if let entry = entry { + return entry + } else { + return Entry( + peerHostname: peerHostName, + createdAt: Date(), + requestsLeft: self.config.limit + ) + } } - } - - requestsLeft -= 1 - guard requestsLeft >= 0 else { - throw Abort( - .tooManyRequests, - metadata: nil, - reason: "Slow down." - ) - } - - let response = try next.respond(to: request) - return response + .map(to: Entry.self) { entry in + + let now = Date() + var mutableEntry = entry + if now.timeIntervalSince1970 - entry.createdAt.timeIntervalSince1970 >= self.config.refreshInterval { + mutableEntry.createdAt = now + mutableEntry.requestsLeft = self.config.limit + } + mutableEntry.requestsLeft -= 1 + return mutableEntry + }.then { entry in + return cache.set(peerCacheKey, to: entry).transform(to: entry) + }.map(to: Entry.self) { entry in + + if entry.requestsLeft <= 0 { + throw Abort( + .tooManyRequests, + reason: "Patience you must have, my young Padawan." + ) + } + return entry + } + } + + private func cacheKey(for hostname: String) -> String { + return "gatekeeper_\(hostname)" + } +} + +extension Gatekeeper { + public struct Entry: Codable { + let peerHostname: String + var createdAt: Date + var requestsLeft: Int } } diff --git a/Sources/Gatekeeper/GatekeeperConfig.swift b/Sources/Gatekeeper/GatekeeperConfig.swift new file mode 100644 index 0000000..79597d6 --- /dev/null +++ b/Sources/Gatekeeper/GatekeeperConfig.swift @@ -0,0 +1,32 @@ +import Vapor + +public struct GatekeeperConfig: Service { + + public enum Interval { + case second + case minute + case hour + case day + } + + public let limit: Int + public let interval: Interval + + public init(maxRequests limit: Int, per interval: Interval) { + self.limit = limit + self.interval = interval + } + + internal var refreshInterval: Double { + switch interval { + case .second: + return 1 + case .minute: + return 60 + case .hour: + return 3_600 + case .day: + return 86_400 + } + } +} diff --git a/Sources/Gatekeeper/GatekeeperMiddleware.swift b/Sources/Gatekeeper/GatekeeperMiddleware.swift new file mode 100644 index 0000000..a65e507 --- /dev/null +++ b/Sources/Gatekeeper/GatekeeperMiddleware.swift @@ -0,0 +1,25 @@ +import Vapor + +public struct GatekeeperMiddleware { + let gatekeeper: Gatekeeper +} + +extension GatekeeperMiddleware: Middleware { + public func respond( + to request: Request, + chainingTo next: Responder + ) throws -> EventLoopFuture { + + return try gatekeeper.accessEndpoint(on: request).do { entry in + print("Gatekeeper Entry: \(entry)") + }.flatMap { _ in + return try next.respond(to: request) + } + } +} + +extension GatekeeperMiddleware: ServiceType { + public static func makeService(for container: Container) throws -> GatekeeperMiddleware { + return try .init(gatekeeper: container.make()) + } +} diff --git a/Sources/Gatekeeper/GatekeeperProvider.swift b/Sources/Gatekeeper/GatekeeperProvider.swift new file mode 100644 index 0000000..551f7d6 --- /dev/null +++ b/Sources/Gatekeeper/GatekeeperProvider.swift @@ -0,0 +1,33 @@ +import Vapor + +public final class GatekeeperProvider { + + internal let config: GatekeeperConfig + internal let cacheFactory: ((Container) throws -> KeyedCache) + + public init( + config: GatekeeperConfig, + cacheFactory: @escaping ((Container) throws -> KeyedCache) = { container in try container.make() } + ) { + self.config = config + self.cacheFactory = cacheFactory + } +} + +extension GatekeeperProvider: Provider { + public func register(_ services: inout Services) throws { + services.register(config) + services.register( + Gatekeeper( + config: config, + cacheFactory: cacheFactory + ), + as: Gatekeeper.self + ) + services.register(GatekeeperMiddleware.self) + } + + public func didBoot(_ container: Container) throws -> EventLoopFuture { + return .done(on: container) + } +} From a7ffcba6793c765ea4027a3d095369935e30dfe0 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 21:04:19 +0200 Subject: [PATCH 03/14] Update readme --- LICENSE | 2 +- README.md | 90 +++++++++++++++++++++++++------------------------------ 2 files changed, 41 insertions(+), 51 deletions(-) diff --git a/LICENSE b/LICENSE index 1ced862..8de12da 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017-2018 Nodes +Copyright (c) 2017-2019 Nodes Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7b60074..e695775 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gatekeeper 👮 -[![Swift Version](https://img.shields.io/badge/Swift-3-brightgreen.svg)](http://swift.org) -[![Vapor Version](https://img.shields.io/badge/Vapor-2-F6CBCA.svg)](http://vapor.codes) +[![Swift Version](https://img.shields.io/badge/Swift-4.2-brightgreen.svg)](http://swift.org) +[![Vapor Version](https://img.shields.io/badge/Vapor-3-30B6FC.svg)](http://vapor.codes) [![Circle CI](https://circleci.com/gh/nodes-vapor/gatekeeper/tree/master.svg?style=shield)](https://circleci.com/gh/nodes-vapor/gatekeeper) [![codebeat badge](https://codebeat.co/badges/35c7b0bb-1662-44ae-b953-ab1d4aaf231f)](https://codebeat.co/projects/github-com-nodes-vapor-gatekeeper-master) [![codecov](https://codecov.io/gh/nodes-vapor/gatekeeper/branch/master/graph/badge.svg)](https://codecov.io/gh/nodes-vapor/gatekeeper) @@ -8,86 +8,76 @@ [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nodes-vapor/gatekeeper/master/LICENSE) Gatekeeper is a middleware that restricts the number of requests from clients, based on their IP address. -It works by adding the clients IP address to the cache and count how many requests the clients can make during the Gatekeeper's defined lifespan and give back an HTTP 429(Too Many Requests) if the limit has been reached. The number of requests left will be reset when the defined timespan has been reached +It works by adding the clients IP address to the cache and count how many requests the clients can make during the Gatekeeper's defined lifespan and give back an HTTP 429(Too Many Requests) if the limit has been reached. The number of requests left will be reset when the defined timespan has been reached. **Please take into consideration that multiple clients can be using the same IP address. eg. public wifi** ## 📦 Installation -Update your `Package.swift` file. +Update your `Package.swift` dependencies: + ```swift -.Package(url: "https://github.com/nodes-vapor/gatekeeper", majorVersion: 0) +.package(url: "https://github.com/nodes-vapor/gatekeeper.git", from: "3.0.0"), ``` - -## Getting started 🚀 - -`Gatekeeper` has two configurable fields: the maximum rate and the cache to use. If you don't supply your own cache the limiter will create its own, in-memory cache. +as well as to your target (e.g. "App"): ```swift -let gatekeeper = GateKeeper(rate: Rate(10, per: .minute)) +targets: [ + .target(name: "App", dependencies: [..., "Gatekeeper", ...]), +// ... +] ``` -### Adding middleware -You can add the middleware either globally or to a route group. +## Getting started 🚀 -#### Adding Middleware Globally +### Configuration -#### `Sources/App/Config+Setup.swift` +in configure.swift: ```swift import Gatekeeper -``` - -```swift -public func setup() throws { - // ... - - addConfigurable(middleware: Gatekeeper(rate: Rate(10, per: .minute)), name: "gatekeeper") -} -``` - -#### `Config/droplet.json` -Add `gatekeeper` to the middleware array - -```json -"middleware": [ - "error", - "date", - "file", - "gatekeeper" -] +// [...] + +// Register providers first +try services.register( + GatekeeperProvider( + config: GatekeeperConfig(maxRequests: 10, per: .second), + cacheFactory: { container -> KeyedCache in + return try container.make() + } + ) +) ``` +### Add to routes -#### Adding Middleware to a Route Group +You can add the `GatekeeperMiddleware` to specific routes or to all. -```Swift -let gatekeeper = Gatekeeper(rate: Rate(10, per: .minute)) - -drop.group(gatekeeper) { group in - // Routes +**Specific routes** +in routes.swift: +```swift +let protectedRoutes = router.grouped(GatekeeperMiddleware.self) +protectedRoutes.get("protected/hello") { req in + return "Protected Hello, World!" } ``` - -### The `Rate.Interval` enumeration - -The currently implemented intervals are: +**For all requests** +in configure.swift: ```swift -case .second -case .minute -case .hour -case .day +// Register middleware +var middlewares = MiddlewareConfig() // Create _empty_ middleware config +middlewares.use(GatekeeperMiddleware.self) +services.register(middlewares) ``` ## Credits 🏆 -This package is developed and maintained by the Vapor team at [Nodes](https://www.nodes.dk). +This package is developed and maintained by the Vapor team at [Nodes](https://www.nodesagency.com). The package owner for this project is [Tom](https://github.com/tomserowka). - ## License 📄 This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) From ea9796a71d3ebe739e706ea6aa19b6a5178b8f2c Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 21:08:10 +0200 Subject: [PATCH 04/14] Update CircleCI config --- .circleci/config.yml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7c4b39c..c9b78ec 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,42 +2,44 @@ version: 2 jobs: MacOS: macos: - xcode: "9.0" + xcode: "10.0.0" steps: - checkout - restore_cache: keys: - - v1-spm-deps-{{ checksum "Package.swift" }} + - v2-spm-deps-{{ checksum "Package.swift" }} - run: - name: Install CMySQL and CTLS + name: Install dependencies command: | brew tap vapor/homebrew-tap brew install cmysql brew install ctls + brew install libressl + brew install cstack - run: name: Build and Run Tests no_output_timeout: 1800 command: | - swift package generate-xcodeproj --enable-code-coverage + swift package generate-xcodeproj --enable-code-coverage xcodebuild -scheme Gatekeeper-Package -enableCodeCoverage YES test | xcpretty - run: name: Report coverage to Codecov command: | bash <(curl -s https://codecov.io/bash) - save_cache: - key: v1-spm-deps-{{ checksum "Package.swift" }} + key: v2-spm-deps-{{ checksum "Package.swift" }} paths: - .build Linux: docker: - - image: brettrtoomey/vapor-ci:0.0.1 + - image: nodesvapor/vapor-ci:swift-4.2 steps: - checkout - restore_cache: keys: - - v2-spm-deps-{{ checksum "Package.swift" }} + - v2l-spm-deps-{{ checksum "Package.swift" }} - run: - name: Copy Package file + name: Copy Package File command: cp Package.swift res - run: name: Build and Run Tests @@ -45,10 +47,10 @@ jobs: command: | swift test -Xswiftc -DNOJSON - run: - name: Restoring Package file + name: Restoring Package File command: mv res Package.swift - save_cache: - key: v2-spm-deps-{{ checksum "Package.swift" }} + key: v2l-spm-deps-{{ checksum "Package.swift" }} paths: - .build workflows: @@ -62,4 +64,4 @@ experimental: branches: only: - master - - develop + - develop \ No newline at end of file From f52105d184c77c87bfd6179f6940887a57f1b15d Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 21:30:39 +0200 Subject: [PATCH 05/14] Update .gitignore --- .gitignore | 80 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d5f2889..3ca8c63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,71 @@ -Packages -.build -.idea -xcuserdata +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +Package.resolved *.xcodeproj -Config/secrets/ -.DS_Store -node_modules/ -bower_components/ -.swift-version -CMakeLists.txt +Packages/ + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xcuserstate + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output + +Package.pins \ No newline at end of file From 8abf45f333fb8593c5642677ca72bac857f96702 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 21:31:02 +0200 Subject: [PATCH 06/14] Remove Package.resolved --- Package.resolved | 160 ----------------------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 0a41dc7..0000000 --- a/Package.resolved +++ /dev/null @@ -1,160 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Console", - "repositoryURL": "https://github.com/vapor/console.git", - "state": { - "branch": null, - "revision": "74cfbea629d4aac34a97cead2447a6870af1950b", - "version": "3.1.1" - } - }, - { - "package": "Core", - "repositoryURL": "https://github.com/vapor/core.git", - "state": { - "branch": null, - "revision": "2731f8ba0cf274a61c9bd6ab43550f692ffaf879", - "version": "3.9.0" - } - }, - { - "package": "Crypto", - "repositoryURL": "https://github.com/vapor/crypto.git", - "state": { - "branch": null, - "revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c", - "version": "3.3.3" - } - }, - { - "package": "DatabaseKit", - "repositoryURL": "https://github.com/vapor/database-kit.git", - "state": { - "branch": null, - "revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4", - "version": "1.3.3" - } - }, - { - "package": "HTTP", - "repositoryURL": "https://github.com/vapor/http.git", - "state": { - "branch": null, - "revision": "254a0a0cbf22a02b697a075a0d2ddbb448bb7c87", - "version": "3.2.0" - } - }, - { - "package": "Multipart", - "repositoryURL": "https://github.com/vapor/multipart.git", - "state": { - "branch": null, - "revision": "f919a01c4d10a281d6236a21b0b1d1759a72b8eb", - "version": "3.0.4" - } - }, - { - "package": "Routing", - "repositoryURL": "https://github.com/vapor/routing.git", - "state": { - "branch": null, - "revision": "626190ddd2bd9f967743b60ba6adaf90bbd2651c", - "version": "3.0.2" - } - }, - { - "package": "Service", - "repositoryURL": "https://github.com/vapor/service.git", - "state": { - "branch": null, - "revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb", - "version": "1.0.2" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd", - "version": "1.14.1" - } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "branch": null, - "revision": "0f3999f3e3c359cc74480c292644c3419e44a12f", - "version": "1.4.0" - } - }, - { - "package": "swift-nio-ssl-support", - "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", - "state": { - "branch": null, - "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", - "version": "1.0.0" - } - }, - { - "package": "swift-nio-zlib-support", - "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", - "state": { - "branch": null, - "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", - "version": "1.0.0" - } - }, - { - "package": "TemplateKit", - "repositoryURL": "https://github.com/vapor/template-kit.git", - "state": { - "branch": null, - "revision": "121ae51433df94cf6e15c09e1f1b0f7c77ff8d5c", - "version": "1.2.0" - } - }, - { - "package": "URLEncodedForm", - "repositoryURL": "https://github.com/vapor/url-encoded-form.git", - "state": { - "branch": null, - "revision": "82d8d63bdb76b6dd8febe916c639ab8608dbbaed", - "version": "1.0.6" - } - }, - { - "package": "Validation", - "repositoryURL": "https://github.com/vapor/validation.git", - "state": { - "branch": null, - "revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6", - "version": "2.1.1" - } - }, - { - "package": "Vapor", - "repositoryURL": "https://github.com/vapor/vapor.git", - "state": { - "branch": null, - "revision": "c86ada59b31c69f08a6abd4f776537cba48d5df6", - "version": "3.3.0" - } - }, - { - "package": "WebSocket", - "repositoryURL": "https://github.com/vapor/websocket.git", - "state": { - "branch": null, - "revision": "d85e5b6dce4d04065865f77385fc3324f98178f6", - "version": "1.1.2" - } - } - ] - }, - "version": 1 -} From bf6360338795fa396d5368bfe20af8cec16b8212 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 22:46:41 +0200 Subject: [PATCH 07/14] Update failing condition --- Sources/Gatekeeper/Gatekeeper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Gatekeeper/Gatekeeper.swift b/Sources/Gatekeeper/Gatekeeper.swift index 815513c..105f0f4 100644 --- a/Sources/Gatekeeper/Gatekeeper.swift +++ b/Sources/Gatekeeper/Gatekeeper.swift @@ -53,7 +53,7 @@ public struct Gatekeeper: Service { return cache.set(peerCacheKey, to: entry).transform(to: entry) }.map(to: Entry.self) { entry in - if entry.requestsLeft <= 0 { + if entry.requestsLeft < 0 { throw Abort( .tooManyRequests, reason: "Patience you must have, my young Padawan." From b3bdc42d1f61c6a90a9128aec2c26b5937c1a6fe Mon Sep 17 00:00:00 2001 From: cweinberger Date: Thu, 2 May 2019 22:46:53 +0200 Subject: [PATCH 08/14] Update tests --- Package.swift | 4 +- Tests/GatekeeperTests/GatekeeperTests.swift | 118 +++++++----------- .../Utilities/Request+test.swift | 41 ++++++ Tests/GatekeeperTests/XCTestManifests.swift | 18 +++ Tests/LinuxMain.swift | 9 +- 5 files changed, 110 insertions(+), 80 deletions(-) create mode 100644 Tests/GatekeeperTests/Utilities/Request+test.swift create mode 100644 Tests/GatekeeperTests/XCTestManifests.swift diff --git a/Package.swift b/Package.swift index 67cecd6..1d0257e 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( "Vapor" ]), .testTarget( - name: "GateKeeperTests", + name: "GatekeeperTests", dependencies: ["Gatekeeper"]), ] -) \ No newline at end of file +) diff --git a/Tests/GatekeeperTests/GatekeeperTests.swift b/Tests/GatekeeperTests/GatekeeperTests.swift index 0180e87..9fb2164 100644 --- a/Tests/GatekeeperTests/GatekeeperTests.swift +++ b/Tests/GatekeeperTests/GatekeeperTests.swift @@ -1,27 +1,21 @@ import XCTest - -import URI -import HTTP import Vapor -import Foundation - @testable import Gatekeeper class GatekeeperTests: XCTestCase { - static var allTests = [ - ("testGateKeeper", testGateKeeper), - ("testGateKeeperNoPeer", testGateKeeperNoPeer), - ("testGateKeeperCountRefresh", testGateKeeperCountRefresh), - ("testRefreshIntervalValues", testRefreshIntervalValues), - ] - - func testGateKeeper() { - let middleware = Gatekeeper(rate: Rate(10, per: .second)) - let request = getHTTPSRequest() - + + func testGateKeeper() throws { + + let request = try Request.test( + gatekeeperConfig: GatekeeperConfig(maxRequests: 10, per: .minute), + peerName: "::1" + ) + + let gateKeeper = try request.make(Gatekeeper.self) + for i in 1...11 { do { - _ = try middleware.respond(to: request, chainingTo: MockResponder()) + _ = try gateKeeper.accessEndpoint(on: request).wait() XCTAssertTrue(i <= 10, "ran \(i) times.") } catch let error as Abort { switch error.status { @@ -38,12 +32,18 @@ class GatekeeperTests: XCTestCase { } } - func testGateKeeperNoPeer() { - let middleware = Gatekeeper(rate: Rate(100, per: .second)) - let request = getHTTPRequest() - + func testGateKeeperNoPeer() throws { + + let request = try Request.test( + gatekeeperConfig: GatekeeperConfig(maxRequests: 10, per: .minute), + peerName: nil + ) + + let gateKeeper = try request.make(Gatekeeper.self) + do { - _ = try middleware.respond(to: request, chainingTo: MockResponder()) + _ = try gateKeeper.accessEndpoint(on: request).wait() + XCTAssertTrue(false, "Gatekeeper should throw") } catch let error as Abort { switch error.status { case .forbidden: @@ -56,81 +56,51 @@ class GatekeeperTests: XCTestCase { XCTFail("Rate limiter failed: \(error)") } } - - func testGateKeeperCountRefresh() { - let middleware = Gatekeeper(rate: Rate(100, per: .second)) - let request = getHTTPSRequest() - + + func testGateKeeperCountRefresh() throws { + + let request = try Request.test( + gatekeeperConfig: GatekeeperConfig(maxRequests: 100, per: .second), + peerName: "192.168.1.2" + ) + + let gateKeeper = try request.make(Gatekeeper.self) + for _ in 0..<50 { do { - _ = try middleware.respond(to: request, chainingTo: MockResponder()) + _ = try gateKeeper.accessEndpoint(on: request).wait() } catch { XCTFail("Rate limiter failed: \(error)") break } } - - var requestsLeft = try! middleware.cache.get("192.168.1.2")?["requestsLeft"]?.int - XCTAssertEqual(requestsLeft, 50) + + let cache = try request.make(KeyedCache.self) + var entry = try! cache.get("gatekeeper_192.168.1.2", as: Gatekeeper.Entry.self).wait() + XCTAssertEqual(entry!.requestsLeft, 50) Thread.sleep(forTimeInterval: 1) do { - _ = try middleware.respond(to: request, chainingTo: MockResponder()) + _ = try gateKeeper.accessEndpoint(on: request).wait() } catch { XCTFail("Rate limiter failed: \(error)") } - - requestsLeft = try! middleware.cache.get("192.168.1.2")?["requestsLeft"]?.int - XCTAssertEqual(requestsLeft, 99, "Requests left should've reset") + + entry = try! cache.get("gatekeeper_192.168.1.2", as: Gatekeeper.Entry.self).wait() + XCTAssertEqual(entry!.requestsLeft, 99, "Requests left should've reset") } func testRefreshIntervalValues() { - let expected: [(Rate.Interval, Double)] = [ + let expected: [(GatekeeperConfig.Interval, Double)] = [ (.second, 1), (.minute, 60), (.hour, 3_600), (.day, 86_400) ] - + expected.forEach { interval, expected in - let rate = Rate(1, per: interval) + let rate = GatekeeperConfig(maxRequests: 1, per: interval) XCTAssertEqual(rate.refreshInterval, expected) } } } - -extension GatekeeperTests { - var developmentDrop: Droplet { - let config = try! Config() - config.environment = .development - return try! Droplet(config: config) - } - - var productionDrop: Droplet { - let config = try! Config() - config.environment = .production - return try! Droplet(config: config) - } - - func getHTTPRequest() -> Request { - return Request(method: .get, uri: "http://localhost:8080/") - } - - func getHTTPSRequest() -> Request { - - var headers = [HeaderKey: String]() - headers["X-Forwarded-For"] = "192.168.1.2" - - return try! Request( - method: .get, - uri: URI("https://localhost:8080/"), - headers: headers - ) - } -} - -struct MockResponder: Responder { - func respond(to request: Request) throws -> Response { - return "Hello, world".makeResponse() - } -} diff --git a/Tests/GatekeeperTests/Utilities/Request+test.swift b/Tests/GatekeeperTests/Utilities/Request+test.swift new file mode 100644 index 0000000..26e0e04 --- /dev/null +++ b/Tests/GatekeeperTests/Utilities/Request+test.swift @@ -0,0 +1,41 @@ +import Gatekeeper +import HTTP +import Vapor + +extension Request { + static func test( + gatekeeperConfig: GatekeeperConfig, + url: URLRepresentable = "http://localhost:8080/test", + peerName: String? = "::1" + ) throws -> Request { + let config = Config() + + var services = Services() + services.register(KeyedCache.self) { container in + return MemoryKeyedCache() + } + + try services.register(GatekeeperProvider(config: gatekeeperConfig)) + + let sharedThreadPool = BlockingIOThreadPool(numberOfThreads: 2) + sharedThreadPool.start() + services.register(sharedThreadPool) + + let app = try Application(config: config, environment: .testing, services: services) + let request = Request( + http: HTTPRequest( + method: .GET, + url: url + ), + using: app + ) + + var http = request.http + if let peerName = peerName { + http.headers.add(name: .init("X-Forwarded-For"), value: peerName) + } + request.http = http + + return request + } +} diff --git a/Tests/GatekeeperTests/XCTestManifests.swift b/Tests/GatekeeperTests/XCTestManifests.swift new file mode 100644 index 0000000..eba17e3 --- /dev/null +++ b/Tests/GatekeeperTests/XCTestManifests.swift @@ -0,0 +1,18 @@ +import XCTest + +extension GatekeeperTests { + static let __allTests = [ + ("testGateKeeper", testGateKeeper), + ("testGateKeeperNoPeer", testGateKeeperNoPeer), + ("testGateKeeperCountRefresh", testGateKeeperCountRefresh), + ("testRefreshIntervalValues", testRefreshIntervalValues), + ] +} + +#if !os(macOS) +public func __allTests() -> [XCTestCaseEntry] { + return [ + testCase(GatekeeperTests.__allTests), + ] +} +#endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index f20a0ee..4a838d0 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,7 +1,8 @@ import XCTest -@testable import GatekeeperTests +import GatekeeperTests -XCTMain([ - testCase(GatekeeperTests.allTests), -]) +var tests = [XCTestCaseEntry]() +tests += GatekeeperTests.__allTests() + +XCTMain(tests) From 6002eacace49ac2626f77cd35ce51bae3ca3f64b Mon Sep 17 00:00:00 2001 From: cweinberger Date: Fri, 3 May 2019 07:49:54 +0200 Subject: [PATCH 09/14] Remove debug output --- Sources/Gatekeeper/GatekeeperMiddleware.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Gatekeeper/GatekeeperMiddleware.swift b/Sources/Gatekeeper/GatekeeperMiddleware.swift index a65e507..52b2ebc 100644 --- a/Sources/Gatekeeper/GatekeeperMiddleware.swift +++ b/Sources/Gatekeeper/GatekeeperMiddleware.swift @@ -11,7 +11,6 @@ extension GatekeeperMiddleware: Middleware { ) throws -> EventLoopFuture { return try gatekeeper.accessEndpoint(on: request).do { entry in - print("Gatekeeper Entry: \(entry)") }.flatMap { _ in return try next.respond(to: request) } From 944f444db39034afe2cf3020447b0309fc6f1051 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Fri, 3 May 2019 08:08:12 +0200 Subject: [PATCH 10/14] Increase test coverage --- Tests/GatekeeperTests/GatekeeperTests.swift | 43 ++++++++++++++++--- .../Utilities/Request+test.swift | 20 ++++++++- .../Utilities/TestResponder.swift | 7 +++ 3 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 Tests/GatekeeperTests/Utilities/TestResponder.swift diff --git a/Tests/GatekeeperTests/GatekeeperTests.swift b/Tests/GatekeeperTests/GatekeeperTests.swift index 9fb2164..5bf66b4 100644 --- a/Tests/GatekeeperTests/GatekeeperTests.swift +++ b/Tests/GatekeeperTests/GatekeeperTests.swift @@ -11,11 +11,11 @@ class GatekeeperTests: XCTestCase { peerName: "::1" ) - let gateKeeper = try request.make(Gatekeeper.self) + let gatekeeperMiddleware = try request.make(GatekeeperMiddleware.self) for i in 1...11 { do { - _ = try gateKeeper.accessEndpoint(on: request).wait() + _ = try gatekeeperMiddleware.respond(to: request, chainingTo: TestResponder()).wait() XCTAssertTrue(i <= 10, "ran \(i) times.") } catch let error as Abort { switch error.status { @@ -39,10 +39,10 @@ class GatekeeperTests: XCTestCase { peerName: nil ) - let gateKeeper = try request.make(Gatekeeper.self) + let gatekeeperMiddleware = try request.make(GatekeeperMiddleware.self) do { - _ = try gateKeeper.accessEndpoint(on: request).wait() + _ = try gatekeeperMiddleware.respond(to: request, chainingTo: TestResponder()).wait() XCTAssertTrue(false, "Gatekeeper should throw") } catch let error as Abort { switch error.status { @@ -64,11 +64,11 @@ class GatekeeperTests: XCTestCase { peerName: "192.168.1.2" ) - let gateKeeper = try request.make(Gatekeeper.self) + let gatekeeperMiddleware = try request.make(GatekeeperMiddleware.self) for _ in 0..<50 { do { - _ = try gateKeeper.accessEndpoint(on: request).wait() + _ = try gatekeeperMiddleware.respond(to: request, chainingTo: TestResponder()).wait() } catch { XCTFail("Rate limiter failed: \(error)") break @@ -81,7 +81,7 @@ class GatekeeperTests: XCTestCase { Thread.sleep(forTimeInterval: 1) do { - _ = try gateKeeper.accessEndpoint(on: request).wait() + _ = try gatekeeperMiddleware.respond(to: request, chainingTo: TestResponder()).wait() } catch { XCTFail("Rate limiter failed: \(error)") } @@ -90,6 +90,35 @@ class GatekeeperTests: XCTestCase { XCTAssertEqual(entry!.requestsLeft, 99, "Requests left should've reset") } + func testGateKeeperWithCacheFactory() throws { + + let request = try Request.test( + gatekeeperConfig: GatekeeperConfig(maxRequests: 10, per: .minute), + peerName: "::1", + cacheFactory: { try $0.make(KeyedCache.self) } + ) + + let gatekeeperMiddleware = try request.make(GatekeeperMiddleware.self) + + for i in 1...11 { + do { + _ = try gatekeeperMiddleware.respond(to: request, chainingTo: TestResponder()).wait() + XCTAssertTrue(i <= 10, "ran \(i) times.") + } catch let error as Abort { + switch error.status { + case .tooManyRequests: + //success + XCTAssertEqual(i, 11, "Should've failed after the 11th attempt.") + break + default: + XCTFail("Expected too many request: \(error)") + } + } catch { + XCTFail("Caught wrong error: \(error)") + } + } + } + func testRefreshIntervalValues() { let expected: [(GatekeeperConfig.Interval, Double)] = [ (.second, 1), diff --git a/Tests/GatekeeperTests/Utilities/Request+test.swift b/Tests/GatekeeperTests/Utilities/Request+test.swift index 26e0e04..04d8088 100644 --- a/Tests/GatekeeperTests/Utilities/Request+test.swift +++ b/Tests/GatekeeperTests/Utilities/Request+test.swift @@ -6,7 +6,8 @@ extension Request { static func test( gatekeeperConfig: GatekeeperConfig, url: URLRepresentable = "http://localhost:8080/test", - peerName: String? = "::1" + peerName: String? = "::1", + cacheFactory: ((Container) throws -> KeyedCache)? = nil ) throws -> Request { let config = Config() @@ -15,7 +16,22 @@ extension Request { return MemoryKeyedCache() } - try services.register(GatekeeperProvider(config: gatekeeperConfig)) + if let cacheFactory = cacheFactory { + try services.register( + GatekeeperProvider( + config: gatekeeperConfig, + cacheFactory: cacheFactory + ) + ) + } else { + try services.register( + GatekeeperProvider( + config: gatekeeperConfig + ) + ) + } + + services.register(GatekeeperMiddleware.self) let sharedThreadPool = BlockingIOThreadPool(numberOfThreads: 2) sharedThreadPool.start() diff --git a/Tests/GatekeeperTests/Utilities/TestResponder.swift b/Tests/GatekeeperTests/Utilities/TestResponder.swift new file mode 100644 index 0000000..1c53793 --- /dev/null +++ b/Tests/GatekeeperTests/Utilities/TestResponder.swift @@ -0,0 +1,7 @@ +import Vapor + +public struct TestResponder: Responder { + public func respond(to req: Request) throws -> EventLoopFuture { + return req.future(req.response()) + } +} From efdb1948d2c2023a025f692a90c14dd6ded405c9 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Fri, 3 May 2019 13:14:55 +0200 Subject: [PATCH 11/14] Update CircleCI, gitignore and SwiftLint to match nodes-vapor/template --- .circleci/config.yml | 8 ++--- .gitignore | 79 +++++--------------------------------------- .swiftlint.yml | 7 ++-- 3 files changed, 17 insertions(+), 77 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c9b78ec..806eafb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ jobs: - checkout - restore_cache: keys: - - v2-spm-deps-{{ checksum "Package.swift" }} + - v1-spm-deps-{{ checksum "Package.swift" }} - run: name: Install dependencies command: | @@ -27,7 +27,7 @@ jobs: command: | bash <(curl -s https://codecov.io/bash) - save_cache: - key: v2-spm-deps-{{ checksum "Package.swift" }} + key: v1-spm-deps-{{ checksum "Package.swift" }} paths: - .build Linux: @@ -37,7 +37,7 @@ jobs: - checkout - restore_cache: keys: - - v2l-spm-deps-{{ checksum "Package.swift" }} + - v1l-spm-deps-{{ checksum "Package.swift" }} - run: name: Copy Package File command: cp Package.swift res @@ -50,7 +50,7 @@ jobs: name: Restoring Package File command: mv res Package.swift - save_cache: - key: v2l-spm-deps-{{ checksum "Package.swift" }} + key: v1l-spm-deps-{{ checksum "Package.swift" }} paths: - .build workflows: diff --git a/.gitignore b/.gitignore index 3ca8c63..460f13c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,71 +1,10 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -Package.resolved +Packages +.build +.idea +xcuserdata *.xcodeproj -Packages/ - -## Build generated -build/ -DerivedData/ - -## Various settings -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata/ - -## Other -*.moved-aside -*.xcuserstate - -## Obj-C/Swift specific -*.hmap -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -.build/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# Pods/ - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output - -Package.pins \ No newline at end of file +Config/secrets/ +.DS_Store +.swift-version +CMakeLists.txt +Package.resolved \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml index 1e79593..5777ef8 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -2,11 +2,12 @@ included: - Sources function_body_length: warning: 60 -variable_name: +identifier_name: min_length: warning: 2 -line_length: 80 +line_length: 100 disabled_rules: - opening_brace + - nesting colon: - flexible_right_spacing: true + flexible_right_spacing: true \ No newline at end of file From dc805486b48e1c2672ebefee01a2a947d9907182 Mon Sep 17 00:00:00 2001 From: cweinberger Date: Fri, 3 May 2019 13:19:27 +0200 Subject: [PATCH 12/14] Incorporate minor comments from PR --- Sources/Gatekeeper/Gatekeeper.swift | 2 +- Sources/Gatekeeper/GatekeeperMiddleware.swift | 5 ++--- Tests/GatekeeperTests/GatekeeperTests.swift | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/Gatekeeper/Gatekeeper.swift b/Sources/Gatekeeper/Gatekeeper.swift index 105f0f4..202d0e5 100644 --- a/Sources/Gatekeeper/Gatekeeper.swift +++ b/Sources/Gatekeeper/Gatekeeper.swift @@ -56,7 +56,7 @@ public struct Gatekeeper: Service { if entry.requestsLeft < 0 { throw Abort( .tooManyRequests, - reason: "Patience you must have, my young Padawan." + reason: "Slow down. You sent too many requests." ) } return entry diff --git a/Sources/Gatekeeper/GatekeeperMiddleware.swift b/Sources/Gatekeeper/GatekeeperMiddleware.swift index 52b2ebc..3f91575 100644 --- a/Sources/Gatekeeper/GatekeeperMiddleware.swift +++ b/Sources/Gatekeeper/GatekeeperMiddleware.swift @@ -8,10 +8,9 @@ extension GatekeeperMiddleware: Middleware { public func respond( to request: Request, chainingTo next: Responder - ) throws -> EventLoopFuture { + ) throws -> Future { - return try gatekeeper.accessEndpoint(on: request).do { entry in - }.flatMap { _ in + return try gatekeeper.accessEndpoint(on: request).flatMap { _ in return try next.respond(to: request) } } diff --git a/Tests/GatekeeperTests/GatekeeperTests.swift b/Tests/GatekeeperTests/GatekeeperTests.swift index 5bf66b4..60b1bf3 100644 --- a/Tests/GatekeeperTests/GatekeeperTests.swift +++ b/Tests/GatekeeperTests/GatekeeperTests.swift @@ -43,7 +43,7 @@ class GatekeeperTests: XCTestCase { do { _ = try gatekeeperMiddleware.respond(to: request, chainingTo: TestResponder()).wait() - XCTAssertTrue(false, "Gatekeeper should throw") + XCTFail("Gatekeeper should throw") } catch let error as Abort { switch error.status { case .forbidden: @@ -76,7 +76,7 @@ class GatekeeperTests: XCTestCase { } let cache = try request.make(KeyedCache.self) - var entry = try! cache.get("gatekeeper_192.168.1.2", as: Gatekeeper.Entry.self).wait() + var entry = try cache.get("gatekeeper_192.168.1.2", as: Gatekeeper.Entry.self).wait() XCTAssertEqual(entry!.requestsLeft, 50) Thread.sleep(forTimeInterval: 1) From 11523fe4d80a0952c9bbdf31503f2e50033b73a4 Mon Sep 17 00:00:00 2001 From: Christian Weinberger Date: Fri, 3 May 2019 13:25:01 +0200 Subject: [PATCH 13/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e695775..f7f876e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ services.register(middlewares) ## Credits 🏆 This package is developed and maintained by the Vapor team at [Nodes](https://www.nodesagency.com). -The package owner for this project is [Tom](https://github.com/tomserowka). +The package owner for this project is [Christian](https://github.com/cweinberger). ## License 📄 From 695602e222dde2809ffec729fa8ee30ac052cc1f Mon Sep 17 00:00:00 2001 From: Christian Weinberger Date: Fri, 3 May 2019 14:30:53 +0200 Subject: [PATCH 14/14] Update config.yml --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 806eafb..d207044 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,7 +37,7 @@ jobs: - checkout - restore_cache: keys: - - v1l-spm-deps-{{ checksum "Package.swift" }} + - v1-spm-deps-{{ checksum "Package.swift" }} - run: name: Copy Package File command: cp Package.swift res @@ -50,7 +50,7 @@ jobs: name: Restoring Package File command: mv res Package.swift - save_cache: - key: v1l-spm-deps-{{ checksum "Package.swift" }} + key: v1-spm-deps-{{ checksum "Package.swift" }} paths: - .build workflows: @@ -64,4 +64,4 @@ experimental: branches: only: - master - - develop \ No newline at end of file + - develop