123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- ---
- title: Server Push
- order: 16
- layout: page
- ---
-
- [[advanced.push]]
- = Server Push
-
- When you need to update a UI from another UI, possibly of another user, or from
- a background thread running in the server, you usually want to have the update
- show immediately, not when the browser happens to make the next server request.
- For this purpose, you can use __server push__ that sends the data to the browser
- immediately. Push is based on a client-server connection, usually a WebSocket
- connection, that the client establishes and the server can then use to send
- updates to the client.
-
- The server-client communication is done by default with a WebSocket connection
- if the browser and the server support it. If not, Vaadin will fall back to a
- method supported by the browser. Vaadin Push uses a custom build of the
- link:https://github.com/Atmosphere/atmosphere[Atmosphere framework] for
- client-server communication.
-
- [[advanced.push.installation]]
- == Installing the Push Support
-
- The server push support in Vaadin requires the separate Vaadin Push library. It
- is included in the installation package as [filename]#vaadin-push.jar#.
-
- [[advanced.push.installation.ivy]]
- === Retrieving with Ivy
-
- With Ivy, you can get it with the following declaration in the
- [filename]#ivy.xml#:
-
-
- [source, xml]
- ----
- <dependency org="com.vaadin" name="vaadin-push"
- rev="&vaadin.version;" conf="default->default"/>
- ----
-
- In some servers, you may need to exlude a [literal]#++sl4j++# dependency as
- follows:
-
-
- [source, xml]
- ----
- <dependency org="com.vaadin" name="vaadin-push"
- rev="&vaadin.version;" conf="default->default">
- <exclude org="org.slf4j" name="slf4j-api"/>
- </dependency>
- ----
-
- Pay note that the Atmosphere library is a bundle, so if you retrieve the
- libraries with Ant, for example, you need to retrieve
- [literal]#++type="jar,bundle"++#.
-
-
- [[advanced.push.installation.maven]]
- === Retrieving with Maven
-
- In Maven, you can get the push library with the following dependency in the POM:
-
-
- [source, xml]
- ----
- <dependency>
- <groupId>com.vaadin</groupId>
- <artifactId>vaadin-push</artifactId>
- <version>${vaadin.version}</version>
- </dependency>
- ----
-
-
-
- [[advanced.push.enabling]]
- == Enabling Push for a UI
-
- To enable server push, you need to define the push mode either in the deployment
- descriptor or with the [classname]#@Push# annotation for the UI.
-
- [[advanced.push.enabling.pushmode]]
- === Push Modes and Transports
-
- You can use server push in two modes: [literal]#++automatic++# and
- [literal]#++manual++#. The automatic mode pushes changes to the browser
- automatically after access() finishes. With the manual mode, you can do the push
- explicitly with [methodname]#push()#, which allows more flexibility.
-
- Sever push can use several transports: WebSockets, long polling, or combined WebSockets+XHR.
- [literal]#++WebSockets++# is the default transport.
-
-
- [[advanced.push.enabling.pushmode]]
- === The [classname]#@Push# annotation
-
- You can enable server push for a UI with the [classname]#@Push# annotation as
- follows. It defaults to automatic mode ( [parameter]#PushMode.AUTOMATIC#).
-
-
- [source, java]
- ----
- @Push
- public class PushyUI extends UI {
- ----
-
- To enable manual mode, you need to give the [parameter]#PushMode.MANUAL#
- parameter as follows:
-
-
- [source, java]
- ----
- @Push(PushMode.MANUAL)
- public class PushyUI extends UI {
- ----
-
- To use the long polling transport, you need to set the transport parameter as [parameter]#Transport.LONG_POLLING# as follows:
-
- [source, java]
- ----
- @Push(transport=Transport.LONG_POLLING)
- public class PushyUI extends UI {
- ----
-
- [[advanced.push.enabling.servlet]]
- === Servlet Configuration
-
- You can enable the server push and define the push mode also in the servlet
- configuration with the [parameter]#pushmode# parameter for the servlet in the
- [filename]#web.xml# deployment descriptor. If you use a Servlet 3.0 compatible
- server, you also want to enable asynchronous processing with the
- [literal]#++async-supported++# parameter. Note the use of Servlet 3.0 schema in
- the deployment descriptor.
-
-
- [subs="normal"]
- ----
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app
- id="WebApp_ID" version="**3.0**"
- xmlns="**http://java.sun.com/xml/ns/javaee**"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="**http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd**">
- <servlet>
- <servlet-name>Pushy UI</servlet-name>
- <servlet-class>
- com.vaadin.server.VaadinServlet</servlet-class>
-
- <init-param>
- <param-name>UI</param-name>
- <param-value>**com.example.my.PushyUI**</param-value>
- </init-param>
-
- <!-- Enable server push -->
- <init-param>
- <param-name>pushmode</param-name>
- <param-value>**automatic**</param-value>
- </init-param>
- <async-supported>**true**</async-supported>
- </servlet>
- </web-app>
- ----
-
- [[advanced.push.running]]
- == Accessing UI from Another Thread
-
- Making changes to a [classname]#UI# object from another thread and pushing them
- to the browser requires locking the user session when accessing the UI.
- Otherwise, the UI update done from another thread could conflict with a regular
- event-driven update and cause either data corruption or deadlocks. Because of
- this, you may only access an UI using the [methodname]#access()# method, which
- locks the session to prevent conflicts. It takes a [interfacename]#Runnable#
- which it executes as its parameter.
-
- For example:
-
-
- [source, java]
- ----
- ui.access(new Runnable() {
- @Override
- public void run() {
- series.add(new DataSeriesItem(x, y));
- }
- });
- ----
-
- In Java 8, where a parameterless lambda expression creates a runnable, you could
- simply write:
-
-
- [source, java]
- ----
- ui.access(() ->
- series.add(new DataSeriesItem(x, y)));
- ----
-
- If the push mode is [literal]#++manual++#, you need to push the pending UI
- changes to the browser explicitly with the [methodname]#push()# method.
-
-
- [source, java]
- ----
- ui.access(new Runnable() {
- @Override
- public void run() {
- series.add(new DataSeriesItem(x, y));
- ui.push();
- }
- });
- ----
-
- Below is a complete example of a case where we make UI changes from another
- thread.
-
-
- [source, java]
- ----
- public class PushyUI extends UI {
- Chart chart = new Chart(ChartType.AREASPLINE);
- DataSeries series = new DataSeries();
-
- @Override
- protected void init(VaadinRequest request) {
- chart.setSizeFull();
- setContent(chart);
-
- // Prepare the data display
- Configuration conf = chart.getConfiguration();
- conf.setTitle("Hot New Data");
- conf.setSeries(series);
-
- // Start the data feed thread
- new FeederThread().start();
- }
-
- class FeederThread extends Thread {
- int count = 0;
-
- @Override
- public void run() {
- try {
- // Update the data for a while
- while (count < 100) {
- Thread.sleep(1000);
-
- access(new Runnable() {
- @Override
- public void run() {
- double y = Math.random();
- series.add(
- new DataSeriesItem(count++, y),
- true, count > 10);
- }
- });
- }
-
- // Inform that we have stopped running
- access(new Runnable() {
- @Override
- public void run() {
- setContent(new Label("Done!"));
- }
- });
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- ----
-
- When sharing data between UIs or user sessions, you need to consider the
- message-passing mechanism more carefully, as explained next.
-
-
- [[advanced.push.pusharound]]
- == Broadcasting to Other Users
-
- Broadcasting messages to be pushed to UIs in other user sessions requires having
- some sort of message-passing mechanism that sends the messages to all UIs that
- register as recipients. As processing server requests for different UIs is done
- concurrently in different threads of the application server, locking the threads
- properly is very important to avoid deadlock situations.
-
- [[advanced.push.pusharound.broadcaster]]
- === The Broadcaster
-
- The standard pattern for sending messages to other users is to use a
- __broadcaster__ singleton that registers the UIs and broadcasts messages to them
- safely. To avoid deadlocks, it is recommended that the messages should be sent
- through a message queue in a separate thread. Using a Java
- [classname]#ExecutorService# running in a single thread is usually the easiest
- and safest way.
-
-
- [source, java]
- ----
- public class Broadcaster implements Serializable {
- static ExecutorService executorService =
- Executors.newSingleThreadExecutor();
-
- public interface BroadcastListener {
- void receiveBroadcast(String message);
- }
-
- private static LinkedList<BroadcastListener> listeners =
- new LinkedList<BroadcastListener>();
-
- public static synchronized void register(
- BroadcastListener listener) {
- listeners.add(listener);
- }
-
- public static synchronized void unregister(
- BroadcastListener listener) {
- listeners.remove(listener);
- }
-
- public static synchronized void broadcast(
- final String message) {
- for (final BroadcastListener listener: listeners)
- executorService.execute(new Runnable() {
- @Override
- public void run() {
- listener.receiveBroadcast(message);
- }
- });
- }
- }
- ----
-
- In Java 8, you could use lambda expressions for the listeners instead of the
- interface, and a parameterless expression to create the runnable:
-
-
- [source, java]
- ----
- for (final Consumer<String> listener: listeners)
- executorService.execute(() ->
- listener.accept(message));
- ----
-
-
- [[advanced.push.pusharound.receiving]]
- === Receiving Broadcasts
-
- The receivers need to implement the receiver interface and register to the
- broadcaster to receive the broadcasts. A listener should be unregistered when
- the UI expires. When updating the UI in a receiver, it should be done safely as
- described earlier, by executing the update through the [methodname]#access()#
- method of the UI.
-
-
- [source, java]
- ----
- @Push
- public class PushAroundUI extends UI
- implements Broadcaster.BroadcastListener {
-
- VerticalLayout messages = new VerticalLayout();
-
- @Override
- protected void init(VaadinRequest request) {
- ... build the UI ...
-
- // Register to receive broadcasts
- Broadcaster.register(this);
- }
-
- // Must also unregister when the UI expires
- @Override
- public void detach() {
- Broadcaster.unregister(this);
- super.detach();
- }
-
- @Override
- public void receiveBroadcast(final String message) {
- // Must lock the session to execute logic safely
- access(new Runnable() {
- @Override
- public void run() {
- // Show it somehow
- messages.addComponent(new Label(message));
- }
- });
- }
- }
- ----
-
-
- [[advanced.push.pusharound.sending]]
- === Sending Broadcasts
-
- To send broadcasts with a broadcaster singleton, such as the one described
- above, you would only need to call the [methodname]#broadcast()# method as
- follows.
-
-
- [source, java]
- ----
- final TextField input = new TextField();
- sendBar.addComponent(input);
-
- Button send = new Button("Send");
- send.addClickListener(new ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- // Broadcast the message
- Broadcaster.broadcast(input.getValue());
-
- input.setValue("");
- }
- });
- ----
-
-
-
-
|