Skip to content

Commit e7d23e6

Browse files
authored
feat: HTTP Status 204 should halt stream (#59)
1 parent f50e92d commit e7d23e6

File tree

3 files changed

+59
-7
lines changed

3 files changed

+59
-7
lines changed

ContractTestService/Sources/ContractTestService/main.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import LDSwiftEventSource
55

66
struct StatusResp: Encodable {
77
let name = "swift-eventsource"
8-
let capabilities = ["comments", "headers", "last-event-id", "post", "read-timeout", "report"]
8+
let capabilities = ["server-directed-shutdown-request", "comments", "headers", "last-event-id", "post", "read-timeout", "report"]
99
}
1010

1111
struct CreateStreamReq: Decodable {

Source/LDSwiftEventSource.swift

+16-5
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,19 @@ public class EventSource {
104104
/**
105105
An error handler that is called when an error occurs and can shut down the client in response.
106106

107-
The default error handler will always attempt to reconnect on an error, unless `EventSource.stop()` is called.
107+
The default error handler will always attempt to reconnect on an
108+
error, unless `EventSource.stop()` is called or the error code is 204.
108109
*/
109-
public var connectionErrorHandler: ConnectionErrorHandler = { _ in .proceed }
110+
public var connectionErrorHandler: ConnectionErrorHandler = { error in
111+
guard let unsuccessfulResponseError = error as? UnsuccessfulResponseError
112+
else { return .proceed }
113+
114+
let responseCode: Int = unsuccessfulResponseError.responseCode
115+
if 204 == responseCode {
116+
return .shutdown
117+
}
118+
return .proceed
119+
}
110120

111121
/// Create a new configuration with an `EventHandler` and a `URL`
112122
public init(handler: EventHandler, url: URL) {
@@ -288,14 +298,15 @@ class EventSourceDelegate: NSObject, URLSessionDataDelegate {
288298

289299
// swiftlint:disable:next force_cast
290300
let httpResponse = response as! HTTPURLResponse
291-
if (200..<300).contains(httpResponse.statusCode) {
301+
let statusCode = httpResponse.statusCode
302+
if (200..<300).contains(statusCode) && statusCode != 204 {
292303
reconnectionTimer.connectedTime = Date()
293304
readyState = .open
294305
config.handler.onOpened()
295306
completionHandler(.allow)
296307
} else {
297-
logger.log(.info, "Unsuccessful response: %d", httpResponse.statusCode)
298-
if dispatchError(error: UnsuccessfulResponseError(responseCode: httpResponse.statusCode)) == .shutdown {
308+
logger.log(.info, "Unsuccessful response: %d", statusCode)
309+
if dispatchError(error: UnsuccessfulResponseError(responseCode: statusCode)) == .shutdown {
299310
logger.log(.info, "Connection has been explicitly shut down by error handler")
300311
readyState = .shutdown
301312
}

Tests/LDSwiftEventSourceTests.swift

+42-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ final class LDSwiftEventSourceTests: XCTestCase {
207207
XCTAssertEqual(handler.request.allHTTPHeaderFields?["X-LD-Header"], "def")
208208
es.stop()
209209
}
210-
210+
211211
func testStartRequestIsNotReentrant() {
212212
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
213213
config.urlSessionConfiguration = sessionWithMockProtocol()
@@ -349,6 +349,47 @@ final class LDSwiftEventSourceTests: XCTestCase {
349349
// Error should not have been given to the handler
350350
mockHandler.events.expectNoEvent()
351351
}
352+
353+
func testShutdownBy204Response() {
354+
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
355+
config.urlSessionConfiguration = sessionWithMockProtocol()
356+
config.reconnectTime = 0.1
357+
358+
let es = EventSource(config: config)
359+
es.start()
360+
361+
let handler = MockingProtocol.requested.expectEvent()
362+
handler.respond(statusCode: 204)
363+
364+
MockingProtocol.requested.expectNoEvent(within: 1.0)
365+
366+
es.stop()
367+
// Error should not have been given to the handler
368+
mockHandler.events.expectNoEvent()
369+
}
370+
371+
func testCanOverride204DefaultBehavior() {
372+
var config = EventSource.Config(handler: mockHandler, url: URL(string: "http://example.com")!)
373+
config.urlSessionConfiguration = sessionWithMockProtocol()
374+
config.reconnectTime = 0.1
375+
config.connectionErrorHandler = { err in
376+
if let responseErr = err as? UnsuccessfulResponseError {
377+
XCTAssertEqual(responseErr.responseCode, 204)
378+
} else {
379+
XCTFail("Expected UnsuccessfulResponseError to be given to handler")
380+
}
381+
return .shutdown
382+
}
383+
let es = EventSource(config: config)
384+
es.start()
385+
let handler = MockingProtocol.requested.expectEvent()
386+
handler.respond(statusCode: 204)
387+
// Expect the client not to reconnect
388+
MockingProtocol.requested.expectNoEvent(within: 1.0)
389+
es.stop()
390+
// Error should not have been given to the handler
391+
mockHandler.events.expectNoEvent()
392+
}
352393
#endif
353394
}
354395

0 commit comments

Comments
 (0)