launchdarkly / swift-eventsource

Server-sent events (SSE) client implementation in Swift for iOS, macOS, tvOS, and watchOS
https://launchdarkly.github.io/swift-eventsource/
Other
187 stars 34 forks source link

Please add a small example how to use this. #75

Open houmie opened 5 months ago

houmie commented 5 months ago

Hello,

Thank you first for this amazing project. I'm desperately looking for a SSE library in Swift. I have difficulties understanding how to utilise this library in Swift. There is an API doc, but still very difficult to understand where to get started. A simple example would be amazing.

     guard let urlFull = URL(string: Constants.apiServiceUrlFull) else {
         print("Invalid Full URL")
         return
    }
        var request = URLRequest(url: urlFull)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        let requestBody: [String: Any] = [
            "max_new_tokens": 256,
            "max_tokens": 256,
            "stream": true,
        ]

        do {
            let jsonData = try JSONSerialization.data(withJSONObject: requestBody, options: [])
            request.httpBody = jsonData
        } catch {
            print("Error serializing JSON: \(error)")
            return
        }

For example if I had this POST that requires Streaming as response, how would I use your library please?

Thanks

keelerm84 commented 5 months ago

šŸ‘‹šŸ¼ Hi @houmie

I'm sorry you're having trouble getting started. I think the below sample should be close to what you need based on your code sample. I hope this helps!

import Foundation
import LDSwiftEventSource

class MyEventHandler: EventHandler {
    func onOpened() {
        print("** onOpened")
    }

    func onClosed() {
        print("** onClosed")
    }

    func onMessage(eventType: String, messageEvent: LDSwiftEventSource.MessageEvent) {
        print("** onMessage received event type \(eventType) and event \(String(describing: messageEvent))")
    }

    func onComment(comment: String) {
        print("** onComment with comment \(comment)")
    }

    func onError(error: any Error) {
        print("** onError with error \(String(describing: error))")
    }
}

let body: [String: Any] = [
    "max_new_tokens": 256,
    "max_tokens": 256,
    "stream": true,
]

let json = try? JSONSerialization.data(withJSONObject: body, options: [])

guard let json = json else { exit(1) }
guard let url = URL(string: "your-url") else { exit(1) }

var eventHandler = MyEventHandler()
var config = EventSource.Config(handler: eventHandler, url: url)
config.method = "POST"
config.headers = ["Content-Type": "application/json"]
config.body = json;

var eventSource = EventSource(config: config)
eventSource.start()

var semaphore = DispatchSemaphore(value: 0)
semaphore.wait()
houmie commented 5 months ago

Hello @keelerm84

Thank you so much for this example. It was incredibly helpful. Yes it works. I have noticed without the semaphore, there is no guarantee that it would be working.

var semaphore = DispatchSemaphore(value: 0)
semaphore.wait()

I have spent hours attempting to replace semaphores with different asynchronous patterns in SwiftUI, but without success. My struggles may stem from a lack of experience with Server-Sent Events (SSE). I'm considering whether to retain the semaphores and instead invoke eventSource.stop() at a specific moment. However, without knowing when the streaming will conclude, determining the perfect timing poses a challenge.

I hope you can offer some advice on how to address this issue. Thank you.

keelerm84 commented 5 months ago

I have noticed without the semaphore, there is no guarantee that it would be working.

Yes. This is because the main thread would exit otherwise. The event process work is all done asynchronously so you have to keep the application alive some other way.

I have spent hours attempting to replace semaphores with different asynchronous patterns in SwiftUI, but without success.

I'm not sure I follow. If you have an application that is launching and you can instantiate the event source independent of its lifecycle, you shouldn't have a problem with the events coming in. As long as you retain a reference to the event source instance of course. Otherwise, it is going to shut everything down.

I'm considering whether to retain the semaphores and instead invoke eventSource.stop() at a specific moment. However, without knowing when the streaming will conclude, determining the perfect timing poses a challenge.

Under what conditions do you want to stop the event source? If the SSE server disconnects? You could just call eventSource.stop() from the onClosed handler method. Do it when there is an error? onError should work fine.

houmie commented 5 months ago

Hello Mathew,

Sorry for the late reply.

I finally managed to replace the semaphore with AsyncThrowingStream pattern in Swift. And it works very well. Thank you for this project. After a lot of analysis this is the only SSE project for iOS that works flawlessly, although a bit difficult at first to understand it. Many thanks for the hard work.

I only noticed one thing in the console when running it.

State: raw -> connecting
Starting EventSource client
Initial reply received
State: connecting -> open
** onOpened
Connection unexpectedly closed.
** onClosed
State: open -> closed
Waiting 1.570 seconds before reconnecting...
State: closed -> shutdown

1) When the stream finishes by itself (and FYI I'm not even calling eventSource.stop() from onClosed()) it seems that it throws a Connection unexpectedly closed. warning in the console. It is unusual since the connection is supposed to be on its way to be closed, so why is the library throwing this warning?

2) And the second message is also a bit confusing. Waiting 1.570 seconds before reconnecting.... Not quite sure what that means. So when the SSE connection finishes, there is a small cooldown period before it can be used again?

keelerm84 commented 1 month ago

@houmie I'm not sure how I missed this original response. I realize it is very late, but just for completeness and for anyone else's future reference.

When the stream finishes by itself (and FYI I'm not even calling eventSource.stop() from onClosed()) it seems that it throws a Connection unexpectedly closed. warning in the console. It is unusual since the connection is supposed to be on its way to be closed, so why is the library throwing this warning?

This library is built specifically to serve the needs of the LD SDKs. We expect the SSE connection to always be active. If it needs to be shutdown, that is for the consumer to decide, not this library.

And the second message is also a bit confusing. Waiting 1.570 seconds before reconnecting.... Not quite sure what that means. So when the SSE connection finishes, there is a small cooldown period before it can be used again?

Yes, this library implements some basic backoff and jitter retry logic. If a re-connect fails, the delay will increase up to some defined maximum.