@@ -17,13 +17,13 @@ import NIOConcurrencyHelpers
17
17
import NIOHTTP1
18
18
19
19
/// A barebone HTTP client to interact with AWS Runtime Engine which is an HTTP server.
20
- internal class HTTPClient {
20
+ internal final class HTTPClient {
21
21
private let eventLoop : EventLoop
22
22
private let configuration : Lambda . Configuration . RuntimeEngine
23
23
private let targetHost : String
24
24
25
25
private var state = State . disconnected
26
- private let lock = Lock ( )
26
+ private let stateLock = Lock ( )
27
27
28
28
init ( eventLoop: EventLoop , configuration: Lambda . Configuration . RuntimeEngine ) {
29
29
self . eventLoop = eventLoop
@@ -46,54 +46,60 @@ internal class HTTPClient {
46
46
timeout: timeout ?? self . configuration. requestTimeout) )
47
47
}
48
48
49
-
49
+ // TODO: cap reconnect attempt
50
50
private func execute( _ request: Request ) -> EventLoopFuture < Response > {
51
- self . lock . lock ( )
51
+ self . stateLock . lock ( )
52
52
switch self . state {
53
+ case . disconnected:
54
+ let promise = self . eventLoop. makePromise ( of: Response . self)
55
+ self . state = . connecting( promise. futureResult)
56
+ self . stateLock. unlock ( )
57
+ self . connect ( ) . flatMap { channel -> EventLoopFuture < Response > in
58
+ self . stateLock. withLock {
59
+ guard case . connecting = self . state else {
60
+ preconditionFailure ( " invalid state \( self . state) " )
61
+ }
62
+ self . state = . connected( channel)
63
+ }
64
+ return self . execute ( request)
65
+ } . cascade ( to: promise)
66
+ return promise. futureResult
67
+ case . connecting( let future) :
68
+ let future = future. flatMap { _ in
69
+ self . execute ( request)
70
+ }
71
+ self . state = . connecting( future)
72
+ self . stateLock. unlock ( )
73
+ return future
53
74
case . connected( let channel) :
54
75
guard channel. isActive else {
55
- // attempt to reconnect
56
76
self . state = . disconnected
57
- self . lock . unlock ( )
77
+ self . stateLock . unlock ( )
58
78
return self . execute ( request)
59
79
}
60
- self . lock . unlock ( )
80
+ self . stateLock . unlock ( )
61
81
let promise = channel. eventLoop. makePromise ( of: Response . self)
62
82
let wrapper = HTTPRequestWrapper ( request: request, promise: promise)
63
- return channel. writeAndFlush ( wrapper) . flatMap {
64
- promise. futureResult
65
- }
66
- case . disconnected:
67
- return self . connect ( ) . flatMap {
68
- self . lock. unlock ( )
69
- return self . execute ( request)
70
- }
71
- default :
72
- preconditionFailure ( " invalid state \( self . state) " )
83
+ channel. writeAndFlush ( wrapper) . cascadeFailure ( to: promise)
84
+ return promise. futureResult
73
85
}
74
86
}
75
87
76
- private func connect( ) -> EventLoopFuture < Void > {
77
- guard case . disconnected = self . state else {
78
- preconditionFailure ( " invalid state \( self . state) " )
79
- }
80
- self . state = . connecting
81
- let bootstrap = ClientBootstrap ( group: eventLoop)
88
+ private func connect( ) -> EventLoopFuture < Channel > {
89
+ let bootstrap = ClientBootstrap ( group: self . eventLoop)
82
90
. channelInitializer { channel in
83
91
channel. pipeline. addHTTPClientHandlers ( ) . flatMap {
84
92
channel. pipeline. addHandlers ( [ HTTPHandler ( keepAlive: self . configuration. keepAlive) ,
85
93
UnaryHandler ( keepAlive: self . configuration. keepAlive) ] )
86
94
}
87
95
}
88
-
96
+
89
97
do {
90
98
// connect directly via socket address to avoid happy eyeballs (perf)
91
99
let address = try SocketAddress ( ipAddress: self . configuration. ip, port: self . configuration. port)
92
- return bootstrap. connect ( to: address) . flatMapThrowing { channel in
93
- self . state = . connected( channel)
94
- }
100
+ return bootstrap. connect ( to: address)
95
101
} catch {
96
- return eventLoop. makeFailedFuture ( error)
102
+ return self . eventLoop. makeFailedFuture ( error)
97
103
}
98
104
}
99
105
@@ -127,13 +133,13 @@ internal class HTTPClient {
127
133
}
128
134
129
135
private enum State {
130
- case connecting
131
- case connected( Channel )
132
136
case disconnected
137
+ case connecting( EventLoopFuture < Response > )
138
+ case connected( Channel )
133
139
}
134
140
}
135
141
136
- private class HTTPHandler : ChannelDuplexHandler {
142
+ private final class HTTPHandler : ChannelDuplexHandler {
137
143
typealias OutboundIn = HTTPClient . Request
138
144
typealias InboundOut = HTTPClient . Response
139
145
typealias InboundIn = HTTPClientResponsePart
@@ -208,15 +214,15 @@ private class HTTPHandler: ChannelDuplexHandler {
208
214
}
209
215
}
210
216
211
- private class UnaryHandler : ChannelInboundHandler , ChannelOutboundHandler {
217
+ private final class UnaryHandler : ChannelInboundHandler , ChannelOutboundHandler {
212
218
typealias OutboundIn = HTTPRequestWrapper
213
219
typealias InboundIn = HTTPClient . Response
214
220
typealias OutboundOut = HTTPClient . Request
215
221
216
222
private let keepAlive : Bool
217
223
218
224
private let lock = Lock ( )
219
- private var pendingResponses = CircularBuffer < ( EventLoopPromise < HTTPClient . Response > , Scheduled < Void > ? ) > ( )
225
+ private var pendingResponses = CircularBuffer < ( promise : EventLoopPromise < HTTPClient . Response > , timeout : Scheduled < Void > ? ) > ( )
220
226
private var lastError : Error ?
221
227
222
228
init ( keepAlive: Bool ) {
@@ -232,19 +238,21 @@ private class UnaryHandler: ChannelInboundHandler, ChannelOutboundHandler {
232
238
}
233
239
}
234
240
}
235
- self . lock. withLockVoid { pendingResponses. append ( ( wrapper. promise, timeoutTask) ) }
241
+ self . lock. withLockVoid { pendingResponses. append ( ( promise : wrapper. promise, timeout : timeoutTask) ) }
236
242
context. writeAndFlush ( wrapOutboundOut ( wrapper. request) , promise: promise)
237
243
}
238
244
239
245
func channelRead( context: ChannelHandlerContext , data: NIOAny ) {
240
246
let response = unwrapInboundIn ( data)
241
247
if let pending = ( self . lock. withLock { self . pendingResponses. popFirst ( ) } ) {
242
248
let serverKeepAlive = response. headers [ " connection " ] . first? . lowercased ( ) == " keep-alive "
243
- let future = self . keepAlive && serverKeepAlive ? context . eventLoop . makeSucceededFuture ( ( ) ) : context . channel . close ( )
244
- future . whenComplete { _ in
245
- pending . 1 ? . cancel ( )
246
- pending . 0 . succeed ( response )
249
+ if ! self . keepAlive || ! serverKeepAlive {
250
+ pending . promise . futureResult . whenComplete { _ in
251
+ _ = context . channel . close ( )
252
+ }
247
253
}
254
+ pending. timeout? . cancel ( )
255
+ pending. promise. succeed ( response)
248
256
}
249
257
}
250
258
0 commit comments