How to set the event name for server sent events (SSE) in Quarkus - server-sent-events

I have following Quarkus resource:
#Path("/myResource")
class MyResource {
#GET
#Path("/eventStream")
#Produces(MediaType.SERVER_SENT_EVENTS)
#SseElementType(MediaType.APPLICATION_JSON)
fun stream(): Multi<MyDto> = deviceStatusService.getStream()
}
This will produce events without event name and only the data section.
How can I specify an event name?

My final code is now:
#GET
#Path("/eventStream")
#Produces(MediaType.SERVER_SENT_EVENTS)
#SseElementType(MediaType.APPLICATION_JSON)
fun stream(#Context sse: Sse, #Context sseEventSink: SseEventSink) {
deviceStatusService.getStream().subscribe().with { deviceStatus ->
sseEventSink.send(sse.newEventBuilder()
.name("deviceStatus")
.data(deviceStatus)
.build())
}
}

As per comments to the question, looks like it's not implemented in Resteasy library, One option is to use non-reactive approach e.g
#GET
#Path("/eventStream")
#Produces(MediaType.SERVER_SENT_EVENTS)
#SseElementType(MediaType.APPLICATION_JSON)
fun stream(#Context sse: Sse, #Context sseEventSink: SseEventSink) {
return deviceStatusService.getStream().subscribe().asIterable().forEach { it -> sseEventSink.send(sse.newEvent("myEvent", it.toString()))}
}
You can use OutboundSseEventImpl.BuilderImpl() to build event with json object.
Although, again, it's a temporary solution

Related

Quarkus - Reactive file download

Using Quarkus, can somebody give an example on how the server and client side code using a reactive API to download a file over http looks?
So far I tried to return a Flux of nio ByteBuffers but it seems not to be supported:
#RegisterRestClient(baseUri = "http://some-page.com")
interface SomeService {
// same interface for client and server
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
#Path("/somePath")
fun downloadFile(): reactor.core.publisher.Flux<java.nio.ByteBuffer>
}
Trying to return a Flux on the server-side results in the following exception:
ERROR: RESTEASY002005: Failed executing GET /somePath
org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: kotlinx.coroutines.reactor.FlowAsFlux of media type: application/octet-stream
at org.jboss.resteasy.core.ServerResponseWriter.lambda$writeNomapResponse$3(ServerResponseWriter.java:124)
at org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:403)
at org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:251)
...
Here is an example how to start reactive file download with smallrye mutiny. Main function is getFile
#GET
#Path("/f/{fileName}")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Uni<Response> getFile(#PathParam String fileName) {
File nf = new File(fileName);
log.info("file:" + nf.exists());
ResponseBuilder response = Response.ok((Object) nf);
response.header("Content-Disposition", "attachment;filename=" + nf);
Uni<Response> re = Uni.createFrom().item(response.build());
return re;
}
You can test in your local with mvn quarkus:dev and go to this url to see what files are there http://localhost:8080/hello/list/test and after that you can call this url to start download http://localhost:8080/hello/f/reactive-file-download-dev.jar
I did not check about Flux(which looks like more spring then quarkus), feel free to share your thoughts. I am just learning and answering/sharing.
As of this commit, Quarkus has out-of-the-box support for AsyncFile. So, we can stream down a file by returning an AsyncFile instance.
For example, in a JAX-RS resource controller:
// we need a Vertx instance for accessing filesystem
#Inject
Vertx vertx;
#GET
#Path("/file-data-1")
#Produces(MediaType.TEXT_PLAIN)
public Uni<Response> streamDataFromFile1()
{
final OpenOptions openOptions = (new OpenOptions()).setCreate(false).setWrite(false);
Uni<AsyncFile> uni1 = vertx.fileSystem()
.open("/srv/texts/hello.txt", openOptions);
return uni1.onItem()
.transform(asyncFile -> Response.ok(asyncFile)
.header("Content-Disposition", "attachment;filename=\"Hello.txt\"")
.build());
}

Redux Offline debounce server requests

I'm trying to work out the best way to get Redux Offline to debounce server requests.
Currently when the server is busy, it saves them up in a queue and sends all of them. I'd like it just to save the last one but use the same Save action to update the redux store.
My code in the action is:
export class SaveSession extends OfflineAction<SessionTypes> {
public readonly type = SessionTypes.SAVE_SESSION;
constructor(public session: ISession) {
super();
this.meta = {
offline: {
effect: {
url: `${process.env.REACT_APP_API}/api/session/save`,
body: JSON.stringify(session),
method: 'POST',
headers: authHeader()
},
commit: new SaveSessionSuccess(),
rollback: new SaveSessionError()
}
};
}
}
I'm looking through the documentation but I can't see anything around debouncing server requests.
Is this possible?
Have a look at this link from the documentation.
It seems like you will be interested in overriding the enqueue function to adjust the logic to suit your need: some kind of smart queue.
They said they've come up with smart-queue to serve this purpose: check this. Check it out and roll out the mechanism to match your requirement.

AEM Servlet not getting executed

I have a servlet with OSGI annotation like below
#Component( immediate = true, service = Servlet.class, property = { "sling.servlet.extensions=json",
"sling.servlet.paths=/example/search", "sling.servlet.methods=get" } )
public class SearchSevrlet
extends SlingSafeMethodsServlet {
#Override
protected void doGet( final SlingHttpServletRequest req, final SlingHttpServletResponse resp )
throws ServletException, IOException {
log.info("This is not getting called ");
}
}
But When i try to hit the servlet with JQuery
$.get( "/example/search.json", function( data ) {
$( ".result" ).html( data );
alert( "Load was performed." );
});
I am getting below information rather than servlet getting executed.
{"sling:resourceSuperType":"sling/bundle/resource","servletClass":"com.group.aem.example.servlet.SearchSevrlet","sling:resourceType":"/example/search.servlet","servletName":"com.group.aem.example.servlet.SearchSevrlet"}
Please let me know if i need to make any other configuration.
The info that you are getting is the answer of the Default JSON Servlet
Please read this: Servlets and Scripts
You are registering the "SearchServlet" with the property "sling.servlet.paths". This property is defined as:
sling.servlet.paths: A list of absolute paths under which the servlet is accessible as a Resource. The property value must either be a single String, an array of Strings...
That means that your servlet will be only triggered if you request the same exact path, in this case "/example/search", like this:
GET /example/search
I would recommend you to use the properties "resourceTypes" and "selectors" in your Servlet rather than "paths". For example, a better configuration could be:
property = {
"sling.servlet.resourceTypes=/example/search.servlet",
"sling.servlet.selectors=searchselector",
"sling.servlet.extensions=json",
"sling.servlet.methods=GET"
}
With this config, your SearchServlet should be triggered with a GET request to a resource with resourceType="/example/search.servlet", with the selector "searchselector" and the extension "json". For example:
GET /corcoran/search.searchselector.json
I had a similar problem with yours.
To find out what is wrong, I checked "Recent Requests" page.
(at http://localhost:4502/system/console/requests.)
In my case, there was a log saying, "Will not look for a servlet at (my request path) as it is not in the list of allowed paths".
So I moved to "Config Manager" page(at http://localhost:4502/system/console/configMgr), and searched for "Apache Sling Servlet/Script Resolver and Error Handler".
It has a list named "Execution Paths", and I added my request path to the list.
After adding my path to the list, the problem is solved.

Testing Post requests in Ktor

Ktor (kotlin web framework) has an awesome testable mode where http requests can be wrapped in unit tests. They give a nice example of how to test a GET endpoint here,
however I'm having trouble with an http POST.
I tried this but the post params don't seem to be added to the request:
#Test
fun testSomePostThing() = withTestApplication(Application::myModule) {
with(handleRequest(HttpMethod.Post, "/api/v2/processing") {
addHeader("content-type", "application/x-www-form-urlencoded")
addHeader("Accept", "application/json")
body = "param1=cool7&param2=awesome4"
}) {
assertEquals(HttpStatusCode.OK, response.status())
val resp = mapper.readValue<TriggerResponse>(response.content ?: "")
assertEquals(TriggerResponse("cool7", "awesome4", true), resp)
}
}
Anyone have any ideas?
For those using the alternate .apply to verify results, you can prepend the body before the test call
withTestApplication({ module(testing = true) }) {
handleRequest(HttpMethod.Post, "/"){
setBody(...)
}.apply {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("HELLO WORLD!", response.content)
}
}
Ok dumb mistake, I'll post it here in case this saves somebody else from wasting time ;)
The unit test was actually catching a real problem (thats what they're for I guess)
In my routing I was using:
install(Routing) {
post("/api/v2/processing") {
val params = call.parameters
...
}
}
However that only works for 'get' params. Post params need:
install(Routing) {
post("/api/v2/processing") {
val params = call.receive<ValuesMap>()
...
}
}
For those reading it nowadays, back in 2018 receiveParameters() method was added for such cases. You can use it as:
install(Routing) {
post("/api/v2/processing") {
val params = call.receiveParameters()
println(params["param1"]) // Prints cool7
...
}
}
Also it's worth noting that request construction in the example could be further improved nowadays:
// Use provided consts, not strings
addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
// Convenient method instead of constructing string requests
setBody(listOf("param1" to "cool7", "param2" to "awesome4").formUrlEncode())
call.parameters also works for post routes.
get("api/{country}") {
val country = call.parameters["country"]!!
...
}
This will give you whatever is passed in the uri after api.
call.receive is for the body of a request.

Signalr (1.0.0-alpha2) Hubs - Can you add client functions after connection has been started?

Using Signalr (1.0.0-alpha2), I want to know if it is possible to add client functions after a connection has been started.
Say I create my connection and grab the proxy. Then I add some Server Fired client functions to the hub to do a few things. Then I start my connection. I then want to add some more Server Fired functions to my hub object. Is this possible?
var myHub= $.connection.myHub;
myHub.SomeClientFunction = function() {
alert("serverside called 'Clients.SomeClientFunction()'");
};
$.connection.hub.start()
.done(function() {
myHub.SomeNewClientFunction = function() {
alert("serverside called 'Clients.SomeNewClientFunction()'");
}
})
This example is not realistic, but I basically want to send my 'myHub' variable to a different object after the hub is started to subscribe to new events that the original code did not care for.
Real Life Example: A dashboard with a number of different hub events (new site visits, chat message, site error). I 'subscribe' after the connection has started and then pass my hub proxy to all of my different UI components to handle their specific 'message types'. Should I create separate Hubs for these or should I be able to add more Server Fired client functions on the fly?
Yes you can. Use the .on method.
Example:
myHub.on('somethingNew', function() {
alert("This was called after the connection started!");
});
If you want to remove it later on use the .off method.
I have the exact same situation. You might want to consider adding another layout of abstraction if you're trying to call it from multiple places.
Here's a preliminary version of what I've come up with (typescript).
I'll start with the usage. SignalRManager is my 'manager' class that abstracts my debuggingHub hub. I have a client method fooChanged that is triggered on the server.
Somewhere in the module that is using SignalR I just call the start method, which is not re-started if already started.
// ensure signalR is started
SignalRManager.start().done(() =>
{
$.connection.debuggingHub.server.init();
});
Your 'module' simply registers its callback through the manager class and whenever the SignalR client method is triggered your handler is called.
// handler for foo changed
SignalRManager.onFooChanged((guid: string) =>
{
if (this.currentSession().guid == guid)
{
alert('changed');
}
});
This is a simple version of SignalRManager that uses jQuery $.Callbacks to pass on the request to as many modules as you have. Of course you could use any mechanism you wanted, but this seems to be the simplest.
module RR
{
export class SignalRManager
{
// the original promise returned when calling hub.Start
static _start: JQueryPromise<any>;
private static _fooChangedCallback = $.Callbacks();
// add callback for 'fooChanged' callback
static onfooChanged(callback: (guid: string) => any)
{
SignalRManager._fooChangedCallback.add(callback);
}
static start(): JQueryPromise<any>
{
if (!SignalRManager._start)
{
// callback for fooChanged
$.connection.debuggingHub.client.fooChanged = (guid: string) =>
{
console.log('foo Changed ' + guid);
SignalRManager._fooChangedCallback.fire.apply(arguments);
};
// start hub and save the promise returned
SignalRManager._start = $.connection.hub.start().done(() =>
{
console.log('Signal R initialized');
});
}
return SignalRManager._start;
}
}
}
Note: there may be extra work involved to handle disconnections or connections lost.

Resources