Reactive Endpoints
Important
| This page is being migrated to the new Building Applications section. See the Background Jobs and Server Push documentation pages. |
Although traditional server calls work fine in most cases, sometimes you need different tools. Reactor is one of these, and can help you stream data to clients — and it fits well into a non-blocking application. Whenever the simple request-response pattern doesn’t suit your needs, you might consider Reactor. Multi-user applications, infinite data streaming, and retries are all good examples of what you can do with it.
If you want to know more about Reactor, see their curated learning page.
Although getting comfortable with creating streaming, non-blocking applications requires some effort, Hilla allows you to take advantage of Reactor in minutes. To learn the basics of reactive endpoints, read the blog article on the subject.
Familiarizing
Before looking at the API details, you might familiarize yourself with the basics of reactive endpoints by writing a simple application that can stream the current time of the server to the client. The task of this example is to send the server time to all subscribed clients, with an interval of one second between each message.
Start by creating a Hilla application like so:
npx @hilla/cli init flux-clock
Server-Side Endpoints
Next you’ll need to create a server-side endpoint. To do this, open your application in your favorite IDE or editor.
Create a new endpoint in a package of your choice and name it ReactiveEndpoint
. Add these two methods to it:
@AnonymousAllowed
public Flux<@Nonnull String> getClock() {
return Flux.interval(Duration.ofSeconds(1)).onBackpressureDrop()
.map(_interval -> new Date().toString());
}
@AnonymousAllowed
public EndpointSubscription<@Nonnull String> getClockCancellable() {
return EndpointSubscription.of(getClock(), () -> {
LOGGER.info("Subscription has been cancelled");
});
}
Note
|
You can click on the expand code icon at the top-right of the code snippet to get the whole file.
|
The first method, getClock()
, creates a Flux that streams the server time each second. It uses onBackpressureDrop() to avoid saturation in case some messages can’t be sent to the client in real time. In this use case, it’s acceptable to lose some messages.
The second method, getClockCancellable()
, returns the same Flux
, but wrapped in an EndpointSubscription
. This is a Hilla-specific Flux
wrapper which allows you to execute something on the server side if the subscription is cancelled by the client.
In this example, a log message is produced.
Showing Messages
Next, you’ll need to create a view to show the messages. You’ll use these methods in your client views.
Create the frontend/views/reactive/ReactiveView.tsx
file to add a new view. This looks like the default "Hello world" example, customized with the code that follows.
import { Subscription } from '@vaadin/hilla-frontend';
import { Button } from '@vaadin/react-components/Button.js';
import { TextField } from '@vaadin/react-components/TextField.js';
import { getClockCancellable } from 'Frontend/generated/ReactiveEndpoint';
export default function ReactiveView() {
const serverTime = useSignal("");
const subscription = useSignal<Subscription<string> | undefined>(undefined);
const toggleServerClock = async () => {
if (subscription) {
subscription.cancel();
subscription.value = undefined;
} else {
subscription.value = getClockCancellable().onNext((time) => {
serverTime.value = time;
});
}
}
return (
<section className="flex p-m gap-m items-end">
<TextField label="Server time" value={serverTime.value} readonly />
<Button onClick={toggleServerClock}>Toggle server clock</Button>
</section>
);
}
Then, add the new view to the router by importing it in frontend/routes.tsx
with this:
import ReactiveView from './views/reactive/ReactiveView';
Declare it with:
{ path: '/reactive', element: <ReactiveView />, handle: { icon: 'la la-file', title: 'Reactive' } },
right after the /about
path.
API Overview
The generated TypeScript endpoint methods return a Subscription<T>
object, which offers these methods:
cancel()
-
Cancel the subscription.
onNext()
-
Get the next message in the Flux.
onError()
-
Get errors that can happen in the Flux.
onComplete()
-
Be informed when the Flux has no more messages to send.
context()
-
Bind to the context, which allows the application automatically to close it when the view is detached.
onSubscriptionLost()
-
Called when the connection is restored, but there’s no longer a valid subscription. If the callback returns
ActionOnLostSubscription.RESUBSCRIBE
, the subscription will be re-established by connecting to the same server method again. If the callback returnsActionOnLostSubscription.REMOVE
, the subscription will be forgotten. This is also the default behavior if the callback is not set or if it returnsundefined
. onConnectionStateChange()
-
Called when the subscription state changes. The callback receives an event of type
FluxSubscriptionStateChangeEvent
that contains the new state. The possible states are defined as enumerated values ofCONNECTING
,CONNECTED
, andCLOSED
.