Browse Source

Separate server message sending to its own class (#11733)

Change-Id: Ib3c4ac687387f2a239908b7e25e2753dbbf7e98b
tags/7.6.0.alpha5
Artur Signell 9 years ago
parent
commit
90b4c678d1

+ 27
- 462
client/src/com/vaadin/client/ApplicationConnection.java View File

@@ -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());
}
@@ -2106,6 +1665,15 @@ public class ApplicationConnection implements HasHandlers {
return rpcManager;
}

/**
* Gets the server communication handler for this application
*
* @return the server communication handler
*/
public ServerCommunicationHandler getServerCommunicationHandler() {
return serverCommunicationHandler;
}

/**
* @return the widget set
*/
@@ -2113,11 +1681,8 @@ public class ApplicationConnection implements HasHandlers {
return widgetSet;
}

/**
* @since
* @return
*/
public int getLastSeenServerSyncId() {
return getServerMessageHandler().getLastSeenServerSyncId();
}

}

+ 1
- 1
client/src/com/vaadin/client/Util.java View File

@@ -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:");

+ 1
- 1
client/src/com/vaadin/client/communication/AtmospherePushConnection.java View File

@@ -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);
}
}


+ 9
- 5
client/src/com/vaadin/client/communication/CommunicationProblemHandler.java View File

@@ -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();
}
}

+ 482
- 0
client/src/com/vaadin/client/communication/ServerCommunicationHandler.java View File

@@ -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();
}

}

+ 53
- 2
client/src/com/vaadin/client/communication/ServerMessageHandler.java View File

@@ -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();
}

}

+ 2
- 1
client/src/com/vaadin/client/communication/ServerRpcQueue.java View File

@@ -201,7 +201,8 @@ public class ServerRpcQueue {
// Somebody else cleared the queue before we had the chance
return;
}
connection.doSendPendingVariableChanges();
connection.getServerCommunicationHandler()
.doSendPendingVariableChanges();
}
};


+ 1
- 1
client/src/com/vaadin/client/debug/internal/InfoSection.java View File

@@ -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

+ 2
- 1
client/src/com/vaadin/client/ui/VScrollTable.java View File

@@ -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);

+ 2
- 1
client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java View File

@@ -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;
}

+ 1
- 1
client/src/com/vaadin/client/ui/ui/UIConnector.java View File

@@ -748,7 +748,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}

if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) {
getConnection().setPushEnabled(
getConnection().getServerCommunicationHandler().setPushEnabled(
getState().pushConfiguration.mode.isEnabled());
}


+ 9
- 16
uitest/src/com/vaadin/tests/widgetset/client/MockApplicationConnection.java View File

@@ -16,12 +16,8 @@
package com.vaadin.tests.widgetset.client;

import com.vaadin.client.ApplicationConnection;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.tests.widgetset.server.csrf.ui.CsrfTokenDisabled;

import elemental.json.JsonObject;
import elemental.json.JsonValue;

/**
* Mock ApplicationConnection for several issues where we need to hack it.
*
@@ -34,16 +30,21 @@ public class MockApplicationConnection extends ApplicationConnection {
super();
serverMessageHandler = new MockServerMessageHandler();
serverMessageHandler.setConnection(this);
serverCommunicationHandler = new MockServerCommunicationHandler();
serverCommunicationHandler.setConnection(this);
}

// The last token sent to the server.
private String lastCsrfTokenSent;

@Override
public MockServerMessageHandler getServerMessageHandler() {
return (MockServerMessageHandler) super.getServerMessageHandler();
}

@Override
public MockServerCommunicationHandler getServerCommunicationHandler() {
return (MockServerCommunicationHandler) super
.getServerCommunicationHandler();
}

/**
* Provide the last token received from the server. <br/>
* We added this to test the change done on CSRF token.
@@ -61,15 +62,7 @@ public class MockApplicationConnection extends ApplicationConnection {
* @see CsrfTokenDisabled
*/
public String getLastCsrfTokenSent() {
return lastCsrfTokenSent;
}

@Override
public void doUidlRequest(String uri, JsonObject payload, boolean retry) {
JsonValue jsonValue = payload.get(ApplicationConstants.CSRF_TOKEN);
lastCsrfTokenSent = jsonValue != null ? jsonValue.toJson() : null;

super.doUidlRequest(uri, payload, retry);
return getServerCommunicationHandler().lastCsrfTokenSent;
}

}

+ 36
- 0
uitest/src/com/vaadin/tests/widgetset/client/MockServerCommunicationHandler.java View File

@@ -0,0 +1,36 @@
/*
* 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.tests.widgetset.client;

import com.vaadin.client.communication.ServerCommunicationHandler;
import com.vaadin.shared.ApplicationConstants;

import elemental.json.JsonObject;
import elemental.json.JsonValue;

public class MockServerCommunicationHandler extends ServerCommunicationHandler {

// The last token sent to the server.
String lastCsrfTokenSent;

@Override
public void doUidlRequest(String uri, JsonObject payload, boolean retry) {
JsonValue jsonValue = payload.get(ApplicationConstants.CSRF_TOKEN);
lastCsrfTokenSent = jsonValue != null ? jsonValue.toJson() : null;

super.doUidlRequest(uri, payload, retry);
}
}

Loading…
Cancel
Save