summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2015-04-16 16:40:36 +0300
committerArtur Signell <artur@vaadin.com>2015-07-13 17:19:07 +0300
commit90b4c678d139fc4927811cdcad87d0b91eca98b6 (patch)
tree67dc629b9627efcebeabb0d0360f20f304e8615a /client
parent2e8d06f89b99a63f4b4fa6ece11b4119ae55fd57 (diff)
downloadvaadin-framework-90b4c678d139fc4927811cdcad87d0b91eca98b6.tar.gz
vaadin-framework-90b4c678d139fc4927811cdcad87d0b91eca98b6.zip
Separate server message sending to its own class (#11733)
Change-Id: Ib3c4ac687387f2a239908b7e25e2753dbbf7e98b
Diffstat (limited to 'client')
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java489
-rw-r--r--client/src/com/vaadin/client/Util.java2
-rw-r--r--client/src/com/vaadin/client/communication/AtmospherePushConnection.java2
-rw-r--r--client/src/com/vaadin/client/communication/CommunicationProblemHandler.java14
-rw-r--r--client/src/com/vaadin/client/communication/ServerCommunicationHandler.java482
-rw-r--r--client/src/com/vaadin/client/communication/ServerMessageHandler.java55
-rw-r--r--client/src/com/vaadin/client/communication/ServerRpcQueue.java3
-rw-r--r--client/src/com/vaadin/client/debug/internal/InfoSection.java2
-rw-r--r--client/src/com/vaadin/client/ui/VScrollTable.java3
-rw-r--r--client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java3
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java2
11 files changed, 581 insertions, 476 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index 8865e6efcb..0e84392cf0 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -36,29 +36,20 @@ import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.event.shared.SimpleEventBus;
-import com.google.gwt.http.client.Request;
-import com.google.gwt.http.client.RequestBuilder;
-import com.google.gwt.http.client.RequestCallback;
-import com.google.gwt.http.client.RequestException;
-import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.Window.ClosingEvent;
-import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConfiguration.ErrorMessage;
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
import com.vaadin.client.ResourceLoader.ResourceLoadListener;
-import com.vaadin.client.communication.CommunicationProblemEvent;
import com.vaadin.client.communication.CommunicationProblemHandler;
import com.vaadin.client.communication.Heartbeat;
-import com.vaadin.client.communication.PushConnection;
import com.vaadin.client.communication.RpcManager;
+import com.vaadin.client.communication.ServerCommunicationHandler;
import com.vaadin.client.communication.ServerMessageHandler;
import com.vaadin.client.communication.ServerRpcQueue;
import com.vaadin.client.componentlocator.ComponentLocator;
@@ -73,17 +64,12 @@ import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.VaadinUriResolver;
import com.vaadin.shared.Version;
import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
-import com.vaadin.shared.ui.ui.UIConstants;
-import com.vaadin.shared.ui.ui.UIState.PushConfigurationState;
import com.vaadin.shared.util.SharedUtil;
import elemental.json.Json;
-import elemental.json.JsonArray;
-import elemental.json.JsonObject;
/**
* This is the client side communication "engine", managing client-server
@@ -137,9 +123,6 @@ public class ApplicationConnection implements HasHandlers {
*/
public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh";
- private final String JSON_COMMUNICATION_PREFIX = "for(;;);[";
- private final String JSON_COMMUNICATION_SUFFIX = "]";
-
private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
private WidgetSet widgetSet;
@@ -148,30 +131,15 @@ public class ApplicationConnection implements HasHandlers {
private final UIConnector uIConnector;
- private boolean hasActiveRequest = false;
-
- /**
- * Webkit will ignore outgoing requests while waiting for a response to a
- * navigation event (indicated by a beforeunload event). When this happens,
- * we should keep trying to send the request every now and then until there
- * is a response or until it throws an exception saying that it is already
- * being sent.
- */
- private boolean webkitMaybeIgnoringRequests = false;
-
protected boolean cssLoaded = false;
/** Parameters for this application connection loaded from the web-page */
private ApplicationConfiguration configuration;
- private Date requestStartTime;
-
private final LayoutManager layoutManager;
private final RpcManager rpcManager;
- private PushConnection push;
-
/** Event bus for communication events */
private EventBus eventBus = GWT.create(SimpleEventBus.class);
@@ -405,6 +373,9 @@ public class ApplicationConnection implements HasHandlers {
communicationProblemHandler.setConnection(this);
serverMessageHandler = GWT.create(ServerMessageHandler.class);
serverMessageHandler.setConnection(this);
+ serverCommunicationHandler = GWT
+ .create(ServerCommunicationHandler.class);
+ serverCommunicationHandler.setConnection(this);
}
public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) {
@@ -443,13 +414,6 @@ public class ApplicationConnection implements HasHandlers {
heartbeat.init(this);
- Window.addWindowClosingHandler(new ClosingHandler() {
- @Override
- public void onWindowClosing(ClosingEvent event) {
- webkitMaybeIgnoringRequests = true;
- }
- });
-
// Ensure the overlay container is added to the dom and set as a live
// area for assistive devices
Element overlayContainer = VOverlay.getOverlayContainer(this);
@@ -476,9 +440,10 @@ public class ApplicationConnection implements HasHandlers {
repaintAll();
} else {
// initial UIDL provided in DOM, continue as if returned by request
+
// Hack to avoid logging an error in endRequest()
- startRequest();
- handleJSONText(jsonText, -1);
+ getServerCommunicationHandler().startRequest();
+ getServerMessageHandler().handleJSONText(jsonText, -1);
}
// Tooltip can't be created earlier because the
@@ -502,7 +467,8 @@ public class ApplicationConnection implements HasHandlers {
*/
private boolean isActive() {
return !getServerMessageHandler().isInitialUidlHandled()
- || isWorkPending() || hasActiveRequest()
+ || isWorkPending()
+ || getServerCommunicationHandler().hasActiveRequest()
|| isExecutingDeferredCommands();
}
@@ -613,7 +579,7 @@ public class ApplicationConnection implements HasHandlers {
*
* @param appId
*/
- private static native void runPostRequestHooks(String appId)
+ public static native void runPostRequestHooks(String appId)
/*-{
if ($wnd.vaadin.postRequestHooks) {
for ( var hook in $wnd.vaadin.postRequestHooks) {
@@ -635,7 +601,7 @@ public class ApplicationConnection implements HasHandlers {
* session even though the server side considers the session to be active.
* See ticket #8305 for more information.
*/
- protected native void extendLiferaySession()
+ public static native void extendLiferaySession()
/*-{
if ($wnd.Liferay && $wnd.Liferay.Session) {
$wnd.Liferay.Session.extend();
@@ -646,24 +612,15 @@ public class ApplicationConnection implements HasHandlers {
}
}-*/;
- /**
- * Indicates whether or not there are currently active UIDL requests. Used
- * internally to sequence requests properly, seldom needed in Widgets.
- *
- * @return true if there are active requests
- */
- public boolean hasActiveRequest() {
- return hasActiveRequest;
- }
-
- private String getRepaintAllParameters() {
+ public String getRepaintAllParameters() {
String parameters = ApplicationConstants.URL_PARAMETER_REPAINT_ALL
+ "=1";
return parameters;
}
public void repaintAll() {
- makeUidlRequest(Json.createArray(), getRepaintAllParameters());
+ getServerCommunicationHandler().makeUidlRequest(Json.createArray(),
+ getRepaintAllParameters());
}
/**
@@ -691,231 +648,16 @@ public class ApplicationConnection implements HasHandlers {
getUIConnector().showServerDebugInfo(serverConnector);
}
- /**
- * Makes an UIDL request to the server.
- *
- * @param reqInvocations
- * Data containing RPC invocations and all related information.
- * @param extraParams
- * Parameters that are added as GET parameters to the url.
- * Contains key=value pairs joined by & characters or is empty if
- * no parameters should be added. Should not start with any
- * special character.
- */
- protected void makeUidlRequest(final JsonArray reqInvocations,
- final String extraParams) {
- startRequest();
-
- JsonObject payload = Json.createObject();
- String csrfToken = getServerMessageHandler().getCsrfToken();
- if (!csrfToken.equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) {
- payload.put(ApplicationConstants.CSRF_TOKEN, csrfToken);
- }
- payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations);
- payload.put(ApplicationConstants.SERVER_SYNC_ID,
- getServerMessageHandler().getLastSeenServerSyncId());
-
- getLogger()
- .info("Making UIDL Request with params: " + payload.toJson());
- String uri = translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
- + ApplicationConstants.UIDL_PATH + '/');
-
- if (extraParams != null && extraParams.length() > 0) {
- if (extraParams.equals(getRepaintAllParameters())) {
- payload.put(ApplicationConstants.RESYNCHRONIZE_ID, true);
- } else {
- uri = SharedUtil.addGetParameters(uri, extraParams);
- }
- }
- uri = SharedUtil.addGetParameters(uri, UIConstants.UI_ID_PARAMETER
- + "=" + configuration.getUIId());
-
- doUidlRequest(uri, payload, true);
-
- }
-
- /**
- * Sends an asynchronous or synchronous UIDL request to the server using the
- * given URI.
- *
- * @param uri
- * The URI to use for the request. May includes GET parameters
- * @param payload
- * The contents of the request to send
- * @param retry
- * true when a status code 0 should be retried
- * @since 7.3.7
- */
- public void doUidlRequest(final String uri, final JsonObject payload,
- final boolean retry) {
- RequestCallback requestCallback = new RequestCallback() {
-
- @Override
- public void onError(Request request, Throwable exception) {
- getCommunicationProblemHandler().xhrException(
- payload,
- new CommunicationProblemEvent(request, uri, payload,
- exception));
- }
-
- @Override
- public void onResponseReceived(Request request, Response response) {
- getLogger().info(
- "Server visit took "
- + String.valueOf((new Date()).getTime()
- - requestStartTime.getTime()) + "ms");
-
- int statusCode = response.getStatusCode();
-
- if (statusCode != 200) {
- // There was a problem
- CommunicationProblemEvent problemEvent = new CommunicationProblemEvent(
- request, uri, payload, response);
-
- getCommunicationProblemHandler().xhrInvalidStatusCode(
- problemEvent, retry);
- return;
- }
-
- String contentType = response.getHeader("Content-Type");
- if (contentType == null
- || !contentType.startsWith("application/json")) {
- getCommunicationProblemHandler().xhrInvalidContent(
- new CommunicationProblemEvent(request, uri,
- payload, response));
- return;
- }
-
- // for(;;);["+ realJson +"]"
- String responseText = response.getText();
-
- if (!responseText.startsWith(JSON_COMMUNICATION_PREFIX)) {
- getCommunicationProblemHandler().xhrInvalidContent(
- new CommunicationProblemEvent(request, uri,
- payload, response));
- return;
- }
-
- final String jsonText = responseText.substring(
- JSON_COMMUNICATION_PREFIX.length(),
- responseText.length()
- - JSON_COMMUNICATION_SUFFIX.length());
-
- handleJSONText(jsonText, statusCode);
- }
- };
- if (push != null) {
- push.push(payload);
- } else {
- try {
- doAjaxRequest(uri, payload, requestCallback);
- } catch (RequestException e) {
- getCommunicationProblemHandler().xhrException(payload,
- new CommunicationProblemEvent(null, uri, payload, e));
- }
- }
- }
-
- /**
- * Handles received UIDL JSON text, parsing it, and passing it on to the
- * appropriate handlers, while logging timing information.
- *
- * @param jsonText
- * @param statusCode
- */
- private void handleJSONText(String jsonText, int statusCode) {
- final Date start = new Date();
- final ValueMap json;
- try {
- json = parseJSONResponse(jsonText);
- } catch (final Exception e) {
- endRequest();
- showCommunicationError(e.getMessage() + " - Original JSON-text:"
- + jsonText, statusCode);
- return;
- }
-
- getLogger().info(
- "JSON parsing took " + (new Date().getTime() - start.getTime())
- + "ms");
- if (getState() == State.RUNNING) {
- getServerMessageHandler().handleUIDLMessage(start, jsonText, json);
- } else if (getState() == State.INITIALIZING) {
- // Application is starting up for the first time
- setApplicationRunning(true);
- handleWhenCSSLoaded(jsonText, json);
- } else {
- getLogger()
- .warning(
- "Ignored received message because application has already been stopped");
- return;
- }
- }
-
- /**
- * Sends an asynchronous UIDL request to the server using the given URI.
- *
- * @param uri
- * The URI to use for the request. May includes GET parameters
- * @param payload
- * The contents of the request to send
- * @param requestCallback
- * The handler for the response
- * @throws RequestException
- * if the request could not be sent
- */
- protected void doAjaxRequest(String uri, JsonObject payload,
- RequestCallback requestCallback) throws RequestException {
- RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
- // TODO enable timeout
- // rb.setTimeoutMillis(timeoutMillis);
- // TODO this should be configurable
- rb.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE);
- rb.setRequestData(payload.toJson());
- rb.setCallback(requestCallback);
-
- final Request request = rb.send();
- if (webkitMaybeIgnoringRequests && BrowserInfo.get().isWebkit()) {
- final int retryTimeout = 250;
- new Timer() {
- @Override
- public void run() {
- // Use native js to access private field in Request
- if (resendRequest(request) && webkitMaybeIgnoringRequests) {
- // Schedule retry if still needed
- schedule(retryTimeout);
- }
- }
- }.schedule(retryTimeout);
- }
- }
-
- private static native boolean resendRequest(Request request)
- /*-{
- var xhr = request.@com.google.gwt.http.client.Request::xmlHttpRequest
- if (xhr.readyState != 1) {
- // Progressed to some other readyState -> no longer blocked
- return false;
- }
- try {
- xhr.send();
- return true;
- } catch (e) {
- // send throws exception if it is running for real
- return false;
- }
- }-*/;
-
int cssWaits = 0;
protected ServerRpcQueue serverRpcQueue;
protected CommunicationProblemHandler communicationProblemHandler;
protected ServerMessageHandler serverMessageHandler;
+ protected ServerCommunicationHandler serverCommunicationHandler;
static final int MAX_CSS_WAITS = 100;
- protected void handleWhenCSSLoaded(final String jsonText,
- final ValueMap json) {
+ public void handleWhenCSSLoaded(final String jsonText, final ValueMap json) {
if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) {
(new Timer() {
@Override
@@ -962,7 +704,7 @@ public class ApplicationConnection implements HasHandlers {
* The status code returned for the request
*
*/
- protected void showCommunicationError(String details, int statusCode) {
+ public void showCommunicationError(String details, int statusCode) {
getLogger().severe("Communication error: " + details);
showError(details, configuration.getCommunicationError());
}
@@ -1002,57 +744,6 @@ public class ApplicationConnection implements HasHandlers {
message.getMessage(), details, message.getUrl());
}
- protected void startRequest() {
- if (hasActiveRequest) {
- getLogger().severe(
- "Trying to start a new request while another is active");
- }
- hasActiveRequest = true;
- requestStartTime = new Date();
- eventBus.fireEvent(new RequestStartingEvent(this));
- }
-
- public void endRequest() {
- if (!hasActiveRequest) {
- getLogger().severe("No active request");
- }
- // After sendInvocationsToServer() there may be a new active
- // request, so we must set hasActiveRequest to false before, not after,
- // the call. Active requests used to be tracked with an integer counter,
- // so setting it after used to work but not with the #8505 changes.
- hasActiveRequest = false;
-
- webkitMaybeIgnoringRequests = false;
-
- if (isApplicationRunning()) {
- if (serverRpcQueue.isFlushPending()) {
- sendInvocationsToServer();
- }
- runPostRequestHooks(configuration.getRootPanelId());
- }
-
- // deferring to avoid flickering
- Scheduler.get().scheduleDeferred(new Command() {
- @Override
- public void execute() {
- if (!isApplicationRunning()
- || !(hasActiveRequest() || serverRpcQueue
- .isFlushPending())) {
- getLoadingIndicator().hide();
-
- // If on Liferay and session expiration management is in
- // use, extend session duration on each request.
- // Doing it here rather than before the request to improve
- // responsiveness.
- // Postponed until the end of the next request if other
- // requests still pending.
- extendLiferaySession();
- }
- }
- });
- eventBus.fireEvent(new ResponseHandlingEndedEvent(this));
- }
-
/**
* Checks if the client has running or scheduled commands
*/
@@ -1124,15 +815,6 @@ public class ApplicationConnection implements HasHandlers {
return getLoadingIndicator().isVisible();
}
- private static native ValueMap parseJSONResponse(String jsonText)
- /*-{
- try {
- return JSON.parse(jsonText);
- } catch (ignored) {
- return eval('(' + jsonText + ')');
- }
- }-*/;
-
public void loadStyleDependencies(JsArrayString dependencies) {
// Assuming no reason to interpret in a defined order
ResourceLoadListener resourceLoadListener = new ResourceLoadListener() {
@@ -1226,65 +908,6 @@ public class ApplicationConnection implements HasHandlers {
serverRpcQueue.flush();
}
- public void doSendPendingVariableChanges() {
- if (!isApplicationRunning()) {
- getLogger()
- .warning(
- "Trying to send RPC from not yet started or stopped application");
- return;
- }
-
- if (hasActiveRequest() || (push != null && !push.isActive())) {
- // There is an active request or push is enabled but not active
- // -> send when current request completes or push becomes active
- } else {
- sendInvocationsToServer();
- }
- }
-
- /**
- * Sends all pending method invocations (server RPC and legacy variable
- * changes) to the server.
- *
- */
- private void sendInvocationsToServer() {
- if (serverRpcQueue.isEmpty()) {
- return;
- }
-
- if (ApplicationConfiguration.isDebugMode()) {
- Util.logMethodInvocations(this, serverRpcQueue.getAll());
- }
-
- boolean showLoadingIndicator = serverRpcQueue.showLoadingIndicator();
- JsonArray reqJson = serverRpcQueue.toJson();
- serverRpcQueue.clear();
-
- if (reqJson.length() == 0) {
- // Nothing to send, all invocations were filtered out (for
- // non-existing connectors)
- getLogger()
- .warning(
- "All RPCs filtered out, not sending anything to the server");
- return;
- }
-
- String extraParams = "";
- if (!getConfiguration().isWidgetsetVersionSent()) {
- if (!extraParams.isEmpty()) {
- extraParams += "&";
- }
- String widgetsetVersion = Version.getFullVersion();
- extraParams += "v-wsver=" + widgetsetVersion;
-
- getConfiguration().setWidgetsetVersionSent();
- }
- if (showLoadingIndicator) {
- getLoadingIndicator().trigger();
- }
- makeUidlRequest(reqJson, extraParams);
- }
-
/**
* Sends a new value for the given paintables given variable to the server.
* <p>
@@ -1982,70 +1605,6 @@ public class ApplicationConnection implements HasHandlers {
focusedElement);
}
- /**
- * Sets the status for the push connection.
- *
- * @param enabled
- * <code>true</code> to enable the push connection;
- * <code>false</code> to disable the push connection.
- */
- public void setPushEnabled(boolean enabled) {
- final PushConfigurationState pushState = uIConnector.getState().pushConfiguration;
-
- if (enabled && push == null) {
- push = GWT.create(PushConnection.class);
- push.init(this, pushState, new CommunicationErrorHandler() {
- @Override
- public boolean onError(String details, int statusCode) {
- handleCommunicationError(details, statusCode);
- return true;
- }
- });
- } else if (!enabled && push != null && push.isActive()) {
- push.disconnect(new Command() {
- @Override
- public void execute() {
- push = null;
- /*
- * If push has been enabled again while we were waiting for
- * the old connection to disconnect, now is the right time
- * to open a new connection
- */
- if (pushState.mode.isEnabled()) {
- setPushEnabled(true);
- }
-
- /*
- * Send anything that was enqueued while we waited for the
- * connection to close
- */
- if (serverRpcQueue.isFlushPending()) {
- sendPendingVariableChanges();
- }
- }
- });
- }
- }
-
- public void handlePushMessage(String message) {
- handleJSONText(message, 200);
- }
-
- /**
- * Returns a human readable string representation of the method used to
- * communicate with the server.
- *
- * @since 7.1
- * @return A string representation of the current transport type
- */
- public String getCommunicationMethodName() {
- if (push != null) {
- return "Push (" + push.getTransportType() + ")";
- } else {
- return "XHR";
- }
- }
-
private static Logger getLogger() {
return Logger.getLogger(ApplicationConnection.class.getName());
}
@@ -2107,17 +1666,23 @@ public class ApplicationConnection implements HasHandlers {
}
/**
+ * Gets the server communication handler for this application
+ *
+ * @return the server communication handler
+ */
+ public ServerCommunicationHandler getServerCommunicationHandler() {
+ return serverCommunicationHandler;
+ }
+
+ /**
* @return the widget set
*/
public WidgetSet getWidgetSet() {
return widgetSet;
}
- /**
- * @since
- * @return
- */
public int getLastSeenServerSyncId() {
return getServerMessageHandler().getLastSeenServerSyncId();
}
+
}
diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java
index b4f777f50e..43963e14c2 100644
--- a/client/src/com/vaadin/client/Util.java
+++ b/client/src/com/vaadin/client/Util.java
@@ -780,7 +780,7 @@ public class Util {
+ "(" + formattedParams + ")";
}
- static void logMethodInvocations(ApplicationConnection c,
+ public static void logMethodInvocations(ApplicationConnection c,
Collection<MethodInvocation> methodInvocations) {
try {
getLogger().info("RPC invocations to be sent to the server:");
diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
index f67ef091c9..45095bedb1 100644
--- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
+++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
@@ -339,7 +339,7 @@ public class AtmospherePushConnection implements PushConnection {
getLogger().info("Received push message: " + message);
// "for(;;);[{json}]" -> "{json}"
message = message.substring(9, message.length() - 1);
- connection.handlePushMessage(message);
+ connection.getServerMessageHandler().handleJSONText(message, 200);
}
}
diff --git a/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java
index 23b2678365..d8740a50e6 100644
--- a/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java
+++ b/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java
@@ -108,8 +108,8 @@ public class CommunicationProblemHandler {
public void run() {
// doUidlRequest does not call startRequest so we do
// not call endRequest before it
- connection.doUidlRequest(event.getUri(),
- event.getPayload(), true);
+ getServerCommunicationHandler().doUidlRequest(
+ event.getUri(), event.getPayload(), true);
}
}).schedule(Integer.parseInt(delay));
return;
@@ -138,7 +138,7 @@ public class CommunicationProblemHandler {
* @since
*/
private void endRequestAndStopApplication() {
- connection.endRequest();
+ getServerCommunicationHandler().endRequest();
// Consider application not running any more and prevent all
// future requests
@@ -174,8 +174,8 @@ public class CommunicationProblemHandler {
public void run() {
// doUidlRequest does not call startRequest so we do
// not call endRequest before it
- connection.doUidlRequest(event.getUri(),
- event.getPayload(), false);
+ getServerCommunicationHandler().doUidlRequest(
+ event.getUri(), event.getPayload(), false);
}
}).schedule(100);
} else {
@@ -220,4 +220,8 @@ public class CommunicationProblemHandler {
}
}
+
+ private ServerCommunicationHandler getServerCommunicationHandler() {
+ return connection.getServerCommunicationHandler();
+ }
}
diff --git a/client/src/com/vaadin/client/communication/ServerCommunicationHandler.java b/client/src/com/vaadin/client/communication/ServerCommunicationHandler.java
new file mode 100644
index 0000000000..ef215220e2
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/ServerCommunicationHandler.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.communication;
+
+import java.util.Date;
+import java.util.logging.Logger;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler;
+import com.vaadin.client.ApplicationConnection.RequestStartingEvent;
+import com.vaadin.client.ApplicationConnection.ResponseHandlingEndedEvent;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+import com.vaadin.client.VLoadingIndicator;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.JsonConstants;
+import com.vaadin.shared.Version;
+import com.vaadin.shared.ui.ui.UIConstants;
+import com.vaadin.shared.ui.ui.UIState.PushConfigurationState;
+import com.vaadin.shared.util.SharedUtil;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+
+/**
+ * ServerCommunicationHandler is responsible for communicating (sending and
+ * receiving messages) with the servlet.
+ *
+ * It will internally use either XHR or websockets for communicating, depending
+ * on how the application is configured.
+ *
+ * Uses {@link ServerMessageHandler} for processing received messages
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class ServerCommunicationHandler {
+
+ private final String JSON_COMMUNICATION_PREFIX = "for(;;);[";
+ private final String JSON_COMMUNICATION_SUFFIX = "]";
+
+ private ApplicationConnection connection;
+ private PushConnection push;
+ private boolean hasActiveRequest = false;
+ private Date requestStartTime;
+
+ /**
+ * Webkit will ignore outgoing requests while waiting for a response to a
+ * navigation event (indicated by a beforeunload event). When this happens,
+ * we should keep trying to send the request every now and then until there
+ * is a response or until it throws an exception saying that it is already
+ * being sent.
+ */
+ private boolean webkitMaybeIgnoringRequests = false;
+
+ public ServerCommunicationHandler() {
+ Window.addWindowClosingHandler(new ClosingHandler() {
+ @Override
+ public void onWindowClosing(ClosingEvent event) {
+ webkitMaybeIgnoringRequests = true;
+ }
+ });
+
+ }
+
+ /**
+ * Sets the application connection this handler is connected to
+ *
+ * @param connection
+ * the application connection this handler is connected to
+ */
+ public void setConnection(ApplicationConnection connection) {
+ this.connection = connection;
+ }
+
+ public static Logger getLogger() {
+ return Logger.getLogger(ServerCommunicationHandler.class.getName());
+ }
+
+ public void doSendPendingVariableChanges() {
+ if (!connection.isApplicationRunning()) {
+ getLogger()
+ .warning(
+ "Trying to send RPC from not yet started or stopped application");
+ return;
+ }
+
+ if (hasActiveRequest() || (push != null && !push.isActive())) {
+ // There is an active request or push is enabled but not active
+ // -> send when current request completes or push becomes active
+ } else {
+ sendInvocationsToServer();
+ }
+ }
+
+ /**
+ * Sends all pending method invocations (server RPC and legacy variable
+ * changes) to the server.
+ *
+ */
+ public void sendInvocationsToServer() {
+ ServerRpcQueue serverRpcQueue = getServerRpcQueue();
+ if (serverRpcQueue.isEmpty()) {
+ return;
+ }
+
+ if (ApplicationConfiguration.isDebugMode()) {
+ Util.logMethodInvocations(connection, serverRpcQueue.getAll());
+ }
+
+ boolean showLoadingIndicator = serverRpcQueue.showLoadingIndicator();
+ JsonArray reqJson = serverRpcQueue.toJson();
+ serverRpcQueue.clear();
+
+ if (reqJson.length() == 0) {
+ // Nothing to send, all invocations were filtered out (for
+ // non-existing connectors)
+ getLogger()
+ .warning(
+ "All RPCs filtered out, not sending anything to the server");
+ return;
+ }
+
+ String extraParams = "";
+ if (!connection.getConfiguration().isWidgetsetVersionSent()) {
+ if (!extraParams.isEmpty()) {
+ extraParams += "&";
+ }
+ String widgetsetVersion = Version.getFullVersion();
+ extraParams += "v-wsver=" + widgetsetVersion;
+
+ connection.getConfiguration().setWidgetsetVersionSent();
+ }
+ if (showLoadingIndicator) {
+ connection.getLoadingIndicator().trigger();
+ }
+ makeUidlRequest(reqJson, extraParams);
+ }
+
+ private ServerRpcQueue getServerRpcQueue() {
+ return connection.getServerRpcQueue();
+ }
+
+ /**
+ * Makes an UIDL request to the server.
+ *
+ * @param reqInvocations
+ * Data containing RPC invocations and all related information.
+ * @param extraParams
+ * Parameters that are added as GET parameters to the url.
+ * Contains key=value pairs joined by & characters or is empty if
+ * no parameters should be added. Should not start with any
+ * special character.
+ */
+ public void makeUidlRequest(final JsonArray reqInvocations,
+ final String extraParams) {
+ startRequest();
+
+ JsonObject payload = Json.createObject();
+ String csrfToken = getServerMessageHandler().getCsrfToken();
+ if (!csrfToken.equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) {
+ payload.put(ApplicationConstants.CSRF_TOKEN, csrfToken);
+ }
+ payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations);
+ payload.put(ApplicationConstants.SERVER_SYNC_ID,
+ getServerMessageHandler().getLastSeenServerSyncId());
+
+ getLogger()
+ .info("Making UIDL Request with params: " + payload.toJson());
+ String uri = connection
+ .translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ApplicationConstants.UIDL_PATH + '/');
+
+ if (extraParams.equals(connection.getRepaintAllParameters())) {
+ payload.put(ApplicationConstants.RESYNCHRONIZE_ID, true);
+ } else {
+ uri = SharedUtil.addGetParameters(uri, extraParams);
+ }
+ uri = SharedUtil.addGetParameters(uri, UIConstants.UI_ID_PARAMETER
+ + "=" + connection.getConfiguration().getUIId());
+
+ doUidlRequest(uri, payload, true);
+
+ }
+
+ /**
+ * Sends an asynchronous or synchronous UIDL request to the server using the
+ * given URI.
+ *
+ * @param uri
+ * The URI to use for the request. May includes GET parameters
+ * @param payload
+ * The contents of the request to send
+ * @param retry
+ * true when a status code 0 should be retried
+ */
+ public void doUidlRequest(final String uri, final JsonObject payload,
+ final boolean retry) {
+ RequestCallback requestCallback = new RequestCallback() {
+
+ @Override
+ public void onError(Request request, Throwable exception) {
+ getCommunicationProblemHandler().xhrException(
+ payload,
+ new CommunicationProblemEvent(request, uri, payload,
+ exception));
+ }
+
+ @Override
+ public void onResponseReceived(Request request, Response response) {
+ getLogger().info(
+ "Server visit took "
+ + String.valueOf((new Date()).getTime()
+ - requestStartTime.getTime()) + "ms");
+
+ int statusCode = response.getStatusCode();
+
+ if (statusCode != 200) {
+ // There was a problem
+ CommunicationProblemEvent problemEvent = new CommunicationProblemEvent(
+ request, uri, payload, response);
+
+ getCommunicationProblemHandler().xhrInvalidStatusCode(
+ problemEvent, retry);
+ return;
+ }
+
+ String contentType = response.getHeader("Content-Type");
+ if (contentType == null
+ || !contentType.startsWith("application/json")) {
+ getCommunicationProblemHandler().xhrInvalidContent(
+ new CommunicationProblemEvent(request, uri,
+ payload, response));
+ return;
+ }
+
+ // for(;;);["+ realJson +"]"
+ String responseText = response.getText();
+
+ if (!responseText.startsWith(JSON_COMMUNICATION_PREFIX)) {
+ getCommunicationProblemHandler().xhrInvalidContent(
+ new CommunicationProblemEvent(request, uri,
+ payload, response));
+ return;
+ }
+
+ final String jsonText = responseText.substring(
+ JSON_COMMUNICATION_PREFIX.length(),
+ responseText.length()
+ - JSON_COMMUNICATION_SUFFIX.length());
+
+ getServerMessageHandler().handleJSONText(jsonText, statusCode);
+ }
+ };
+ if (push != null) {
+ push.push(payload);
+ } else {
+ try {
+ doAjaxRequest(uri, payload, requestCallback);
+ } catch (RequestException e) {
+ getCommunicationProblemHandler().xhrException(payload,
+ new CommunicationProblemEvent(null, uri, payload, e));
+ }
+ }
+ }
+
+ /**
+ * Sends an asynchronous UIDL request to the server using the given URI.
+ *
+ * @param uri
+ * The URI to use for the request. May includes GET parameters
+ * @param payload
+ * The contents of the request to send
+ * @param requestCallback
+ * The handler for the response
+ * @throws RequestException
+ * if the request could not be sent
+ */
+ protected void doAjaxRequest(String uri, JsonObject payload,
+ RequestCallback requestCallback) throws RequestException {
+ RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
+ // TODO enable timeout
+ // rb.setTimeoutMillis(timeoutMillis);
+ // TODO this should be configurable
+ rb.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE);
+ rb.setRequestData(payload.toJson());
+ rb.setCallback(requestCallback);
+
+ final Request request = rb.send();
+ if (webkitMaybeIgnoringRequests && BrowserInfo.get().isWebkit()) {
+ final int retryTimeout = 250;
+ new Timer() {
+ @Override
+ public void run() {
+ // Use native js to access private field in Request
+ if (resendRequest(request) && webkitMaybeIgnoringRequests) {
+ // Schedule retry if still needed
+ schedule(retryTimeout);
+ }
+ }
+ }.schedule(retryTimeout);
+ }
+ }
+
+ private static native boolean resendRequest(Request request)
+ /*-{
+ var xhr = request.@com.google.gwt.http.client.Request::xmlHttpRequest
+ if (xhr.readyState != 1) {
+ // Progressed to some other readyState -> no longer blocked
+ return false;
+ }
+ try {
+ xhr.send();
+ return true;
+ } catch (e) {
+ // send throws exception if it is running for real
+ return false;
+ }
+ }-*/;
+
+ /**
+ * Sets the status for the push connection.
+ *
+ * @param enabled
+ * <code>true</code> to enable the push connection;
+ * <code>false</code> to disable the push connection.
+ */
+ public void setPushEnabled(boolean enabled) {
+ final PushConfigurationState pushState = connection.getUIConnector()
+ .getState().pushConfiguration;
+
+ if (enabled && push == null) {
+ push = GWT.create(PushConnection.class);
+ push.init(connection, pushState, new CommunicationErrorHandler() {
+ @Override
+ public boolean onError(String details, int statusCode) {
+ connection.handleCommunicationError(details, statusCode);
+ return true;
+ }
+ });
+ } else if (!enabled && push != null && push.isActive()) {
+ push.disconnect(new Command() {
+ @Override
+ public void execute() {
+ push = null;
+ /*
+ * If push has been enabled again while we were waiting for
+ * the old connection to disconnect, now is the right time
+ * to open a new connection
+ */
+ if (pushState.mode.isEnabled()) {
+ setPushEnabled(true);
+ }
+
+ /*
+ * Send anything that was enqueued while we waited for the
+ * connection to close
+ */
+ if (getServerRpcQueue().isFlushPending()) {
+ getServerRpcQueue().flush();
+ }
+ }
+ });
+ }
+ }
+
+ public void startRequest() {
+ if (hasActiveRequest) {
+ getLogger().severe(
+ "Trying to start a new request while another is active");
+ }
+ hasActiveRequest = true;
+ requestStartTime = new Date();
+ connection.fireEvent(new RequestStartingEvent(connection));
+ }
+
+ public void endRequest() {
+ if (!hasActiveRequest) {
+ getLogger().severe("No active request");
+ }
+ // After sendInvocationsToServer() there may be a new active
+ // request, so we must set hasActiveRequest to false before, not after,
+ // the call. Active requests used to be tracked with an integer counter,
+ // so setting it after used to work but not with the #8505 changes.
+ hasActiveRequest = false;
+
+ webkitMaybeIgnoringRequests = false;
+
+ if (connection.isApplicationRunning()) {
+ if (getServerRpcQueue().isFlushPending()) {
+ sendInvocationsToServer();
+ }
+ ApplicationConnection.runPostRequestHooks(connection
+ .getConfiguration().getRootPanelId());
+ }
+
+ // deferring to avoid flickering
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ if (!connection.isApplicationRunning()
+ || !(hasActiveRequest() || getServerRpcQueue()
+ .isFlushPending())) {
+ getLoadingIndicator().hide();
+
+ // If on Liferay and session expiration management is in
+ // use, extend session duration on each request.
+ // Doing it here rather than before the request to improve
+ // responsiveness.
+ // Postponed until the end of the next request if other
+ // requests still pending.
+ ApplicationConnection.extendLiferaySession();
+ }
+ }
+ });
+ connection.fireEvent(new ResponseHandlingEndedEvent(connection));
+ }
+
+ /**
+ * Indicates whether or not there are currently active UIDL requests. Used
+ * internally to sequence requests properly, seldom needed in Widgets.
+ *
+ * @return true if there are active requests
+ */
+ public boolean hasActiveRequest() {
+ return hasActiveRequest;
+ }
+
+ /**
+ * Returns a human readable string representation of the method used to
+ * communicate with the server.
+ *
+ * @return A string representation of the current transport type
+ */
+ public String getCommunicationMethodName() {
+ if (push != null) {
+ return "Push (" + push.getTransportType() + ")";
+ } else {
+ return "XHR";
+ }
+ }
+
+ private CommunicationProblemHandler getCommunicationProblemHandler() {
+ return connection.getCommunicationProblemHandler();
+ }
+
+ private ServerMessageHandler getServerMessageHandler() {
+ return connection.getServerMessageHandler();
+ }
+
+ private VLoadingIndicator getLoadingIndicator() {
+ return connection.getLoadingIndicator();
+ }
+
+}
diff --git a/client/src/com/vaadin/client/communication/ServerMessageHandler.java b/client/src/com/vaadin/client/communication/ServerMessageHandler.java
index cb0dc898c0..a9874d99ea 100644
--- a/client/src/com/vaadin/client/communication/ServerMessageHandler.java
+++ b/client/src/com/vaadin/client/communication/ServerMessageHandler.java
@@ -38,6 +38,7 @@ import com.vaadin.client.ApplicationConfiguration;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ApplicationConnection.MultiStepDuration;
import com.vaadin.client.ApplicationConnection.ResponseHandlingStartedEvent;
+import com.vaadin.client.ApplicationConnection.State;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.ConnectorMap;
@@ -225,6 +226,52 @@ public class ServerMessageHandler {
return Logger.getLogger(ServerMessageHandler.class.getName());
}
+ /**
+ * Handles received UIDL JSON text, parsing it, and passing it on to the
+ * appropriate handlers, while logging timing information.
+ *
+ * @param jsonText
+ * @param statusCode
+ */
+ public void handleJSONText(String jsonText, int statusCode) {
+ final Date start = new Date();
+ final ValueMap json;
+ try {
+ json = parseJSONResponse(jsonText);
+ } catch (final Exception e) {
+ // FIXME
+ getServerCommunicationHandler().endRequest();
+ connection.showCommunicationError(e.getMessage()
+ + " - Original JSON-text:" + jsonText, statusCode);
+ return;
+ }
+
+ getLogger().info(
+ "JSON parsing took " + (new Date().getTime() - start.getTime())
+ + "ms");
+ if (connection.getState() == State.RUNNING) {
+ handleUIDLMessage(start, jsonText, json);
+ } else if (connection.getState() == State.INITIALIZING) {
+ // Application is starting up for the first time
+ connection.setApplicationRunning(true);
+ connection.handleWhenCSSLoaded(jsonText, json);
+ } else {
+ getLogger()
+ .warning(
+ "Ignored received message because application has already been stopped");
+ return;
+ }
+ }
+
+ private static native ValueMap parseJSONResponse(String jsonText)
+ /*-{
+ try {
+ return JSON.parse(jsonText);
+ } catch (ignored) {
+ return eval('(' + jsonText + ')');
+ }
+ }-*/;
+
public void handleUIDLMessage(final Date start, final String jsonText,
final ValueMap json) {
if (!responseHandlingLocks.isEmpty()) {
@@ -274,7 +321,7 @@ public class ServerMessageHandler {
if (meta == null || !meta.containsKey("async")) {
// End the request if the received message was a
// response, not sent asynchronously
- connection.endRequest();
+ getServerCommunicationHandler().endRequest();
}
resumeResponseHandling(lock);
@@ -513,7 +560,7 @@ public class ServerMessageHandler {
// End the request if the received message was a response,
// not sent asynchronously
// FIXME
- connection.endRequest();
+ getServerCommunicationHandler().endRequest();
}
resumeResponseHandling(lock);
@@ -1539,4 +1586,8 @@ public class ServerMessageHandler {
return connection.getRpcManager();
}
+ private ServerCommunicationHandler getServerCommunicationHandler() {
+ return connection.getServerCommunicationHandler();
+ }
+
}
diff --git a/client/src/com/vaadin/client/communication/ServerRpcQueue.java b/client/src/com/vaadin/client/communication/ServerRpcQueue.java
index d9241e4c63..2b7bab447f 100644
--- a/client/src/com/vaadin/client/communication/ServerRpcQueue.java
+++ b/client/src/com/vaadin/client/communication/ServerRpcQueue.java
@@ -201,7 +201,8 @@ public class ServerRpcQueue {
// Somebody else cleared the queue before we had the chance
return;
}
- connection.doSendPendingVariableChanges();
+ connection.getServerCommunicationHandler()
+ .doSendPendingVariableChanges();
}
};
diff --git a/client/src/com/vaadin/client/debug/internal/InfoSection.java b/client/src/com/vaadin/client/debug/internal/InfoSection.java
index dfb31cdd18..aec6433bd9 100644
--- a/client/src/com/vaadin/client/debug/internal/InfoSection.java
+++ b/client/src/com/vaadin/client/debug/internal/InfoSection.java
@@ -166,7 +166,7 @@ public class InfoSection implements Section {
addRow("Theme", connection.getUIConnector().getActiveTheme());
String communicationMethodInfo = connection
- .getCommunicationMethodName();
+ .getServerCommunicationHandler().getCommunicationMethodName();
int pollInterval = connection.getUIConnector().getState().pollInterval;
if (pollInterval > 0) {
communicationMethodInfo += " (poll interval " + pollInterval
diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java
index 6bb3199b08..2c091e4f78 100644
--- a/client/src/com/vaadin/client/ui/VScrollTable.java
+++ b/client/src/com/vaadin/client/ui/VScrollTable.java
@@ -2616,7 +2616,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
@Override
public void run() {
- if (client.hasActiveRequest() || navKeyDown) {
+ if (client.getServerCommunicationHandler().hasActiveRequest()
+ || navKeyDown) {
// if client connection is busy, don't bother loading it more
VConsole.log("Postponed rowfetch");
schedule(250);
diff --git a/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java b/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java
index efca46b522..a0be6a195f 100644
--- a/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java
+++ b/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java
@@ -488,7 +488,8 @@ public class VDragAndDropManager {
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@Override
public boolean execute() {
- if (!client.hasActiveRequest()) {
+ if (!client.getServerCommunicationHandler()
+ .hasActiveRequest()) {
removeActiveDragSourceStyleName(dragSource);
return false;
}
diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index 60e9587493..6d708a9f14 100644
--- a/client/src/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java
@@ -748,7 +748,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}
if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) {
- getConnection().setPushEnabled(
+ getConnection().getServerCommunicationHandler().setPushEnabled(
getState().pushConfiguration.mode.isEnabled());
}