@@ -7,14 +7,17 @@ import io.ktor.server.engine.*
7
7
import io.ktor.server.netty.*
8
8
import io.ktor.server.response.*
9
9
import io.ktor.server.routing.*
10
+ import io.ktor.util.cio.*
11
+ import io.ktor.utils.io.*
10
12
import kotlinx.coroutines.*
11
- import kotlinx.coroutines.channels.*
13
+ import kotlinx.coroutines.flow.*
14
+ import kotlin.time.Duration.Companion.seconds
12
15
13
16
/* *
14
17
* An SSE (Server-Sent Events) sample application.
15
18
* This is the main entrypoint of the application.
16
19
*/
17
- @OptIn(ExperimentalCoroutinesApi ::class , ObsoleteCoroutinesApi :: class )
20
+ @OptIn(ExperimentalCoroutinesApi ::class )
18
21
fun main () {
19
22
/* *
20
23
* Here we create and start a Netty embedded server listening to the port 8080
@@ -29,43 +32,43 @@ fun main() {
29
32
data class SseEvent (val data : String , val event : String? = null , val id : String? = null )
30
33
31
34
/* *
32
- * Method that responds an [ApplicationCall] by reading all the [SseEvent]s from the specified [events ] [ReceiveChannel ]
35
+ * Method that responds an [ApplicationCall] by reading all the [SseEvent]s from the specified [eventFlow ] [Flow ]
33
36
* and serializing them in a way that is compatible with the Server-Sent Events specification.
34
37
*
35
38
* You can read more about it here: https://www.html5rocks.com/en/tutorials/eventsource/basics/
36
39
*/
37
- suspend fun ApplicationCall.respondSse (events : ReceiveChannel <SseEvent >) {
40
+ suspend fun ApplicationCall.respondSse (eventFlow : Flow <SseEvent >) {
38
41
response.cacheControl(CacheControl .NoCache (null ))
39
- respondTextWriter (contentType = ContentType .Text .EventStream ) {
40
- for ( event in events) {
42
+ respondBytesWriter (contentType = ContentType .Text .EventStream ) {
43
+ eventFlow.collect { event ->
41
44
if (event.id != null ) {
42
- write (" id: ${event.id} \n " )
45
+ writeStringUtf8 (" id: ${event.id} \n " )
43
46
}
44
47
if (event.event != null ) {
45
- write (" event: ${event.event} \n " )
48
+ writeStringUtf8 (" event: ${event.event} \n " )
46
49
}
47
50
for (dataLine in event.data.lines()) {
48
- write (" data: $dataLine \n " )
51
+ writeStringUtf8 (" data: $dataLine \n " )
49
52
}
50
- write (" \n " )
53
+ writeStringUtf8 (" \n " )
51
54
flush()
52
55
}
53
56
}
54
57
}
55
58
56
59
fun Application.module () {
57
60
/* *
58
- * We produce a [BroadcastChannel ] from a suspending function
59
- * that send a [SseEvent] instance each second.
61
+ * We produce a [SharedFlow ] from a function
62
+ * that sends an [SseEvent] instance each second.
60
63
*/
61
- val channel = produce { // this: ProducerScope<SseEvent> ->
64
+ val sseFlow = flow {
62
65
var n = 0
63
66
while (true ) {
64
- send (SseEvent (" demo$n " ))
65
- delay(1000 )
67
+ emit (SseEvent (" demo$n " ))
68
+ delay(1 .seconds )
66
69
n++
67
70
}
68
- }.broadcast( )
71
+ }.shareIn( GlobalScope , SharingStarted . Eagerly )
69
72
70
73
/* *
71
74
* We use the [Routing] plugin to declare [Route] that will be
@@ -75,15 +78,10 @@ fun Application.module() {
75
78
/* *
76
79
* Route to be executed when the client perform a GET `/sse` request.
77
80
* It will respond using the [respondSse] extension method defined in this same file
78
- * that uses the [BroadcastChannel] channel we created earlier to emit those events.
81
+ * that uses the [SharedFlow] to collect sse events.
79
82
*/
80
83
get(" /sse" ) {
81
- val events = channel.openSubscription()
82
- try {
83
- call.respondSse(events)
84
- } finally {
85
- events.cancel()
86
- }
84
+ call.respondSse(sseFlow)
87
85
}
88
86
/* *
89
87
* Route to be executed when the client perform a GET `/` request.
0 commit comments