You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

advanced-push.asciidoc 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. ---
  2. title: Server Push
  3. order: 16
  4. layout: page
  5. ---
  6. [[advanced.push]]
  7. = Server Push
  8. When you need to update a UI from another UI, possibly of another user, or from
  9. a background thread running in the server, you usually want to have the update
  10. show immediately, not when the browser happens to make the next server request.
  11. For this purpose, you can use __server push__ that sends the data to the browser
  12. immediately. Push is based on a client-server connection, usually a WebSocket
  13. connection, that the client establishes and the server can then use to send
  14. updates to the client.
  15. The server-client communication is done by default with a WebSocket connection
  16. if the browser and the server support it. If not, Vaadin will fall back to a
  17. method supported by the browser. Vaadin Push uses a custom build of the
  18. link:https://github.com/Atmosphere/atmosphere[Atmosphere framework] for
  19. client-server communication.
  20. [[advanced.push.installation]]
  21. == Installing the Push Support
  22. The server push support in Vaadin requires the separate Vaadin Push library. It
  23. is included in the installation package as [filename]#vaadin-push.jar#.
  24. [[advanced.push.installation.ivy]]
  25. === Retrieving with Ivy
  26. With Ivy, you can get it with the following declaration in the
  27. [filename]#ivy.xml#:
  28. [source, xml]
  29. ----
  30. <dependency org="com.vaadin" name="vaadin-push"
  31. rev="&vaadin.version;" conf="default->default"/>
  32. ----
  33. In some servers, you may need to exlude a [literal]#++sl4j++# dependency as
  34. follows:
  35. [source, xml]
  36. ----
  37. <dependency org="com.vaadin" name="vaadin-push"
  38. rev="&vaadin.version;" conf="default->default">
  39. <exclude org="org.slf4j" name="slf4j-api"/>
  40. </dependency>
  41. ----
  42. Pay note that the Atmosphere library is a bundle, so if you retrieve the
  43. libraries with Ant, for example, you need to retrieve
  44. [literal]#++type="jar,bundle"++#.
  45. [[advanced.push.installation.maven]]
  46. === Retrieving with Maven
  47. In Maven, you can get the push library with the following dependency in the POM:
  48. [source, xml]
  49. ----
  50. <dependency>
  51. <groupId>com.vaadin</groupId>
  52. <artifactId>vaadin-push</artifactId>
  53. <version>${vaadin.version}</version>
  54. </dependency>
  55. ----
  56. [[advanced.push.enabling]]
  57. == Enabling Push for a UI
  58. To enable server push, you need to define the push mode either in the deployment
  59. descriptor or with the [classname]#@Push# annotation for the UI.
  60. [[advanced.push.enabling.pushmode]]
  61. === Push Modes and Transports
  62. You can use server push in two modes: [literal]#++automatic++# and
  63. [literal]#++manual++#. The automatic mode pushes changes to the browser
  64. automatically after access() finishes. With the manual mode, you can do the push
  65. explicitly with [methodname]#push()#, which allows more flexibility.
  66. Sever push can use several transports: WebSockets, long polling, or combined WebSockets+XHR.
  67. [literal]#++WebSockets++# is the default transport.
  68. [[advanced.push.enabling.pushmode]]
  69. === The [classname]#@Push# annotation
  70. You can enable server push for a UI with the [classname]#@Push# annotation as
  71. follows. It defaults to automatic mode ( [parameter]#PushMode.AUTOMATIC#).
  72. [source, java]
  73. ----
  74. @Push
  75. public class PushyUI extends UI {
  76. ----
  77. To enable manual mode, you need to give the [parameter]#PushMode.MANUAL#
  78. parameter as follows:
  79. [source, java]
  80. ----
  81. @Push(PushMode.MANUAL)
  82. public class PushyUI extends UI {
  83. ----
  84. To use the long polling transport, you need to set the transport parameter as [parameter]#Transport.LONG_POLLING# as follows:
  85. [source, java]
  86. ----
  87. @Push(transport=Transport.LONG_POLLING)
  88. public class PushyUI extends UI {
  89. ----
  90. [[advanced.push.enabling.servlet]]
  91. === Servlet Configuration
  92. You can enable the server push and define the push mode also in the servlet
  93. configuration with the [parameter]#pushmode# parameter for the servlet in the
  94. [filename]#web.xml# deployment descriptor. If you use a Servlet 3.0 compatible
  95. server, you also want to enable asynchronous processing with the
  96. [literal]#++async-supported++# parameter. Note the use of Servlet 3.0 schema in
  97. the deployment descriptor.
  98. [subs="normal"]
  99. ----
  100. &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  101. &lt;web-app
  102. id="WebApp_ID" version="**3.0**"
  103. xmlns="**http://java.sun.com/xml/ns/javaee**"
  104. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  105. xsi:schemaLocation="**http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd**"&gt;
  106. &lt;servlet&gt;
  107. &lt;servlet-name&gt;Pushy UI&lt;/servlet-name&gt;
  108. &lt;servlet-class&gt;
  109. com.vaadin.server.VaadinServlet&lt;/servlet-class&gt;
  110. &lt;init-param&gt;
  111. &lt;param-name&gt;UI&lt;/param-name&gt;
  112. &lt;param-value&gt;**com.example.my.PushyUI**&lt;/param-value&gt;
  113. &lt;/init-param&gt;
  114. &lt;!-- Enable server push --&gt;
  115. &lt;init-param&gt;
  116. &lt;param-name&gt;pushmode&lt;/param-name&gt;
  117. &lt;param-value&gt;**automatic**&lt;/param-value&gt;
  118. &lt;/init-param&gt;
  119. &lt;async-supported&gt;**true**&lt;/async-supported&gt;
  120. &lt;/servlet&gt;
  121. &lt;/web-app&gt;
  122. ----
  123. [[advanced.push.running]]
  124. == Accessing UI from Another Thread
  125. Making changes to a [classname]#UI# object from another thread and pushing them
  126. to the browser requires locking the user session when accessing the UI.
  127. Otherwise, the UI update done from another thread could conflict with a regular
  128. event-driven update and cause either data corruption or deadlocks. Because of
  129. this, you may only access an UI using the [methodname]#access()# method, which
  130. locks the session to prevent conflicts. It takes a [interfacename]#Runnable#
  131. which it executes as its parameter.
  132. For example:
  133. [source, java]
  134. ----
  135. ui.access(new Runnable() {
  136. @Override
  137. public void run() {
  138. series.add(new DataSeriesItem(x, y));
  139. }
  140. });
  141. ----
  142. In Java 8, where a parameterless lambda expression creates a runnable, you could
  143. simply write:
  144. [source, java]
  145. ----
  146. ui.access(() ->
  147. series.add(new DataSeriesItem(x, y)));
  148. ----
  149. If the push mode is [literal]#++manual++#, you need to push the pending UI
  150. changes to the browser explicitly with the [methodname]#push()# method.
  151. [source, java]
  152. ----
  153. ui.access(new Runnable() {
  154. @Override
  155. public void run() {
  156. series.add(new DataSeriesItem(x, y));
  157. ui.push();
  158. }
  159. });
  160. ----
  161. Below is a complete example of a case where we make UI changes from another
  162. thread.
  163. [source, java]
  164. ----
  165. public class PushyUI extends UI {
  166. Chart chart = new Chart(ChartType.AREASPLINE);
  167. DataSeries series = new DataSeries();
  168. @Override
  169. protected void init(VaadinRequest request) {
  170. chart.setSizeFull();
  171. setContent(chart);
  172. // Prepare the data display
  173. Configuration conf = chart.getConfiguration();
  174. conf.setTitle("Hot New Data");
  175. conf.setSeries(series);
  176. // Start the data feed thread
  177. new FeederThread().start();
  178. }
  179. class FeederThread extends Thread {
  180. int count = 0;
  181. @Override
  182. public void run() {
  183. try {
  184. // Update the data for a while
  185. while (count < 100) {
  186. Thread.sleep(1000);
  187. access(new Runnable() {
  188. @Override
  189. public void run() {
  190. double y = Math.random();
  191. series.add(
  192. new DataSeriesItem(count++, y),
  193. true, count > 10);
  194. }
  195. });
  196. }
  197. // Inform that we have stopped running
  198. access(new Runnable() {
  199. @Override
  200. public void run() {
  201. setContent(new Label("Done!"));
  202. }
  203. });
  204. } catch (InterruptedException e) {
  205. e.printStackTrace();
  206. }
  207. }
  208. }
  209. }
  210. ----
  211. When sharing data between UIs or user sessions, you need to consider the
  212. message-passing mechanism more carefully, as explained next.
  213. [[advanced.push.pusharound]]
  214. == Broadcasting to Other Users
  215. Broadcasting messages to be pushed to UIs in other user sessions requires having
  216. some sort of message-passing mechanism that sends the messages to all UIs that
  217. register as recipients. As processing server requests for different UIs is done
  218. concurrently in different threads of the application server, locking the threads
  219. properly is very important to avoid deadlock situations.
  220. [[advanced.push.pusharound.broadcaster]]
  221. === The Broadcaster
  222. The standard pattern for sending messages to other users is to use a
  223. __broadcaster__ singleton that registers the UIs and broadcasts messages to them
  224. safely. To avoid deadlocks, it is recommended that the messages should be sent
  225. through a message queue in a separate thread. Using a Java
  226. [classname]#ExecutorService# running in a single thread is usually the easiest
  227. and safest way.
  228. [source, java]
  229. ----
  230. public class Broadcaster implements Serializable {
  231. static ExecutorService executorService =
  232. Executors.newSingleThreadExecutor();
  233. public interface BroadcastListener {
  234. void receiveBroadcast(String message);
  235. }
  236. private static LinkedList<BroadcastListener> listeners =
  237. new LinkedList<BroadcastListener>();
  238. public static synchronized void register(
  239. BroadcastListener listener) {
  240. listeners.add(listener);
  241. }
  242. public static synchronized void unregister(
  243. BroadcastListener listener) {
  244. listeners.remove(listener);
  245. }
  246. public static synchronized void broadcast(
  247. final String message) {
  248. for (final BroadcastListener listener: listeners)
  249. executorService.execute(new Runnable() {
  250. @Override
  251. public void run() {
  252. listener.receiveBroadcast(message);
  253. }
  254. });
  255. }
  256. }
  257. ----
  258. In Java 8, you could use lambda expressions for the listeners instead of the
  259. interface, and a parameterless expression to create the runnable:
  260. [source, java]
  261. ----
  262. for (final Consumer<String> listener: listeners)
  263. executorService.execute(() ->
  264. listener.accept(message));
  265. ----
  266. [[advanced.push.pusharound.receiving]]
  267. === Receiving Broadcasts
  268. The receivers need to implement the receiver interface and register to the
  269. broadcaster to receive the broadcasts. A listener should be unregistered when
  270. the UI expires. When updating the UI in a receiver, it should be done safely as
  271. described earlier, by executing the update through the [methodname]#access()#
  272. method of the UI.
  273. [source, java]
  274. ----
  275. @Push
  276. public class PushAroundUI extends UI
  277. implements Broadcaster.BroadcastListener {
  278. VerticalLayout messages = new VerticalLayout();
  279. @Override
  280. protected void init(VaadinRequest request) {
  281. ... build the UI ...
  282. // Register to receive broadcasts
  283. Broadcaster.register(this);
  284. }
  285. // Must also unregister when the UI expires
  286. @Override
  287. public void detach() {
  288. Broadcaster.unregister(this);
  289. super.detach();
  290. }
  291. @Override
  292. public void receiveBroadcast(final String message) {
  293. // Must lock the session to execute logic safely
  294. access(new Runnable() {
  295. @Override
  296. public void run() {
  297. // Show it somehow
  298. messages.addComponent(new Label(message));
  299. }
  300. });
  301. }
  302. }
  303. ----
  304. [[advanced.push.pusharound.sending]]
  305. === Sending Broadcasts
  306. To send broadcasts with a broadcaster singleton, such as the one described
  307. above, you would only need to call the [methodname]#broadcast()# method as
  308. follows.
  309. [source, java]
  310. ----
  311. final TextField input = new TextField();
  312. sendBar.addComponent(input);
  313. Button send = new Button("Send");
  314. send.addClickListener(new ClickListener() {
  315. @Override
  316. public void buttonClick(ClickEvent event) {
  317. // Broadcast the message
  318. Broadcaster.broadcast(input.getValue());
  319. input.setValue("");
  320. }
  321. });
  322. ----