Skip to content

Commit ddfee65

Browse files
sbearcsirograemerocher
authored andcommitted
Fix #4 Support more Server Sent Events
- Provide a new type that RxResultSubscriber knows about: SseResult - SseResult and helper methods provide ways of constructing Server Sent Events with comments, ids, retry and multiline data - Update example with SseResult usage and make example use local copy of plugin.
1 parent c9cd3ab commit ddfee65

File tree

14 files changed

+701
-13
lines changed

14 files changed

+701
-13
lines changed

examples/server-sent-event-example/build.gradle

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ buildscript {
55
}
66
dependencies {
77
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
8-
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.8.2"
9-
classpath "org.grails.plugins:hibernate5:6.0.0.M2"
8+
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.11.6"
9+
classpath "org.grails.plugins:hibernate5:6.0.6"
1010
}
1111
}
1212

@@ -44,11 +44,12 @@ dependencies {
4444
compile "org.grails.plugins:scaffolding"
4545
compile "org.grails.plugins:rx-mongodb"
4646
compile "org.grails.plugins:rx-gorm-rest-client:1.0.0.M1"
47-
compile "org.grails.plugins:rxjava:1.0.0-SNAPSHOT"
47+
compile project(":rxjava")
48+
compile 'org.grails.plugins:quartz:2.0.9'
4849

4950
console "org.grails:grails-console"
5051
profile "org.grails.profiles:web"
51-
runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.8.2"
52+
runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.11.6"
5253
runtime "com.h2database:h2"
5354
testCompile "org.grails:grails-plugin-testing"
5455
testCompile "org.grails.plugins:geb"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
grailsVersion=3.2.0.M2
1+
grailsVersion=3.2.5
22
gradleWrapperVersion=2.13
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#Fri Nov 27 23:09:32 CET 2015
1+
#Thu Feb 09 15:18:01 AEDT 2017
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip
Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,101 @@
11
package rxjava.demo
22

3-
import rx.Subscriber
3+
import grails.converters.JSON
44
import grails.rx.web.*
5+
import io.reactivex.Emitter
6+
import io.reactivex.Observable
7+
import io.reactivex.schedulers.Schedulers
8+
import io.reactivex.subjects.PublishSubject
9+
import io.reactivex.subjects.Subject
10+
import reactor.spring.context.annotation.Consumer
11+
import reactor.spring.context.annotation.Selector
12+
13+
import java.util.concurrent.TimeUnit
14+
515
/**
616
* Created by graemerocher on 28/07/2016.
717
*/
18+
@Consumer
819
class TickTockController implements RxController {
920

1021
def index() {
11-
rx.stream { Subscriber subscriber ->
22+
rx.stream { Emitter emitter ->
1223
for(i in (0..5)) {
1324
if(i % 2 == 0) {
14-
subscriber.onNext(
25+
emitter.onNext(
1526
rx.render("Tick")
1627
)
1728
}
1829
else {
19-
subscriber.onNext(
30+
emitter.onNext(
2031
rx.render("Tock")
2132
)
2233

2334
}
2435
sleep 1000
2536
}
26-
subscriber.onCompleted()
37+
emitter.onComplete()
2738
}
2839
}
40+
41+
def sse() {
42+
def lastId = request.getHeader('Last-Event-ID') as Integer
43+
def startId = lastId ? lastId + 1 : 0
44+
log.info("Last Event ID: $lastId")
45+
rx.stream { Emitter emitter ->
46+
log.info("SSE Thread ${Thread.currentThread().name}")
47+
for(i in (startId..(startId+9))) {
48+
if(i % 2 == 0) {
49+
emitter.onNext(
50+
rx.event("Tick\n$i", id: i, event: 'tick', comment: 'tick')
51+
)
52+
}
53+
else {
54+
emitter.onNext(
55+
rx.event("Tock\n$i", id: i, event: 'tock', comment: 'tock')
56+
)
57+
58+
}
59+
sleep 1000
60+
}
61+
emitter.onComplete()
62+
}
63+
}
64+
65+
def observable() {
66+
def lastId = request.getHeader('Last-Event-ID') as Integer
67+
def startId = lastId ? lastId + 1 : 0
68+
rx.stream(
69+
Observable
70+
.interval(1, TimeUnit.SECONDS)
71+
.doOnSubscribe { log.info("Observable Subscribe Thread ${Thread.currentThread().name}") }
72+
.doOnNext { log.info("Observable Thread ${Thread.currentThread().name}") }
73+
.map {
74+
def id = it + startId
75+
rx.event([type: 'observable', num: id] as JSON, id: id, comment: 'hello')
76+
}
77+
.take(10),
78+
)
79+
}
80+
81+
Subject subject = PublishSubject.create()
82+
Observable publishedObservable = subject.publish().autoConnect().observeOn(Schedulers.io())
83+
84+
@Selector('MyJob.event')
85+
void myEventListener(Object data) {
86+
log.info("myEvent listener Thread ${Thread.currentThread().name}")
87+
subject.onNext(data)
88+
}
89+
90+
def quartz() {
91+
rx.stream(
92+
publishedObservable
93+
.doOnSubscribe { log.info("Quartz Subscribe Thread ${Thread.currentThread().name}") }
94+
.doOnError { log.info("Quartz thread error") }
95+
.map {
96+
log.info("Quartz Thread ${Thread.currentThread().name}")
97+
rx.event([type: 'quartz', num: it as int] as JSON, comment: 'hello')
98+
}
99+
)
100+
}
29101
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package rxjava.demo
2+
3+
import grails.events.Events
4+
5+
class MyJob implements Events {
6+
static triggers = {
7+
simple name: 'mySimpleTrigger', startDelay: 1000, repeatInterval: 1000
8+
}
9+
def group = "MyGroup"
10+
def description = "Example job with Simple Trigger"
11+
12+
static int i = 0
13+
14+
static final String EVENT_NAME = 'MyJob.event'
15+
16+
def execute(){
17+
log.trace('MyJob.execute()')
18+
19+
notify EVENT_NAME, "${i++}"
20+
}
21+
}

examples/server-sent-event-example/grails-app/views/index.gsp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,24 @@ function tickTock() {
1414
console.log("data: "+event.data)
1515
document.getElementById('message').innerHTML = event.data;
1616
};
17-
17+
// handling different event types
18+
var handler = function(event) {
19+
console.log("data: "+event.data);
20+
document.getElementById('events').innerHTML = event.data;
21+
};
22+
var events = new EventSource("tickTock/sse");
23+
events.addEventListener('tick', handler);
24+
events.addEventListener('tock', handler);
25+
var observable = new EventSource("tickTock/observable");
26+
observable.onmessage = function (event) {
27+
console.log("observable data: " + event.data);
28+
document.getElementById('observable').innerHTML = event.data;
29+
};
30+
var quartz = new EventSource("tickTock/quartz");
31+
quartz.onmessage = function (event) {
32+
console.log("quartz data: " + event.data);
33+
document.getElementById('quartz').innerHTML = event.data;
34+
};
1835
}
1936
tickTock()
2037
</script>
@@ -68,6 +85,9 @@ tickTock()
6885
<section class="row colset-2-its">
6986
<h1>Welcome to Grails</h1>
7087
<h2 style="text-align:center;font-size:50px" id="message"></h2>
88+
<pre style="text-align:center;font-size:50px" id="events"></pre>
89+
<h2 style="text-align:center;font-size:50px" id="observable"></h2>
90+
<h2 style="text-align:center;font-size:50px" id="quartz"></h2>
7191
<p>
7292
Congratulations, you have successfully started your first Grails application! At the moment
7393
this is the default page, feel free to modify it to either redirect to a controller or display
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include ":rxjava"
2+
project(":rxjava").projectDir = file("../../")

src/main/groovy/grails/rx/web/Rx.groovy

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import grails.web.databinding.DataBindingUtils
55
import grails.web.mapping.mvc.exceptions.CannotRedirectException
66
import groovy.transform.CompileDynamic
77
import groovy.transform.CompileStatic
8+
import groovy.transform.stc.ClosureParams
9+
import groovy.transform.stc.SimpleType
810
import org.grails.plugins.rx.web.NewObservableResult
911
import org.grails.plugins.rx.web.ObservableResult
1012
import org.grails.plugins.rx.web.StreamingNewObservableResult
1113
import org.grails.plugins.rx.web.StreamingObservableResult
1214
import org.grails.plugins.rx.web.result.*
15+
import org.grails.plugins.rx.web.sse.SseResult
1316
import org.grails.web.converters.Converter
1417
import org.grails.web.servlet.mvc.GrailsWebRequest
1518
import org.springframework.web.context.request.RequestContextHolder
@@ -127,6 +130,73 @@ class Rx {
127130
return new RenderConverterResult(converter)
128131
}
129132

133+
/**
134+
* Creates a Server Sent Events event for the given {@link Writable}. Optional named
135+
* params can be used to set a `comment`, `id` and `event` for the SSE event.
136+
*
137+
* @param sseOptions Optional named parameters
138+
* @param writable The writable to write as the event data
139+
* @return The SSE event
140+
*/
141+
static SseResult event(Map sseOptions, Writable writable) {
142+
def result = sseOptions as SseResult
143+
result.data = writable
144+
return result
145+
}
146+
147+
/**
148+
* Creates a Server Sent Events event for the given {@link Converter}.
149+
*
150+
* @param sseOptions Optional named parameters
151+
* @param converter The converter
152+
* @return The SSE event
153+
*/
154+
static SseResult event(Map sseOptions, Converter converter) {
155+
event(sseOptions, { Writer out ->
156+
converter.render(out)
157+
out
158+
} as Writable
159+
)
160+
}
161+
162+
/**
163+
* Creates a Server Sent Events event for the given {@link GString}.
164+
*
165+
* @param sseOptions Optional named parameters
166+
* @param gString The String
167+
* @return The SSE event
168+
*/
169+
static SseResult event(Map sseOptions, GString gString) {
170+
event(sseOptions, (Writable)gString)
171+
}
172+
173+
/**
174+
* Creates a Server Sent Events event for the given {@link CharSequence}.
175+
*
176+
* @param sseOptions Optional named parameters
177+
* @param charSequence The String
178+
* @return The SSE event
179+
*/
180+
static SseResult event(Map sseOptions, CharSequence charSequence) {
181+
event(sseOptions, { Writer out ->
182+
out.write(charSequence.toString())
183+
out
184+
} as Writable
185+
)
186+
}
187+
188+
/**
189+
* Creates a Server Sent Events event using the given {@link Closure} to generate the data. The closure
190+
* will receive a writer as the only parameter.
191+
*
192+
* @param sseOptions Optional named parameters
193+
* @param closure The closure
194+
* @return The SSE event
195+
*/
196+
static SseResult event(Map sseOptions, @ClosureParams(value=SimpleType,options=['java.io.Writer']) Closure closure) {
197+
event(sseOptions, (Writable)closure.asWritable())
198+
}
199+
130200
/**
131201
*/
132202
static RxResult<Object> respond(Map args, value) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy