Skip to content

Instantly share code, notes, and snippets.

@khanlou
Created May 5, 2023 10:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save khanlou/e715d1465eafd590c3dca6ff71e7d6c1 to your computer and use it in GitHub Desktop.
Save khanlou/e715d1465eafd590c3dca6ff71e7d6c1 to your computer and use it in GitHub Desktop.
import AsyncHTTPClient
import Foundation
struct ServerSentEvent {
let name: String
let data: String
init(_ substring: Substring) {
let lines = substring.split(whereSeparator: \.isNewline)
self.name = String(lines
.filter({ $0.starts(with: "event: ") })
.map({ $0.dropFirst("event: ".count) })
.joined())
self.data = String(lines
.filter({ $0.starts(with: "data: ") })
.map({ $0.dropFirst("data: ".count) })
.joined())
}
}
private let validNewlineCharacters = ["\r\n", "\n", "\r"]
private let delimiters = validNewlineCharacters.map { "\($0)\($0)" }
func findEvents(in substring: inout Substring) -> [ServerSentEvent] {
var foundEvents: [ServerSentEvent] = []
while true {
let firstMatch = delimiters.compactMap({ substring.range(of: $0) }).first
guard let firstMatch else { break }
let eventContent = substring[..<firstMatch.lowerBound]
substring = substring[firstMatch.lowerBound...]
substring.removeFirst(2)
foundEvents.append(ServerSentEvent(eventContent))
}
return foundEvents
}
extension HTTPClientResponse {
var serverSentEvents: AsyncStream<ServerSentEvent> {
AsyncStream(ServerSentEvent.self, { continuation in
Task {
var string = Substring()
for try await buffer in self.body {
string += String(buffer: buffer)
let events = findEvents(in: &string)
for event in events {
if event.data == "[DONE]" {
continuation.finish()
} else {
continuation.yield(event)
}
}
}
// throw error if there is one -- test this
if !(200..<300).contains(status.code) {
throw StatusCodeError(code: Int(status.code), jsonData: Data(string.utf8))
}
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment