From: Artur Signell Date: Sat, 25 Apr 2015 12:58:27 +0000 (+0300) Subject: Merge communication problem handlers (#11733,#17075) X-Git-Tag: 7.6.0.alpha5~16^2~29 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d193814bb5dd920bc402997c41cbd34771d09b51;p=vaadin-framework.git Merge communication problem handlers (#11733,#17075) Change-Id: I91d3d73cb10c10a9306b67e6d99e4405f56bc275 --- diff --git a/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java index 3d3921ee10..d47846060e 100644 --- a/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java +++ b/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java @@ -51,10 +51,8 @@ public interface CommunicationProblemHandler { * The heartbeat request * @param exception * The exception which occurred - * @return true if a new heartbeat should be sent, false if no further - * heartbeats should be sent */ - boolean heartbeatException(Request request, Throwable exception); + void heartbeatException(Request request, Throwable exception); /** * Called when a heartbeat request returns a status code other than OK (200) @@ -63,10 +61,8 @@ public interface CommunicationProblemHandler { * The heartbeat request * @param response * The heartbeat response - * @return true if a new heartbeat should be sent, false if no further - * heartbeats should be sent */ - boolean heartbeatInvalidStatusCode(Request request, Response response); + void heartbeatInvalidStatusCode(Request request, Response response); /** * Called when a {@link Heartbeat} request succeeds diff --git a/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java deleted file mode 100644 index c2f25cfa89..0000000000 --- a/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * 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.logging.Logger; - -import com.google.gwt.http.client.Request; -import com.google.gwt.http.client.Response; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; -import com.google.gwt.user.client.Timer; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.WidgetUtil; - -import elemental.json.JsonObject; - -/** - * Default implementation of the communication problem handler. - * - * Implements error handling by assuming all problems are terminal and simply - * showing a notification to the user - * - * @since 7.6 - * @author Vaadin Ltd - */ -public class DefaultCommunicationProblemHandler implements - CommunicationProblemHandler { - - private ApplicationConnection connection; - - @Override - public void setConnection(ApplicationConnection connection) { - this.connection = connection; - } - - protected ApplicationConnection getConnection() { - return connection; - } - - public static Logger getLogger() { - return Logger.getLogger(DefaultCommunicationProblemHandler.class - .getName()); - } - - @Override - public void xhrException(CommunicationProblemEvent event) { - handleUnrecoverableCommunicationError( - event.getException().getMessage(), event); - - } - - @Override - public void xhrInvalidStatusCode(final CommunicationProblemEvent event) { - Response response = event.getResponse(); - int statusCode = response.getStatusCode(); - if (statusCode == 0) { - handleNoConnection(event); - } else if (statusCode == 401) { - handleAuthorizationFailed(event); - } else if (statusCode == 503) { - handleServiceUnavailable(event); - } else if ((statusCode / 100) == 4) { - // Handle all 4xx errors the same way as (they are - // all permanent errors) - String msg = "UIDL could not be read from server." - + " Check servlets mappings. Error code: " + statusCode; - handleUnrecoverableCommunicationError(msg, event); - } else if ((statusCode / 100) == 5) { - // Something's wrong on the server, there's nothing the - // client can do except maybe try again. - String msg = "Server error. Error code: " + statusCode; - handleUnrecoverableCommunicationError(msg, event); - return; - } - - } - - private void handleServiceUnavailable(final CommunicationProblemEvent event) { - /* - * We'll assume msec instead of the usual seconds. If there's no - * Retry-After header, handle the error like a 500, as per RFC 2616 - * section 10.5.4. - */ - String delay = event.getResponse().getHeader("Retry-After"); - if (delay != null) { - getLogger().warning("503, retrying in " + delay + "msec"); - (new Timer() { - @Override - public void run() { - // send does not call startRequest so we do - // not call endRequest before it - getServerCommunicationHandler().send(event.getPayload()); - } - }).schedule(Integer.parseInt(delay)); - return; - } else { - String msg = "Server error. Error code: " - + event.getResponse().getStatusCode(); - handleUnrecoverableCommunicationError(msg, event); - } - - } - - private void handleAuthorizationFailed(CommunicationProblemEvent event) { - /* - * Authorization has failed (401). Could be that the session has timed - * out and the container is redirecting to a login page. - */ - connection.showAuthenticationError(""); - endRequestAndStopApplication(); - } - - private void endRequestAndStopApplication() { - getServerCommunicationHandler().endRequest(); - - // Consider application not running any more and prevent all - // future requests - connection.setApplicationRunning(false); - } - - private void handleNoConnection(final CommunicationProblemEvent event) { - handleUnrecoverableCommunicationError( - "Invalid status code 0 (server down?)", event); - - } - - private void handleUnrecoverableCommunicationError(String details, - CommunicationProblemEvent event) { - Response response = event.getResponse(); - int statusCode = -1; - if (response != null) { - statusCode = response.getStatusCode(); - } - connection.handleCommunicationError(details, statusCode); - - endRequestAndStopApplication(); - } - - @Override - public void xhrInvalidContent(CommunicationProblemEvent event) { - String responseText = event.getResponse().getText(); - /* - * A servlet filter or equivalent may have intercepted the request and - * served non-UIDL content (for instance, a login page if the session - * has expired.) If the response contains a magic substring, do a - * synchronous refresh. See #8241. - */ - MatchResult refreshToken = RegExp.compile( - ApplicationConnection.UIDL_REFRESH_TOKEN - + "(:\\s*(.*?))?(\\s|$)").exec(responseText); - if (refreshToken != null) { - WidgetUtil.redirect(refreshToken.getGroup(2)); - } else { - handleUnrecoverableCommunicationError( - "Invalid JSON response from server: " + responseText, event); - } - - } - - @Override - public void pushInvalidContent(PushConnection pushConnection, String message) { - // Do nothing for now. Should likely do the same as xhrInvalidContent - } - - private ServerCommunicationHandler getServerCommunicationHandler() { - return connection.getServerCommunicationHandler(); - } - - @Override - public boolean heartbeatInvalidStatusCode(Request request, Response response) { - int status = response.getStatusCode(); - int interval = connection.getHeartbeat().getInterval(); - if (status == 0) { - getLogger().warning( - "Failed sending heartbeat, server is unreachable, retrying in " - + interval + "secs."); - } else if (status == Response.SC_GONE) { - // FIXME Stop application? - connection.showSessionExpiredError(null); - // If session is expired break the loop - return false; - } else if (status >= 500) { - getLogger().warning( - "Failed sending heartbeat, see server logs, retrying in " - + interval + "secs."); - } else { - getLogger() - .warning( - "Failed sending heartbeat to server. Error code: " - + status); - } - - return true; - } - - @Override - public boolean heartbeatException(Request request, Throwable exception) { - getLogger().severe( - "Exception sending heartbeat: " + exception.getMessage()); - return true; - } - - @Override - public void pushError(PushConnection pushConnection) { - connection.handleCommunicationError("Push connection using " - + pushConnection.getTransportType() + " failed!", -1); - } - - @Override - public void pushClientTimeout(PushConnection pushConnection) { - connection - .handleCommunicationError( - "Client unexpectedly disconnected. Ensure client timeout is disabled.", - -1); - - } - - @Override - public void pushScriptLoadError(String resourceUrl) { - connection.handleCommunicationError(resourceUrl - + " could not be loaded. Push will not work.", 0); - - } - - @Override - public void heartbeatOk() { - getLogger().fine("Heartbeat response OK"); - } - - @Override - public void xhrOk() { - - } - - @Override - public void pushClosed(PushConnection pushConnection) { - } - - @Override - public void pushReconnectPending(PushConnection pushConnection) { - } - - @Override - public void pushOk(PushConnection pushConnection) { - } - - @Override - public void pushNotConnected(JsonObject payload) { - } -} diff --git a/client/src/com/vaadin/client/communication/Heartbeat.java b/client/src/com/vaadin/client/communication/Heartbeat.java index eb39622a1a..3b6c9dce6d 100644 --- a/client/src/com/vaadin/client/communication/Heartbeat.java +++ b/client/src/com/vaadin/client/communication/Heartbeat.java @@ -95,28 +95,25 @@ public class Heartbeat { public void onResponseReceived(Request request, Response response) { int status = response.getStatusCode(); - boolean reschedule = true; if (status == Response.SC_OK) { connection.getCommunicationProblemHandler().heartbeatOk(); } else { - reschedule = connection.getCommunicationProblemHandler() + // Handler should stop the application if heartbeat should + // no longer be sent + connection.getCommunicationProblemHandler() .heartbeatInvalidStatusCode(request, response); } - if (reschedule) { - schedule(); - } + schedule(); } @Override public void onError(Request request, Throwable exception) { - boolean reschedule = connection - .getCommunicationProblemHandler().heartbeatException( - request, exception); - - if (reschedule) { - schedule(); - } + // Handler should stop the application if heartbeat should no + // longer be sent + connection.getCommunicationProblemHandler().heartbeatException( + request, exception); + schedule(); } }; diff --git a/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java index efb583ca57..558cce0252 100644 --- a/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java +++ b/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java @@ -15,69 +15,107 @@ */ package com.vaadin.client.communication; +import java.util.logging.Logger; + import com.google.gwt.core.shared.GWT; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.Response; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.WidgetUtil; import elemental.json.JsonObject; -//FIXME This is just a test and should be merged with DCPH -public class ReconnectingCommunicationProblemHandler extends - DefaultCommunicationProblemHandler { +/** + * Default implementation of the communication problem handler. + *

+ * Handles temporary errors by showing a reconnect dialog to the user while + * trying to re-establish the connection to the server and re-send the pending + * message. + *

+ * Handles permanent errors by showing a critical system notification to the + * user + * + * @since 7.6 + * @author Vaadin Ltd + */ +public class ReconnectingCommunicationProblemHandler implements + CommunicationProblemHandler { + private ApplicationConnection connection; + private ReconnectDialog reconnectDialog = GWT.create(ReconnectDialog.class); + private int reconnectAttempt = 0; + private Type reconnectionCause = null; private enum Type { HEARTBEAT, MESSAGE } - ReconnectDialog reconnectDialog = GWT.create(ReconnectDialog.class); - int reconnectAttempt = 0; - private Type reconnectionCause = null; + @Override + public void setConnection(ApplicationConnection connection) { + this.connection = connection; + }; + + private static Logger getLogger() { + return Logger.getLogger(ReconnectingCommunicationProblemHandler.class + .getName()); + } + + /** + * Returns the connection this handler is connected to + * + * @return the connection for this handler + */ + protected ApplicationConnection getConnection() { + return connection; + } @Override public void xhrException(CommunicationProblemEvent event) { getLogger().warning("xhrException"); - handleTemporaryError(Type.MESSAGE, event.getPayload()); + handleRecoverableError(Type.MESSAGE, event.getPayload()); } @Override - public boolean heartbeatException(Request request, Throwable exception) { - handleTemporaryError(Type.HEARTBEAT, null); - return true; + public void heartbeatException(Request request, Throwable exception) { + getLogger().severe("Heartbeat exception: " + exception.getMessage()); + handleRecoverableError(Type.HEARTBEAT, null); } @Override - public boolean heartbeatInvalidStatusCode(Request request, Response response) { + public void heartbeatInvalidStatusCode(Request request, Response response) { + int statusCode = response.getStatusCode(); + getLogger().warning("Heartbeat request returned " + statusCode); + if (response.getStatusCode() == Response.SC_GONE) { // Session expired - resolveTemporaryError(Type.HEARTBEAT, false); - return super.heartbeatInvalidStatusCode(request, response); + getConnection().showSessionExpiredError(null); + stopApplication(); + } else { + handleRecoverableError(Type.HEARTBEAT, null); } - - handleTemporaryError(Type.HEARTBEAT, null); - return true; } @Override public void heartbeatOk() { + getLogger().warning("heartbeatOk"); resolveTemporaryError(Type.HEARTBEAT, true); } - private void handleTemporaryError(Type type, final JsonObject payload) { + protected void handleRecoverableError(Type type, final JsonObject payload) { getLogger().warning("handleTemporaryError(" + type + ")"); reconnectAttempt++; reconnectionCause = type; if (!reconnectDialog.isAttached()) { - // FIXME reconnectDialog.setStyleName("active", true); reconnectDialog.setOwner(getConnection().getUIConnector() .getWidget()); reconnectDialog.setPopupPositionAndShow(new PositionCallback() { @Override public void setPosition(int offsetWidth, int offsetHeight) { - // FIXME reconnectDialog.setPopupPosition(0, 0); } }); @@ -87,10 +125,8 @@ public class ReconnectingCommunicationProblemHandler extends } if (reconnectAttempt >= getMaxReconnectAttempts()) { - // FIXME Remove reconnectDialog.setText("Server connection lost. Gave up after " + reconnectAttempt + " attempts."); - // FIXME reconnectDialog.setStyleName("active", false); getConnection().setApplicationRunning(false); @@ -102,7 +138,6 @@ public class ReconnectingCommunicationProblemHandler extends // Here and not in timer to avoid TB for getting in between if (payload != null) { - // FIXME: Not like this getConnection().getServerCommunicationHandler().startRequest(); } @@ -147,16 +182,86 @@ public class ReconnectingCommunicationProblemHandler extends @Override public void xhrInvalidContent(CommunicationProblemEvent event) { getLogger().warning("xhrInvalidContent"); - super.xhrInvalidContent(event); - }; + String responseText = event.getResponse().getText(); + /* + * A servlet filter or equivalent may have intercepted the request and + * served non-UIDL content (for instance, a login page if the session + * has expired.) If the response contains a magic substring, do a + * synchronous refresh. See #8241. + */ + MatchResult refreshToken = RegExp.compile( + ApplicationConnection.UIDL_REFRESH_TOKEN + + "(:\\s*(.*?))?(\\s|$)").exec(responseText); + if (refreshToken != null) { + WidgetUtil.redirect(refreshToken.getGroup(2)); + } else { + handleUnrecoverableCommunicationError( + "Invalid JSON response from server: " + responseText, event); + } + + } + + @Override + public void pushInvalidContent(PushConnection pushConnection, String message) { + // Do nothing for now. Should likely do the same as xhrInvalidContent + } @Override public void xhrInvalidStatusCode(CommunicationProblemEvent event) { - getLogger().info( - "Server returned " + event.getResponse().getStatusCode() - + " for xhr request"); getLogger().warning("xhrInvalidStatusCode"); - handleTemporaryError(Type.MESSAGE, event.getPayload()); + Response response = event.getResponse(); + int statusCode = response.getStatusCode(); + getLogger().warning("Server returned " + statusCode + " for xhr"); + + if (statusCode == 401) { + // Authentication/authorization failed, no need to re-try + handleUnauthorized(event); + return; + } else { + // 404, 408 and other 4xx codes CAN be temporary when you have a + // proxy between the client and the server and e.g. restart the + // server + // 5xx codes may or may not be temporary + handleRecoverableError(Type.MESSAGE, event.getPayload()); + } + } + + protected void handleUnauthorized(CommunicationProblemEvent event) { + /* + * Authorization has failed (401). Could be that the session has timed + * out. + */ + connection.showAuthenticationError(""); + endRequestAndStopApplication(); + } + + private void endRequestAndStopApplication() { + connection.getServerCommunicationHandler().endRequest(); + + stopApplication(); + } + + private void stopApplication() { + // Consider application not running any more and prevent all + // future requests + connection.setApplicationRunning(false); + } + + /** + * @since + * @param event + */ + private void handleUnrecoverableCommunicationError(String details, + CommunicationProblemEvent event) { + Response response = event.getResponse(); + int statusCode = -1; + if (response != null) { + statusCode = response.getStatusCode(); + } + connection.handleCommunicationError(details, statusCode); + + endRequestAndStopApplication(); + } @Override @@ -193,13 +298,52 @@ public class ReconnectingCommunicationProblemHandler extends @Override public void pushOk(PushConnection pushConnection) { - super.pushOk(pushConnection); + getLogger().warning("pushOk()"); resolveTemporaryError(Type.MESSAGE, true); } + @Override + public void pushScriptLoadError(String resourceUrl) { + connection.handleCommunicationError(resourceUrl + + " could not be loaded. Push will not work.", 0); + } + @Override public void pushNotConnected(JsonObject payload) { - super.pushNotConnected(payload); - handleTemporaryError(Type.MESSAGE, payload); + getLogger().warning("pushNotConnected()"); + handleRecoverableError(Type.MESSAGE, payload); + } + + @Override + public void pushReconnectPending(PushConnection pushConnection) { + getLogger().warning( + "pushReconnectPending(" + pushConnection.getTransportType() + + ")"); + getLogger().info("Reopening push connection"); + } + + @Override + public void pushError(PushConnection pushConnection) { + getLogger().warning("pushError()"); + connection.handleCommunicationError("Push connection using " + + pushConnection.getTransportType() + " failed!", -1); } + + @Override + public void pushClientTimeout(PushConnection pushConnection) { + getLogger().warning("pushClientTimeout()"); + // TODO Reconnect, allowing client timeout to be set + // https://dev.vaadin.com/ticket/18429 + connection + .handleCommunicationError( + "Client unexpectedly disconnected. Ensure client timeout is disabled.", + -1); + } + + @Override + public void pushClosed(PushConnection pushConnection) { + getLogger().warning("pushClosed()"); + getLogger().info("Push connection closed"); + } + }