From: Johannes Dahlström Date: Wed, 22 Aug 2012 10:59:26 +0000 (+0300) Subject: Sending and receiving heartbeat requests (#9265) X-Git-Tag: 7.0.0.beta1~220^2~14 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7a8fd7b5c0223ef6820b4e5cdbe1c05578b17d4a;p=vaadin-framework.git Sending and receiving heartbeat requests (#9265) --- diff --git a/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index a8852fe9fa..d2deb70190 100644 --- a/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -230,6 +230,8 @@ public class ApplicationConnection { rootConnector.init(cnf.getRootPanelId(), this); showLoadingIndicator(); + + scheduleHeartbeat(); } /** @@ -2520,4 +2522,72 @@ public class ApplicationConnection { public SerializerMap getSerializerMap() { return serializerMap; } + + /** + * Schedules a heartbeat request. + * + * @see #sendHeartbeat() + */ + private void scheduleHeartbeat() { + final int interval = 1000 * getConfiguration().getHeartbeatInterval(); + if (interval > 0) { + new Timer() { + @Override + public void run() { + sendHeartbeat(); + } + }.schedule(interval); + } + } + + /** + * Sends a heartbeat request to the server. + *

+ * Heartbeat requests are used to inform the server that the client-side is + * still alive. If the client page is closed or the connection lost, the + * server will eventually close the inactive Root. + *

+ * TODO: Improved error handling, like in doUidlRequest(). + * + * @see #scheduleHeartbeat() + * @see com.vaadin.ui.Root#heartbeat() + */ + private void sendHeartbeat() { + final String uri = addGetParameters( + translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX + + ApplicationConstants.HEARTBEAT_REQUEST_PATH), + ApplicationConstants.ROOT_ID_PARAMETER + "=" + + getConfiguration().getRootId()); + + final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); + + final RequestCallback callback = new RequestCallback() { + + @Override + public void onResponseReceived(Request request, Response response) { + int status = response.getStatusCode(); + if (status == Response.SC_OK) { + // TODO Permit retry in some error situations + scheduleHeartbeat(); + } else { + VConsole.error("Heartbeat request failed with status code " + + status); + } + } + + @Override + public void onError(Request request, Throwable exception) { + VConsole.error("Heartbeat request resulted in exception"); + VConsole.error(exception); + } + }; + + rb.setCallback(callback); + + try { + rb.send(); + } catch (RequestException re) { + callback.onError(null, re); + } + } } diff --git a/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java index 0a0e0e5082..39702e6ba0 100644 --- a/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java +++ b/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -23,16 +23,10 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; -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.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.History; -import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; @@ -90,14 +84,6 @@ public class RootConnector extends AbstractComponentContainerConnector com.google.gwt.user.client.Window.setTitle(title); } }); - final int heartbeatInterval = getState().getHeartbeatInterval(); - new Timer() { - @Override - public void run() { - sendHeartbeat(); - schedule(heartbeatInterval); - } - }.schedule(heartbeatInterval); } @Override @@ -455,29 +441,4 @@ public class RootConnector extends AbstractComponentContainerConnector } }); } - - private void sendHeartbeat() { - RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, "url"); - - rb.setCallback(new RequestCallback() { - - @Override - public void onResponseReceived(Request request, Response response) { - // TODO Auto-generated method stub - - } - - @Override - public void onError(Request request, Throwable exception) { - // TODO Auto-generated method stub - - } - }); - - try { - rb.send(); - } catch (RequestException re) { - - } - } } diff --git a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index bd39504237..dea42b6b69 100644 --- a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -340,7 +340,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } protected enum RequestType { - FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, CONNECTOR_RESOURCE; + FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, CONNECTOR_RESOURCE, HEARTBEAT; } protected RequestType getRequestType(WrappedPortletRequest wrappedRequest) { @@ -361,6 +361,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet } else if (ServletPortletHelper .isApplicationResourceRequest(wrappedRequest)) { return RequestType.APPLICATION_RESOURCE; + } else if (ServletPortletHelper.isHeartbeatRequest(wrappedRequest)) { + return RequestType.HEARTBEAT; } else if (isDummyRequest(resourceRequest)) { return RequestType.DUMMY; } else { @@ -431,6 +433,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet Application application = null; boolean transactionStarted = false; boolean requestStarted = false; + boolean applicationRunning = false; try { // TODO What about PARAM_UNLOADBURST & redirectToApplication?? @@ -459,6 +462,10 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet applicationManager.serveConnectorResource(wrappedRequest, wrappedResponse); return; + } else if (requestType == RequestType.HEARTBEAT) { + applicationManager.handleHeartbeatRequest(wrappedRequest, + wrappedResponse, application); + return; } /* Update browser information from request */ @@ -477,6 +484,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet /* Start the newly created application */ startApplication(request, application, applicationContext); + applicationRunning = true; /* * Transaction starts. Call transaction listeners. Transaction @@ -585,6 +593,11 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet handleServiceException(wrappedRequest, wrappedResponse, application, e); } finally { + + if (applicationRunning) { + application.closeInactiveRoots(); + } + // Notifies transaction end try { if (transactionStarted) { diff --git a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 062ba6cdf7..0fc8bc09a0 100644 --- a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -248,6 +248,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Application application = null; boolean transactionStarted = false; boolean requestStarted = false; + boolean applicationRunning = false; try { // If a duplicate "close application" URL is received for an @@ -287,6 +288,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements if (requestType == RequestType.CONNECTOR_RESOURCE) { applicationManager.serveConnectorResource(request, response); return; + } else if (requestType == RequestType.HEARTBEAT) { + applicationManager.handleHeartbeatRequest(request, response, + application); + return; } /* Update browser information from the request */ @@ -304,6 +309,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements // Start the application if it's newly created startApplication(request, application, webApplicationContext); + applicationRunning = true; /* * Transaction starts. Call transaction listeners. Transaction end @@ -354,6 +360,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } catch (final Throwable e) { handleServiceException(request, response, application, e); } finally { + + if (applicationRunning) { + application.closeInactiveRoots(); + } + // Notifies transaction end try { if (transactionStarted) { @@ -1121,7 +1132,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements } protected enum RequestType { - FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE; + FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE, HEARTBEAT; } protected RequestType getRequestType(WrappedHttpServletRequest request) { @@ -1137,6 +1148,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements return RequestType.STATIC_FILE; } else if (ServletPortletHelper.isApplicationResourceRequest(request)) { return RequestType.APPLICATION_RESOURCE; + } else if (ServletPortletHelper.isHeartbeatRequest(request)) { + return RequestType.HEARTBEAT; } return RequestType.OTHER; diff --git a/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 00e65382cd..3099903454 100644 --- a/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -87,6 +87,7 @@ import com.vaadin.terminal.Vaadin6Component; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedResponse; +import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext; import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException; @@ -102,7 +103,7 @@ import com.vaadin.ui.Window; * This is a common base class for the server-side implementations of the * communication system between the client code (compiled with GWT into * JavaScript) and the server side components. Its client side counterpart is - * {@link ApplicationConstants}. + * {@link ApplicationConnection}. * * TODO Document better! */ @@ -577,6 +578,9 @@ public abstract class AbstractCommunicationManager implements Serializable { return; } + // Keep the root alive + root.heartbeat(); + // Change all variables based on request parameters if (!handleVariables(request, response, callback, application, root)) { @@ -2634,6 +2638,38 @@ public abstract class AbstractCommunicationManager implements Serializable { } + /** + * Handles a heartbeat request. Heartbeat requests are periodically sent by + * the client-side to inform the server that the root sending the heartbeat + * is still alive (the browser window is open, the connection is up) even + * when there are no UIDL requests for a prolonged period of time. Roots + * that do not receive either heartbeat or UIDL requests are eventually + * removed from the application and garbage collected. + * + * @param request + * @param response + * @param application + * @throws IOException + */ + public void handleHeartbeatRequest(WrappedRequest request, + WrappedResponse response, Application application) + throws IOException { + Root root = null; + try { + int rootId = Integer.parseInt(request + .getParameter(ApplicationConstants.ROOT_ID_PARAMETER)); + root = application.getRootById(rootId); + } catch (NumberFormatException nfe) { + // null-check below handles this as well + } + if (root != null) { + root.heartbeat(); + } else { + response.sendError(HttpServletResponse.SC_NOT_FOUND, + "Root not found"); + } + } + public StreamVariable getStreamVariable(String connectorId, String variableName) { Map map = pidToNameToStreamVariable diff --git a/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java b/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java index 200f9a9103..6911101920 100644 --- a/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java +++ b/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java @@ -9,7 +9,7 @@ import com.vaadin.terminal.WrappedRequest; import com.vaadin.ui.Root; /* - * Copyright 2011 Vaadin Ltd. + * Copyright 2011 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 @@ -129,4 +129,9 @@ class ServletPortletHelper implements Serializable { return hasPathPrefix(request, ApplicationConstants.APP_REQUEST_PATH); } + public static boolean isHeartbeatRequest(WrappedRequest request) { + return hasPathPrefix(request, + ApplicationConstants.HEARTBEAT_REQUEST_PATH); + } + } diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java index 31e633a210..9d1ed7341d 100644 --- a/shared/src/com/vaadin/shared/ApplicationConstants.java +++ b/shared/src/com/vaadin/shared/ApplicationConstants.java @@ -23,6 +23,8 @@ public class ApplicationConstants { public static final String UIDL_REQUEST_PATH = "UIDL/"; + public static final String HEARTBEAT_REQUEST_PATH = "HEARTBEAT/"; + public static final String CONNECTOR_RESOURCE_PREFIX = APP_REQUEST_PATH + "CONNECTOR";