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.

ServerCommunicationHandler.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.communication;
  17. import java.util.logging.Logger;
  18. import com.google.gwt.core.client.GWT;
  19. import com.google.gwt.core.client.Scheduler;
  20. import com.google.gwt.user.client.Command;
  21. import com.vaadin.client.ApplicationConfiguration;
  22. import com.vaadin.client.ApplicationConnection;
  23. import com.vaadin.client.ApplicationConnection.RequestStartingEvent;
  24. import com.vaadin.client.ApplicationConnection.ResponseHandlingEndedEvent;
  25. import com.vaadin.client.Util;
  26. import com.vaadin.client.VLoadingIndicator;
  27. import com.vaadin.shared.ApplicationConstants;
  28. import com.vaadin.shared.Version;
  29. import com.vaadin.shared.ui.ui.UIState.PushConfigurationState;
  30. import elemental.json.Json;
  31. import elemental.json.JsonArray;
  32. import elemental.json.JsonObject;
  33. /**
  34. * ServerCommunicationHandler is responsible for communicating (sending and
  35. * receiving messages) with the servlet.
  36. *
  37. * It will internally use either XHR or websockets for communicating, depending
  38. * on how the application is configured.
  39. *
  40. * Uses {@link ServerMessageHandler} for processing received messages
  41. *
  42. * @since
  43. * @author Vaadin Ltd
  44. */
  45. public class ServerCommunicationHandler {
  46. public static final String JSON_COMMUNICATION_PREFIX = "for(;;);[";
  47. public static final String JSON_COMMUNICATION_SUFFIX = "]";
  48. private ApplicationConnection connection;
  49. private boolean hasActiveRequest = false;
  50. /**
  51. * Counter for the messages send to the server. First sent message has id 0.
  52. */
  53. private int clientToServerMessageId = 0;
  54. private XhrConnection xhrConnection;
  55. private PushConnection push;
  56. public ServerCommunicationHandler() {
  57. xhrConnection = GWT.create(XhrConnection.class);
  58. }
  59. /**
  60. * Sets the application connection this handler is connected to
  61. *
  62. * @param connection
  63. * the application connection this handler is connected to
  64. */
  65. public void setConnection(ApplicationConnection connection) {
  66. this.connection = connection;
  67. xhrConnection.setConnection(connection);
  68. }
  69. public static Logger getLogger() {
  70. return Logger.getLogger(ServerCommunicationHandler.class.getName());
  71. }
  72. public void sendInvocationsToServer() {
  73. if (!connection.isApplicationRunning()) {
  74. getLogger()
  75. .warning(
  76. "Trying to send RPC from not yet started or stopped application");
  77. return;
  78. }
  79. if (hasActiveRequest() || (push != null && !push.isActive())) {
  80. // There is an active request or push is enabled but not active
  81. // -> send when current request completes or push becomes active
  82. } else {
  83. doSendInvocationsToServer();
  84. }
  85. }
  86. /**
  87. * Sends all pending method invocations (server RPC and legacy variable
  88. * changes) to the server.
  89. *
  90. */
  91. private void doSendInvocationsToServer() {
  92. ServerRpcQueue serverRpcQueue = getServerRpcQueue();
  93. if (serverRpcQueue.isEmpty()) {
  94. return;
  95. }
  96. if (ApplicationConfiguration.isDebugMode()) {
  97. Util.logMethodInvocations(connection, serverRpcQueue.getAll());
  98. }
  99. boolean showLoadingIndicator = serverRpcQueue.showLoadingIndicator();
  100. JsonArray reqJson = serverRpcQueue.toJson();
  101. serverRpcQueue.clear();
  102. if (reqJson.length() == 0) {
  103. // Nothing to send, all invocations were filtered out (for
  104. // non-existing connectors)
  105. getLogger()
  106. .warning(
  107. "All RPCs filtered out, not sending anything to the server");
  108. return;
  109. }
  110. JsonObject extraJson = Json.createObject();
  111. if (!connection.getConfiguration().isWidgetsetVersionSent()) {
  112. extraJson.put(ApplicationConstants.WIDGETSET_VERSION_ID,
  113. Version.getFullVersion());
  114. connection.getConfiguration().setWidgetsetVersionSent();
  115. }
  116. if (showLoadingIndicator) {
  117. connection.getLoadingIndicator().trigger();
  118. }
  119. send(reqJson, extraJson);
  120. }
  121. private ServerRpcQueue getServerRpcQueue() {
  122. return connection.getServerRpcQueue();
  123. }
  124. /**
  125. * Makes an UIDL request to the server.
  126. *
  127. * @param reqInvocations
  128. * Data containing RPC invocations and all related information.
  129. * @param extraParams
  130. * Parameters that are added to the payload
  131. */
  132. protected void send(final JsonArray reqInvocations,
  133. final JsonObject extraJson) {
  134. startRequest();
  135. JsonObject payload = Json.createObject();
  136. String csrfToken = getServerMessageHandler().getCsrfToken();
  137. if (!csrfToken.equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) {
  138. payload.put(ApplicationConstants.CSRF_TOKEN, csrfToken);
  139. }
  140. payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations);
  141. payload.put(ApplicationConstants.SERVER_SYNC_ID,
  142. getServerMessageHandler().getLastSeenServerSyncId());
  143. payload.put(ApplicationConstants.CLIENT_TO_SERVER_ID,
  144. clientToServerMessageId++);
  145. if (extraJson != null) {
  146. for (String key : extraJson.keys()) {
  147. payload.put(key, extraJson.get(key));
  148. }
  149. }
  150. send(payload);
  151. }
  152. /**
  153. * Sends an asynchronous or synchronous UIDL request to the server using the
  154. * given URI.
  155. *
  156. * @param uri
  157. * The URI to use for the request. May includes GET parameters
  158. * @param payload
  159. * The contents of the request to send
  160. */
  161. public void send(final JsonObject payload) {
  162. if (push != null && push.isBidirectional()) {
  163. push.push(payload);
  164. } else {
  165. xhrConnection.send(payload);
  166. }
  167. }
  168. /**
  169. * Sets the status for the push connection.
  170. *
  171. * @param enabled
  172. * <code>true</code> to enable the push connection;
  173. * <code>false</code> to disable the push connection.
  174. */
  175. public void setPushEnabled(boolean enabled) {
  176. final PushConfigurationState pushState = connection.getUIConnector()
  177. .getState().pushConfiguration;
  178. if (enabled && push == null) {
  179. push = GWT.create(PushConnection.class);
  180. push.init(connection, pushState);
  181. } else if (!enabled && push != null && push.isActive()) {
  182. push.disconnect(new Command() {
  183. @Override
  184. public void execute() {
  185. push = null;
  186. /*
  187. * If push has been enabled again while we were waiting for
  188. * the old connection to disconnect, now is the right time
  189. * to open a new connection
  190. */
  191. if (pushState.mode.isEnabled()) {
  192. setPushEnabled(true);
  193. }
  194. /*
  195. * Send anything that was enqueued while we waited for the
  196. * connection to close
  197. */
  198. if (getServerRpcQueue().isFlushPending()) {
  199. getServerRpcQueue().flush();
  200. }
  201. }
  202. });
  203. }
  204. }
  205. public void startRequest() {
  206. if (hasActiveRequest) {
  207. getLogger().severe(
  208. "Trying to start a new request while another is active");
  209. }
  210. hasActiveRequest = true;
  211. connection.fireEvent(new RequestStartingEvent(connection));
  212. }
  213. public void endRequest() {
  214. if (!hasActiveRequest) {
  215. getLogger().severe("No active request");
  216. }
  217. // After sendInvocationsToServer() there may be a new active
  218. // request, so we must set hasActiveRequest to false before, not after,
  219. // the call. Active requests used to be tracked with an integer counter,
  220. // so setting it after used to work but not with the #8505 changes.
  221. hasActiveRequest = false;
  222. if (connection.isApplicationRunning()) {
  223. if (getServerRpcQueue().isFlushPending()) {
  224. sendInvocationsToServer();
  225. }
  226. ApplicationConnection.runPostRequestHooks(connection
  227. .getConfiguration().getRootPanelId());
  228. }
  229. // deferring to avoid flickering
  230. Scheduler.get().scheduleDeferred(new Command() {
  231. @Override
  232. public void execute() {
  233. if (!connection.isApplicationRunning()
  234. || !(hasActiveRequest() || getServerRpcQueue()
  235. .isFlushPending())) {
  236. getLoadingIndicator().hide();
  237. // If on Liferay and session expiration management is in
  238. // use, extend session duration on each request.
  239. // Doing it here rather than before the request to improve
  240. // responsiveness.
  241. // Postponed until the end of the next request if other
  242. // requests still pending.
  243. ApplicationConnection.extendLiferaySession();
  244. }
  245. }
  246. });
  247. connection.fireEvent(new ResponseHandlingEndedEvent(connection));
  248. }
  249. /**
  250. * Indicates whether or not there are currently active UIDL requests. Used
  251. * internally to sequence requests properly, seldom needed in Widgets.
  252. *
  253. * @return true if there are active requests
  254. */
  255. public boolean hasActiveRequest() {
  256. return hasActiveRequest;
  257. }
  258. /**
  259. * Returns a human readable string representation of the method used to
  260. * communicate with the server.
  261. *
  262. * @return A string representation of the current transport type
  263. */
  264. public String getCommunicationMethodName() {
  265. if (push != null) {
  266. return "Push (" + push.getTransportType() + ")";
  267. } else {
  268. return "XHR";
  269. }
  270. }
  271. private CommunicationProblemHandler getCommunicationProblemHandler() {
  272. return connection.getCommunicationProblemHandler();
  273. }
  274. private ServerMessageHandler getServerMessageHandler() {
  275. return connection.getServerMessageHandler();
  276. }
  277. private VLoadingIndicator getLoadingIndicator() {
  278. return connection.getLoadingIndicator();
  279. }
  280. /**
  281. * Resynchronize the client side, i.e. reload all component hierarchy and
  282. * state from the server
  283. */
  284. public void resynchronize() {
  285. getLogger().info("Resynchronizing from server");
  286. JsonObject resyncParam = Json.createObject();
  287. resyncParam.put(ApplicationConstants.RESYNCHRONIZE_ID, true);
  288. send(Json.createArray(), resyncParam);
  289. }
  290. /**
  291. * Used internally to update what the server expects
  292. *
  293. * @param clientToServerMessageId
  294. * the new client id to set
  295. */
  296. public void setClientToServerMessageId(int nextExpectedId) {
  297. if (nextExpectedId == clientToServerMessageId) {
  298. // No op as everything matches they way it should
  299. return;
  300. }
  301. if (nextExpectedId > clientToServerMessageId) {
  302. if (clientToServerMessageId == 0) {
  303. // We have never sent a message to the server, so likely the
  304. // server knows better (typical case is that we refreshed a
  305. // @PreserveOnRefresh UI)
  306. getLogger().info(
  307. "Updating client-to-server id to " + nextExpectedId
  308. + " based on server");
  309. } else {
  310. getLogger().warning(
  311. "Server expects next client-to-server id to be "
  312. + nextExpectedId + " but we were going to use "
  313. + clientToServerMessageId + ". Will use "
  314. + nextExpectedId + ".");
  315. }
  316. clientToServerMessageId = nextExpectedId;
  317. } else {
  318. // Server has not yet seen all our messages
  319. // Do nothing as they will arrive eventually
  320. }
  321. }
  322. /**
  323. * Strips the JSON wrapping from the given json string with wrapping.
  324. *
  325. * If the given string is not wrapped as expected, returns null
  326. *
  327. * @since
  328. * @param jsonWithWrapping
  329. * the JSON received from the server
  330. * @return an unwrapped JSON string or null if the given string was not
  331. * wrapped
  332. */
  333. public static String stripJSONWrapping(String jsonWithWrapping) {
  334. if (!jsonWithWrapping
  335. .startsWith(ServerCommunicationHandler.JSON_COMMUNICATION_PREFIX)
  336. || !jsonWithWrapping
  337. .endsWith(ServerCommunicationHandler.JSON_COMMUNICATION_SUFFIX)) {
  338. return null;
  339. }
  340. return jsonWithWrapping.substring(
  341. ServerCommunicationHandler.JSON_COMMUNICATION_PREFIX.length(),
  342. jsonWithWrapping.length()
  343. - ServerCommunicationHandler.JSON_COMMUNICATION_SUFFIX
  344. .length());
  345. }
  346. }