diff options
author | Artur Signell <artur@vaadin.com> | 2015-04-25 15:49:35 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2015-07-13 17:19:08 +0300 |
commit | 132ca1e1b68bf813364a5f87e9afa5997804c07c (patch) | |
tree | 4a7320a380ecae83610c2e18431f0f960404f900 /client | |
parent | d93527f25759e3a9aeb80a0c6684657a193e6a9e (diff) | |
download | vaadin-framework-132ca1e1b68bf813364a5f87e9afa5997804c07c.tar.gz vaadin-framework-132ca1e1b68bf813364a5f87e9afa5997804c07c.zip |
Extract CommunicationProblemHandler methods to an interface (#11733)
Change-Id: Id44eed32a91721a67859d8daedefd3c7a17d61dc
Diffstat (limited to 'client')
3 files changed, 346 insertions, 256 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index b3bf626de7..b3bcbbb3ab 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -46,6 +46,7 @@ import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.communication.CommunicationProblemHandler; +import com.vaadin.client.communication.DefaultCommunicationProblemHandler; import com.vaadin.client.communication.Heartbeat; import com.vaadin.client.communication.RpcManager; import com.vaadin.client.communication.ServerCommunicationHandler; @@ -365,7 +366,7 @@ public class ApplicationConnection implements HasHandlers { serverRpcQueue = GWT.create(ServerRpcQueue.class); serverRpcQueue.setConnection(this); communicationProblemHandler = GWT - .create(CommunicationProblemHandler.class); + .create(DefaultCommunicationProblemHandler.class); communicationProblemHandler.setConnection(this); serverMessageHandler = GWT.create(ServerMessageHandler.class); serverMessageHandler.setConnection(this); diff --git a/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java index 49650f7efd..82bd4403b9 100644 --- a/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java +++ b/client/src/com/vaadin/client/communication/CommunicationProblemHandler.java @@ -15,316 +15,157 @@ */ 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; /** - * TODO + * Interface for handling problems which occur during communications with the + * server. + * + * The handler is responsible for handling any problem in XHR, heartbeat and + * push connections in a way it sees fit. The default implementation used is + * {@link DefaultCommunicationProblemHandler}, which considers all problems + * terminal * - * @since + * @since 7.6 * @author Vaadin Ltd */ -public class CommunicationProblemHandler { - - private ApplicationConnection connection; +public interface CommunicationProblemHandler { /** - * Sets the application connection this queue is connected to + * Sets the application connection this handler is connected to. Called + * internally by the framework. * * @param connection - * the application connection this queue is connected to - */ - public void setConnection(ApplicationConnection connection) { - this.connection = connection; - } - - public static Logger getLogger() { - return Logger.getLogger(CommunicationProblemHandler.class.getName()); - } - - /** - * @param payload - * @param communicationProblemEvent - */ - public void xhrException(CommunicationProblemEvent event) { - handleUnrecoverableCommunicationError( - event.getException().getMessage(), event); - - } - - /** - * @param event - * @param retry - */ - 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; - } - - } - - /** - * @since - * @param event - */ - 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); - } - - } - - /** - * @since - * @param 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(); - } - - /** - * @since - */ - private void endRequestAndStopApplication() { - getServerCommunicationHandler().endRequest(); - - // Consider application not running any more and prevent all - // future requests - connection.setApplicationRunning(false); - } - - /** - * @since - * @param event - * @param retry + * the application connection this handler is connected to */ - 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(); - } + void setConnection(ApplicationConnection connection); /** - * @since - * @param event - */ - 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); - } - - } - - private ServerCommunicationHandler getServerCommunicationHandler() { - return connection.getServerCommunicationHandler(); - } - - /** - * Called when a heartbeat request returns a status code other than 200 + * Called when an exception occurs during a {@link Heartbeat} request * * @param request * The heartbeat request - * @param response - * The heartbeat response + * @param exception + * The exception which occurred * @return true if a new heartbeat should be sent, false if no further * heartbeats should be sent */ - 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; - } + boolean heartbeatException(Request request, Throwable exception); /** - * Called when an exception occurs during a heartbeat request + * Called when a heartbeat request returns a status code other than OK (200) * * @param request * The heartbeat request - * @param exception - * The exception which occurred + * @param response + * The heartbeat response * @return true if a new heartbeat should be sent, false if no further * heartbeats should be sent */ - public boolean heartbeatException(Request request, Throwable exception) { - getLogger().severe( - "Exception sending heartbeat: " + exception.getMessage()); - return true; - } + boolean heartbeatInvalidStatusCode(Request request, Response response); /** - * @since - * @param response + * Called when a {@link Heartbeat} request succeeds */ - public void pushError(PushConnection pushConnection) { - /* - * Push error indicates a fatal error we cannot (or should not) recover - * from by reconnecting or similar. - * - * Atmosphere calls onError - * - * 1. if maxReconnectAttemps is reached - * - * 2. if neither the transport nor the fallback transport can be used - * - * (also for server response codes != 200 if reconnectOnServerError is - * set to false but we do not set that to false) - */ - connection.handleCommunicationError("Push connection using " - + pushConnection.getTransportType() + " failed!", -1); - } + void heartbeatOk(); /** - * @since - * @param response + * Called when the push connection to the server is closed. This might + * result in the push connection trying a fallback connection method, trying + * to reconnect to the server or might just be an indication that the + * connection was intentionally closed ("unsubscribe"), + * + * @param pushConnection + * The push connection which was closed */ - public void pushClientTimeout(PushConnection pushConnection) { - connection - .handleCommunicationError( - "Client unexpectedly disconnected. Ensure client timeout is disabled.", - -1); - - } + void pushClosed(PushConnection pushConnection); /** - * @since - * @param resourceUrl + * Called when a client side timeout occurs before a push connection to the + * server completes. + * + * The client side timeout causes a disconnection of the push connection and + * no reconnect will be attempted after this method is called, + * + * @param pushConnection + * The push connection which timed out */ - public void pushScriptLoadError(String resourceUrl) { - connection.handleCommunicationError(resourceUrl - + " could not be loaded. Push will not work.", 0); - - } + void pushClientTimeout(PushConnection pushConnection); /** - * @since + * Called when a fatal error fatal error occurs in the push connection. + * + * The push connection will not try to recover from this situation itself + * and typically the problem handler should not try to do automatic recovery + * either. The cause can be e.g. maximum number of reconnection attempts + * have been reached, neither the selected transport nor the fallback + * transport can be used or similar. + * + * @param pushConnection + * The push connection where the error occurred */ - public void heartbeatOk() { - getLogger().fine("Heartbeat response OK"); - } + void pushError(PushConnection pushConnection); /** - * @since + * Called when the push connection has lost the connection to the server and + * will proceed to try to re-establish the connection + * + * @param pushConnection + * The push connection which will be reconnected */ - public void xhrOk() { + void pushReconnectPending(PushConnection pushConnection); - } + /** + * Called when the push connection to the server has been established. + * + * @param pushConnection + * The push connection which was established + */ + void pushOk(PushConnection pushConnection); /** - * @since - * @param response + * Called when the required push script could not be loaded + * + * @param resourceUrl + * The URL which was used for loading the script */ - public void pushClosed(PushConnection pushConnection) { - // TODO Auto-generated method stub + void pushScriptLoadError(String resourceUrl); - } + /** + * Called when an exception occurs during an XmlHttpRequest request to the + * server. + * + * @param communicationProblemEvent + * An event containing what was being sent to the server and what + * exception occurred + */ + void xhrException(CommunicationProblemEvent communicationProblemEvent); /** - * @since - * @param response + * Called when invalid content (not JSON) was returned from the server as + * the result of an XmlHttpRequest request + * + * @param communicationProblemEvent + * An event containing what was being sent to the server and what + * was returned */ - public void pushReconnectPending(PushConnection pushConnection) { - // TODO Auto-generated method stub + void xhrInvalidContent(CommunicationProblemEvent communicationProblemEvent); - } + /** + * Called when invalid status code (not 200) was returned by the server as + * the result of an XmlHttpRequest. + * + * @param communicationProblemEvent + * An event containing what was being sent to the server and what + * was returned + */ + void xhrInvalidStatusCode(CommunicationProblemEvent problemEvent); /** - * @since + * Called whenever a XmlHttpRequest to the server completes successfully */ - public void pushOk(PushConnection pushConnection) { - // TODO Auto-generated method stub + void xhrOk(); - } } diff --git a/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java new file mode 100644 index 0000000000..8b97b0a4c7 --- /dev/null +++ b/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java @@ -0,0 +1,248 @@ +/* + * 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; + +/** + * 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; + } + + 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); + } + + } + + 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) { + } +} |