Fix event stream line parsing to handle CRLF and empty lines correctly (#89)

* Optimized code in `connectToEventStream` to use `.lines` computed value of `AsyncSequence` to produce a `AsyncLineSequence` instead of building a lines manually.

* - Reverter use of `lines` (it swallows newlines)
- Fiexed original issue I experienced with line 229 `while let newlineIndex = buffer.firstIndex(of: "\n") {` not handling "\r\n" properly
- Added unit test to test for "\r\n" scenario
- Simplified `end of event` test - `line` creation trims newline, added code trims carriage return. Only thing that should be left is an empty line
This commit is contained in:
Ayal Spitz 2025-04-27 09:50:08 -04:00 • committed by GitHub
commit f6e401aa97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -411,6 +411,40 @@ import Testing
let expectedData = #"{"key":"value"}"#.data(using: .utf8)!
let receivedData = try await iterator.next()
#expect(receivedData == expectedData)
}
@Test("Receive Server-Sent Event (SSE) (CR-NL)", .httpClientTransportSetup)
func testReceiveSSE_CRNL() async throws {
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [MockURLProtocol.self]
let transport = HTTPClientTransport(
endpoint: testEndpoint, configuration: configuration, streaming: true,
logger: nil)
let eventString = "id: event1\r\ndata: {\"key\":\"value\"}\r\n\n"
let sseEventData = eventString.data(using: .utf8)!
await MockURLProtocol.requestHandlerStorage.setHandler {
[testEndpoint] (request: URLRequest) in
#expect(request.url == testEndpoint)
#expect(request.httpMethod == "GET")
#expect(request.value(forHTTPHeaderField: "Accept") == "text/event-stream")
let response = HTTPURLResponse(
url: testEndpoint, statusCode: 200, httpVersion: "HTTP/1.1",
headerFields: ["Content-Type": "text/event-stream"])!
return (response, sseEventData)
}
try await transport.connect()
try await Task.sleep(for: .milliseconds(100))
let stream = await transport.receive()
var iterator = stream.makeAsyncIterator()
let expectedData = #"{"key":"value"}"#.data(using: .utf8)!
let receivedData = try await iterator.next()
#expect(receivedData == expectedData)
}
#endif // !canImport(FoundationNetworking)