Vapor:如何协调多个请求,直到完成一个中央请求
Vapor: How to coordinate multiple requests until one central one is done
我有一个中间件,如果在 Redis 中找不到令牌,它会获取一个令牌。
struct TokenMiddleware: Middleware, TokenAccessor {
func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-ID")) else {
throw Abort(.badRequest, reason: "missing 'Client-ID' in header")
}
guard request.clientID.isEmpty == false else {
throw Abort(.badRequest, reason: "'Client-ID' in header is empty")
}
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-Secret")) else {
throw Abort(.badRequest, reason: "missing 'Client-Secret' in header")
}
/// getToken fetches a new Token and stores it in Redis for the controller to use
return try self.getToken(request: request).flatMap(to: Response.self) { token in
return try next.respond(to: request)
}
}
}
extension TokenMiddleware: Service {}
但这会导致多个进程自行获取新令牌,从而导致竞争条件。
我该如何处理这个问题?
我现在解决了这个问题,感谢来自 http://khanlou.com/2017/09/dispatch-on-the-server/ who hinted me into the right direction. More infos on the DispatchQueues can be found in an excellent article from https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2
的 Soroush
所以:
在服务器上的 iOS 和 Vapor 中,我们都可以创建一个 DispatchQueue
。在我的例子中,我使用的是并发的,在令牌读取、获取(如果需要)和令牌写入发生的关键部分,我使用了一个屏障。
屏障只允许一个进入,因此在这部分中,所有内容都像串行队列一样执行。
希望这对可能遇到相同问题的任何人有所帮助
import Vapor
protocol TokenAccessor: RedisAccessor {
}
extension TokenAccessor {
/// Main convenience function that handles expiry, refetching etc
///
/// - Check if token was saved before
/// - We store the token in redis
/// - We use redis TTL feature to handle token expiry
///
func getToken(request: Request) throws -> Future<Token> {
print(":getToken(request:)")
let promise = request.eventLoop.newPromise(Token.self)
return request.withNewConnection(to: .redis) { redis in
let concurrentQueue = DispatchQueue(label: "com.queuename.gettoken",
attributes: .concurrent)
/// Making the concurrent queue serial because only one is allowed to fetch a new token at a time
concurrentQueue.async(flags: .barrier) {
let _ = redis.get(request.clientIdLastDigits, as: String.self).map(to: Void.self) { tokenOpt in
guard let accessToken = tokenOpt else {
try self.fetchNewToken(forRequest: request).do { newToken in
print("fetched a new token")
promise.succeed(result: newToken)
}.catch { error in
print("failed fetching a new token")
promise.fail(error: error)
}
return
}
print("got a valid token from redis")
let token = Token(client: request.clientIdLastDigits, token: accessToken, expiresIn: Date())
// return request.future(token)
promise.succeed(result: token)
}
}
return promise.futureResult
}
}
...
这是通过中间件在我的方法前面触发的(所以我不需要考虑它)
import Vapor
struct TokenMiddleware: Middleware, TokenAccessor {
func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-ID")) else {
throw Abort(.badRequest, reason: "missing 'Client-ID' in header")
}
guard request.clientID.isEmpty == false else {
throw Abort(.badRequest, reason: "'Client-ID' in header is empty")
}
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-Secret")) else {
throw Abort(.badRequest, reason: "missing 'Client-Secret' in header")
}
return try self.getToken(request: request).flatMap(to: Response.self) { token in
return try next.respond(to: request)
}
}
}
extension TokenMiddleware: Service {}
我有一个中间件,如果在 Redis 中找不到令牌,它会获取一个令牌。
struct TokenMiddleware: Middleware, TokenAccessor {
func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-ID")) else {
throw Abort(.badRequest, reason: "missing 'Client-ID' in header")
}
guard request.clientID.isEmpty == false else {
throw Abort(.badRequest, reason: "'Client-ID' in header is empty")
}
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-Secret")) else {
throw Abort(.badRequest, reason: "missing 'Client-Secret' in header")
}
/// getToken fetches a new Token and stores it in Redis for the controller to use
return try self.getToken(request: request).flatMap(to: Response.self) { token in
return try next.respond(to: request)
}
}
}
extension TokenMiddleware: Service {}
但这会导致多个进程自行获取新令牌,从而导致竞争条件。
我该如何处理这个问题?
我现在解决了这个问题,感谢来自 http://khanlou.com/2017/09/dispatch-on-the-server/ who hinted me into the right direction. More infos on the DispatchQueues can be found in an excellent article from https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2
的 Soroush所以:
在服务器上的 iOS 和 Vapor 中,我们都可以创建一个 DispatchQueue
。在我的例子中,我使用的是并发的,在令牌读取、获取(如果需要)和令牌写入发生的关键部分,我使用了一个屏障。
屏障只允许一个进入,因此在这部分中,所有内容都像串行队列一样执行。
希望这对可能遇到相同问题的任何人有所帮助
import Vapor
protocol TokenAccessor: RedisAccessor {
}
extension TokenAccessor {
/// Main convenience function that handles expiry, refetching etc
///
/// - Check if token was saved before
/// - We store the token in redis
/// - We use redis TTL feature to handle token expiry
///
func getToken(request: Request) throws -> Future<Token> {
print(":getToken(request:)")
let promise = request.eventLoop.newPromise(Token.self)
return request.withNewConnection(to: .redis) { redis in
let concurrentQueue = DispatchQueue(label: "com.queuename.gettoken",
attributes: .concurrent)
/// Making the concurrent queue serial because only one is allowed to fetch a new token at a time
concurrentQueue.async(flags: .barrier) {
let _ = redis.get(request.clientIdLastDigits, as: String.self).map(to: Void.self) { tokenOpt in
guard let accessToken = tokenOpt else {
try self.fetchNewToken(forRequest: request).do { newToken in
print("fetched a new token")
promise.succeed(result: newToken)
}.catch { error in
print("failed fetching a new token")
promise.fail(error: error)
}
return
}
print("got a valid token from redis")
let token = Token(client: request.clientIdLastDigits, token: accessToken, expiresIn: Date())
// return request.future(token)
promise.succeed(result: token)
}
}
return promise.futureResult
}
}
...
这是通过中间件在我的方法前面触发的(所以我不需要考虑它)
import Vapor
struct TokenMiddleware: Middleware, TokenAccessor {
func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-ID")) else {
throw Abort(.badRequest, reason: "missing 'Client-ID' in header")
}
guard request.clientID.isEmpty == false else {
throw Abort(.badRequest, reason: "'Client-ID' in header is empty")
}
guard let _ = request.http.headers.firstValue(name: HTTPHeaderName("Client-Secret")) else {
throw Abort(.badRequest, reason: "missing 'Client-Secret' in header")
}
return try self.getToken(request: request).flatMap(to: Response.self) { token in
return try next.respond(to: request)
}
}
}
extension TokenMiddleware: Service {}