Skip to content

Commit f268cd5

Browse files
committed
Update Gatekeeper for Vapor-3
1 parent 2a46c3e commit f268cd5

File tree

4 files changed

+156
-79
lines changed

4 files changed

+156
-79
lines changed

Sources/Gatekeeper/Gatekeeper.swift

+66-79
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,77 @@
1-
import HTTP
2-
import Cache
31
import Vapor
4-
import Foundation
52

6-
public struct Rate {
7-
public enum Interval {
8-
case second
9-
case minute
10-
case hour
11-
case day
12-
}
13-
14-
public let limit: Int
15-
public let interval: Interval
16-
17-
public init(_ limit: Int, per interval: Interval) {
18-
self.limit = limit
19-
self.interval = interval
20-
}
21-
22-
internal var refreshInterval: Double {
23-
switch interval {
24-
case .second:
25-
return 1
26-
case .minute:
27-
return 60
28-
case .hour:
29-
return 3_600
30-
case .day:
31-
return 86_400
32-
}
33-
}
34-
}
3+
public struct Gatekeeper: Service {
4+
5+
internal let config: GatekeeperConfig
6+
internal let cacheFactory: ((Container) throws -> KeyedCache)
357

36-
public struct Gatekeeper: Middleware {
37-
internal var cache: CacheProtocol
38-
39-
internal let limit: Int
40-
internal let refreshInterval: Double
41-
42-
public init(rate: Rate, cache: CacheProtocol = MemoryCache()) {
43-
self.cache = cache
44-
self.limit = rate.limit
45-
self.refreshInterval = rate.refreshInterval
8+
public init(
9+
config: GatekeeperConfig,
10+
cacheFactory: @escaping ((Container) throws -> KeyedCache) = { container in try container.make() }
11+
) {
12+
self.config = config
13+
self.cacheFactory = cacheFactory
4614
}
47-
48-
public func respond(to request: Request, chainingTo next: Responder) throws -> Response {
49-
guard let peer = request.peerHostname else {
15+
16+
public func accessEndpoint(
17+
on request: Request
18+
) throws -> Future<Gatekeeper.Entry> {
19+
20+
guard let peerHostName = request.http.remotePeer.hostname else {
5021
throw Abort(
5122
.forbidden,
52-
metadata: nil,
53-
reason: "Unable to verify peer."
23+
reason: "Unable to verify peer"
5424
)
5525
}
56-
57-
var entry = try cache.get(peer)
58-
var createdAt = entry?["createdAt"]?.double ?? Date().timeIntervalSince1970
59-
var requestsLeft = entry?["requestsLeft"]?.int ?? limit
60-
61-
let now = Date().timeIntervalSince1970
62-
if now - createdAt >= refreshInterval {
63-
createdAt = now
64-
requestsLeft = limit
65-
}
66-
67-
defer {
68-
do {
69-
try cache.set(peer, Node(node: [
70-
"createdAt": createdAt,
71-
"requestsLeft": requestsLeft
72-
]))
73-
} catch {
74-
print("WARNING: cache failed: \(error)")
26+
27+
let peerCacheKey = cacheKey(for: peerHostName)
28+
let cache = try cacheFactory(request)
29+
30+
return cache.get(peerCacheKey, as: Entry.self)
31+
.map(to: Entry.self) { entry in
32+
if let entry = entry {
33+
return entry
34+
} else {
35+
return Entry(
36+
peerHostname: peerHostName,
37+
createdAt: Date(),
38+
requestsLeft: self.config.limit
39+
)
40+
}
7541
}
76-
}
77-
78-
requestsLeft -= 1
79-
guard requestsLeft >= 0 else {
80-
throw Abort(
81-
.tooManyRequests,
82-
metadata: nil,
83-
reason: "Slow down."
84-
)
85-
}
86-
87-
let response = try next.respond(to: request)
88-
return response
42+
.map(to: Entry.self) { entry in
43+
44+
let now = Date()
45+
var mutableEntry = entry
46+
if now.timeIntervalSince1970 - entry.createdAt.timeIntervalSince1970 >= self.config.refreshInterval {
47+
mutableEntry.createdAt = now
48+
mutableEntry.requestsLeft = self.config.limit
49+
}
50+
mutableEntry.requestsLeft -= 1
51+
return mutableEntry
52+
}.then { entry in
53+
return cache.set(peerCacheKey, to: entry).transform(to: entry)
54+
}.map(to: Entry.self) { entry in
55+
56+
if entry.requestsLeft <= 0 {
57+
throw Abort(
58+
.tooManyRequests,
59+
reason: "Patience you must have, my young Padawan."
60+
)
61+
}
62+
return entry
63+
}
64+
}
65+
66+
private func cacheKey(for hostname: String) -> String {
67+
return "gatekeeper_\(hostname)"
68+
}
69+
}
70+
71+
extension Gatekeeper {
72+
public struct Entry: Codable {
73+
let peerHostname: String
74+
var createdAt: Date
75+
var requestsLeft: Int
8976
}
9077
}
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Vapor
2+
3+
public struct GatekeeperConfig: Service {
4+
5+
public enum Interval {
6+
case second
7+
case minute
8+
case hour
9+
case day
10+
}
11+
12+
public let limit: Int
13+
public let interval: Interval
14+
15+
public init(maxRequests limit: Int, per interval: Interval) {
16+
self.limit = limit
17+
self.interval = interval
18+
}
19+
20+
internal var refreshInterval: Double {
21+
switch interval {
22+
case .second:
23+
return 1
24+
case .minute:
25+
return 60
26+
case .hour:
27+
return 3_600
28+
case .day:
29+
return 86_400
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Vapor
2+
3+
public struct GatekeeperMiddleware {
4+
let gatekeeper: Gatekeeper
5+
}
6+
7+
extension GatekeeperMiddleware: Middleware {
8+
public func respond(
9+
to request: Request,
10+
chainingTo next: Responder
11+
) throws -> EventLoopFuture<Response> {
12+
13+
return try gatekeeper.accessEndpoint(on: request).do { entry in
14+
print("Gatekeeper Entry: \(entry)")
15+
}.flatMap { _ in
16+
return try next.respond(to: request)
17+
}
18+
}
19+
}
20+
21+
extension GatekeeperMiddleware: ServiceType {
22+
public static func makeService(for container: Container) throws -> GatekeeperMiddleware {
23+
return try .init(gatekeeper: container.make())
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Vapor
2+
3+
public final class GatekeeperProvider {
4+
5+
internal let config: GatekeeperConfig
6+
internal let cacheFactory: ((Container) throws -> KeyedCache)
7+
8+
public init(
9+
config: GatekeeperConfig,
10+
cacheFactory: @escaping ((Container) throws -> KeyedCache) = { container in try container.make() }
11+
) {
12+
self.config = config
13+
self.cacheFactory = cacheFactory
14+
}
15+
}
16+
17+
extension GatekeeperProvider: Provider {
18+
public func register(_ services: inout Services) throws {
19+
services.register(config)
20+
services.register(
21+
Gatekeeper(
22+
config: config,
23+
cacheFactory: cacheFactory
24+
),
25+
as: Gatekeeper.self
26+
)
27+
services.register(GatekeeperMiddleware.self)
28+
}
29+
30+
public func didBoot(_ container: Container) throws -> EventLoopFuture<Void> {
31+
return .done(on: container)
32+
}
33+
}

0 commit comments

Comments
 (0)