@@ -108,6 +108,30 @@ public class EventSource {
108
108
}
109
109
}
110
110
111
+ class ReconnectionTimer {
112
+ private let maxDelay : TimeInterval
113
+ private let resetInterval : TimeInterval
114
+
115
+ var backoffCount : Int = 0
116
+ var connectedTime : Date ?
117
+
118
+ init ( maxDelay: TimeInterval , resetInterval: TimeInterval ) {
119
+ self . maxDelay = maxDelay
120
+ self . resetInterval = resetInterval
121
+ }
122
+
123
+ func reconnectDelay( baseDelay: TimeInterval ) -> TimeInterval {
124
+ backoffCount += 1
125
+ if let connectedTime = connectedTime, Date ( ) . timeIntervalSince ( connectedTime) >= resetInterval {
126
+ backoffCount = 0
127
+ }
128
+ self . connectedTime = nil
129
+ let maxSleep = min ( maxDelay, baseDelay * pow( 2.0 , Double ( backoffCount) ) )
130
+ return maxSleep / 2 + Double. random ( in: 0 ... ( maxSleep / 2 ) )
131
+ }
132
+ }
133
+
134
+ // MARK: EventSourceDelegate
111
135
class EventSourceDelegate : NSObject , URLSessionDataDelegate {
112
136
private let delegateQueue : DispatchQueue = DispatchQueue ( label: " ESDelegateQueue " )
113
137
private let logger = Logs ( )
@@ -120,22 +144,19 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
120
144
}
121
145
}
122
146
123
- private var lastEventId : String ?
124
- private var reconnectTime : TimeInterval
125
- private var connectedTime : Date ?
126
-
127
- private var reconnectionAttempts : Int = 0
128
- private var errorHandlerAction : ConnectionErrorAction = . proceed
129
147
private let utf8LineParser : UTF8LineParser = UTF8LineParser ( )
130
- // swiftlint:disable:next implicitly_unwrapped_optional
131
- private var eventParser : EventParser !
148
+ private let eventParser : EventParser
149
+ private let reconnectionTimer : ReconnectionTimer
132
150
private var urlSession : URLSession ?
133
151
private var sessionTask : URLSessionDataTask ?
134
152
135
153
init ( config: EventSource . Config ) {
136
154
self . config = config
137
- self . lastEventId = config. lastEventId
138
- self . reconnectTime = config. reconnectTime
155
+ self . eventParser = EventParser ( handler: config. handler,
156
+ initialEventId: config. lastEventId,
157
+ initialRetry: config. reconnectTime)
158
+ self . reconnectionTimer = ReconnectionTimer ( maxDelay: config. maxReconnectTime,
159
+ resetInterval: config. backoffResetThreshold)
139
160
}
140
161
141
162
func start( ) {
@@ -153,19 +174,24 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
153
174
}
154
175
155
176
func stop( ) {
156
- let previousState = readyState
157
- readyState = . shutdown
158
- sessionTask? . cancel ( )
159
- if previousState == . open {
160
- config. handler. onClosed ( )
177
+ delegateQueue. async {
178
+ let previousState = self . readyState
179
+ self . readyState = . shutdown
180
+ self . sessionTask? . cancel ( )
181
+ if previousState == . open {
182
+ self . config. handler. onClosed ( )
183
+ }
184
+ self . urlSession? . invalidateAndCancel ( )
185
+ self . urlSession = nil
161
186
}
162
- urlSession? . invalidateAndCancel ( )
163
187
}
164
188
165
- func getLastEventId( ) -> String ? { lastEventId }
189
+ func getLastEventId( ) -> String ? { eventParser . getLastEventId ( ) }
166
190
167
191
func createSession( ) -> URLSession {
168
- URLSession ( configuration: config. urlSessionConfiguration, delegate: self , delegateQueue: nil )
192
+ let opQueue = OperationQueue ( )
193
+ opQueue. underlyingQueue = self . delegateQueue
194
+ return URLSession ( configuration: config. urlSessionConfiguration, delegate: self , delegateQueue: opQueue)
169
195
}
170
196
171
197
func createRequest( ) -> URLRequest {
@@ -174,7 +200,7 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
174
200
timeoutInterval: self . config. idleTimeout)
175
201
urlRequest. httpMethod = self . config. method
176
202
urlRequest. httpBody = self . config. body
177
- urlRequest. setValue ( self . lastEventId , forHTTPHeaderField: " Last-Event-Id " )
203
+ urlRequest. setValue ( eventParser . getLastEventId ( ) , forHTTPHeaderField: " Last-Event-Id " )
178
204
urlRequest. allHTTPHeaderFields = self . config. headerTransform (
179
205
urlRequest. allHTTPHeaderFields? . merging ( self . config. headers) { $1 } ?? self . config. headers
180
206
)
@@ -183,11 +209,6 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
183
209
184
210
private func connect( ) {
185
211
logger. log ( . info, " Starting EventSource client " )
186
- let connectionHandler : ConnectionHandler = (
187
- setReconnectionTime: { [ weak self] reconnectionTime in self ? . reconnectTime = reconnectionTime } ,
188
- setLastEventId: { [ weak self] eventId in self ? . lastEventId = eventId }
189
- )
190
- self . eventParser = EventParser ( handler: self . config. handler, connectionHandler: connectionHandler)
191
212
let task = urlSession? . dataTask ( with: createRequest ( ) )
192
213
task? . resume ( )
193
214
sessionTask = task
@@ -201,67 +222,44 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
201
222
return action
202
223
}
203
224
204
- private func afterComplete( ) {
205
- guard readyState != . shutdown
206
- else { return }
207
-
208
- var nextState : ReadyState = . closed
209
- let currentState : ReadyState = readyState
210
- if errorHandlerAction == . shutdown {
211
- logger. log ( . info, " Connection has been explicitly shut down by error handler " )
212
- nextState = . shutdown
213
- }
214
- readyState = nextState
215
-
216
- if currentState == . open {
217
- config. handler. onClosed ( )
218
- }
219
-
220
- if nextState != . shutdown {
221
- reconnect ( )
222
- }
223
- }
224
-
225
- private func reconnect( ) {
226
- reconnectionAttempts += 1
227
-
228
- if let connectedTime = connectedTime, Date ( ) . timeIntervalSince ( connectedTime) >= config. backoffResetThreshold {
229
- reconnectionAttempts = 0
230
- }
231
- self . connectedTime = nil
232
-
233
- let maxSleep = min ( config. maxReconnectTime, reconnectTime * pow( 2.0 , Double ( reconnectionAttempts) ) )
234
- let sleep = maxSleep / 2 + Double. random ( in: 0 ... ( maxSleep / 2 ) )
235
-
236
- logger. log ( . info, " Waiting %.3f seconds before reconnecting... " , sleep)
237
- delegateQueue. asyncAfter ( deadline: . now( ) + sleep) { [ weak self] in
238
- self ? . connect ( )
239
- }
240
- }
241
-
242
225
// MARK: URLSession Delegates
243
226
244
227
// Tells the delegate that the task finished transferring data.
245
228
public func urlSession( _ session: URLSession ,
246
229
task: URLSessionTask ,
247
230
didCompleteWithError error: Error ? ) {
248
231
utf8LineParser. closeAndReset ( )
249
- eventParser. reset ( )
232
+ let currentRetry = eventParser. reset ( )
233
+
234
+ guard readyState != . shutdown
235
+ else { return }
250
236
251
237
if let error = error {
252
- // Ignore cancelled error
253
- if ( error as NSError ) . code == NSURLErrorCancelled {
254
- } else if readyState != . shutdown && errorHandlerAction != . shutdown {
238
+ if ( error as NSError ) . code != NSURLErrorCancelled {
255
239
logger. log ( . info, " Connection error: %@ " , error. localizedDescription)
256
- errorHandlerAction = dispatchError ( error: error)
257
- } else {
258
- errorHandlerAction = . shutdown
240
+ if dispatchError ( error: error) == . shutdown {
241
+ logger. log ( . info, " Connection has been explicitly shut down by error handler " )
242
+ if readyState == . open {
243
+ config. handler. onClosed ( )
244
+ }
245
+ readyState = . shutdown
246
+ return
247
+ }
259
248
}
260
249
} else {
261
250
logger. log ( . info, " Connection unexpectedly closed. " )
262
251
}
263
252
264
- afterComplete ( )
253
+ if readyState == . open {
254
+ config. handler. onClosed ( )
255
+ }
256
+
257
+ readyState = . closed
258
+ let sleep = reconnectionTimer. reconnectDelay ( baseDelay: currentRetry)
259
+ logger. log ( . info, " Waiting %.3f seconds before reconnecting... " , sleep)
260
+ delegateQueue. asyncAfter ( deadline: . now( ) + sleep) { [ weak self] in
261
+ self ? . connect ( )
262
+ }
265
263
}
266
264
267
265
// Tells the delegate that the data task received the initial reply (headers) from the server.
@@ -280,13 +278,16 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
280
278
// swiftlint:disable:next force_cast
281
279
let httpResponse = response as! HTTPURLResponse
282
280
if ( 200 ..< 300 ) . contains ( httpResponse. statusCode) {
283
- connectedTime = Date ( )
281
+ reconnectionTimer . connectedTime = Date ( )
284
282
readyState = . open
285
283
config. handler. onOpened ( )
286
284
completionHandler ( . allow)
287
285
} else {
288
286
logger. log ( . info, " Unsuccessful response: %d " , httpResponse. statusCode)
289
- errorHandlerAction = dispatchError ( error: UnsuccessfulResponseError ( responseCode: httpResponse. statusCode) )
287
+ if dispatchError ( error: UnsuccessfulResponseError ( responseCode: httpResponse. statusCode) ) == . shutdown {
288
+ logger. log ( . info, " Connection has been explicitly shut down by error handler " )
289
+ readyState = . shutdown
290
+ }
290
291
completionHandler ( . cancel)
291
292
}
292
293
}
0 commit comments