Browse Source

Sending and receiving heartbeat requests (#9265)

tags/7.0.0.beta1
Johannes Dahlström 11 years ago
parent
commit
7a8fd7b5c0

+ 70
- 0
client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java View File



rootConnector.init(cnf.getRootPanelId(), this); rootConnector.init(cnf.getRootPanelId(), this);
showLoadingIndicator(); showLoadingIndicator();

scheduleHeartbeat();
} }


/** /**
public SerializerMap getSerializerMap() { public SerializerMap getSerializerMap() {
return serializerMap; 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.
* <p>
* 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.
* <p>
* <b>TODO</b>: 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);
}
}
} }

+ 0
- 39
client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java View File

import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position; 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.Command;
import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History; 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.Window;
import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
com.google.gwt.user.client.Window.setTitle(title); 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 @Override
} }
}); });
} }

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) {

}
}
} }

+ 14
- 1
server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java View File

} }


protected enum RequestType { 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) { protected RequestType getRequestType(WrappedPortletRequest wrappedRequest) {
} else if (ServletPortletHelper } else if (ServletPortletHelper
.isApplicationResourceRequest(wrappedRequest)) { .isApplicationResourceRequest(wrappedRequest)) {
return RequestType.APPLICATION_RESOURCE; return RequestType.APPLICATION_RESOURCE;
} else if (ServletPortletHelper.isHeartbeatRequest(wrappedRequest)) {
return RequestType.HEARTBEAT;
} else if (isDummyRequest(resourceRequest)) { } else if (isDummyRequest(resourceRequest)) {
return RequestType.DUMMY; return RequestType.DUMMY;
} else { } else {
Application application = null; Application application = null;
boolean transactionStarted = false; boolean transactionStarted = false;
boolean requestStarted = false; boolean requestStarted = false;
boolean applicationRunning = false;


try { try {
// TODO What about PARAM_UNLOADBURST & redirectToApplication?? // TODO What about PARAM_UNLOADBURST & redirectToApplication??
applicationManager.serveConnectorResource(wrappedRequest, applicationManager.serveConnectorResource(wrappedRequest,
wrappedResponse); wrappedResponse);
return; return;
} else if (requestType == RequestType.HEARTBEAT) {
applicationManager.handleHeartbeatRequest(wrappedRequest,
wrappedResponse, application);
return;
} }


/* Update browser information from request */ /* Update browser information from request */


/* Start the newly created application */ /* Start the newly created application */
startApplication(request, application, applicationContext); startApplication(request, application, applicationContext);
applicationRunning = true;


/* /*
* Transaction starts. Call transaction listeners. Transaction * Transaction starts. Call transaction listeners. Transaction
handleServiceException(wrappedRequest, wrappedResponse, handleServiceException(wrappedRequest, wrappedResponse,
application, e); application, e);
} finally { } finally {

if (applicationRunning) {
application.closeInactiveRoots();
}

// Notifies transaction end // Notifies transaction end
try { try {
if (transactionStarted) { if (transactionStarted) {

+ 14
- 1
server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java View File

Application application = null; Application application = null;
boolean transactionStarted = false; boolean transactionStarted = false;
boolean requestStarted = false; boolean requestStarted = false;
boolean applicationRunning = false;


try { try {
// If a duplicate "close application" URL is received for an // If a duplicate "close application" URL is received for an
if (requestType == RequestType.CONNECTOR_RESOURCE) { if (requestType == RequestType.CONNECTOR_RESOURCE) {
applicationManager.serveConnectorResource(request, response); applicationManager.serveConnectorResource(request, response);
return; return;
} else if (requestType == RequestType.HEARTBEAT) {
applicationManager.handleHeartbeatRequest(request, response,
application);
return;
} }


/* Update browser information from the request */ /* Update browser information from the request */


// Start the application if it's newly created // Start the application if it's newly created
startApplication(request, application, webApplicationContext); startApplication(request, application, webApplicationContext);
applicationRunning = true;


/* /*
* Transaction starts. Call transaction listeners. Transaction end * Transaction starts. Call transaction listeners. Transaction end
} catch (final Throwable e) { } catch (final Throwable e) {
handleServiceException(request, response, application, e); handleServiceException(request, response, application, e);
} finally { } finally {

if (applicationRunning) {
application.closeInactiveRoots();
}

// Notifies transaction end // Notifies transaction end
try { try {
if (transactionStarted) { if (transactionStarted) {
} }


protected enum RequestType { 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) { protected RequestType getRequestType(WrappedHttpServletRequest request) {
return RequestType.STATIC_FILE; return RequestType.STATIC_FILE;
} else if (ServletPortletHelper.isApplicationResourceRequest(request)) { } else if (ServletPortletHelper.isApplicationResourceRequest(request)) {
return RequestType.APPLICATION_RESOURCE; return RequestType.APPLICATION_RESOURCE;
} else if (ServletPortletHelper.isHeartbeatRequest(request)) {
return RequestType.HEARTBEAT;
} }
return RequestType.OTHER; return RequestType.OTHER;



+ 37
- 1
server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java View File

import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.VariableOwner;
import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest;
import com.vaadin.terminal.WrappedResponse; 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.BootstrapHandler.BootstrapContext;
import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException; import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException;
* This is a common base class for the server-side implementations of the * This is a common base class for the server-side implementations of the
* communication system between the client code (compiled with GWT into * communication system between the client code (compiled with GWT into
* JavaScript) and the server side components. Its client side counterpart is * JavaScript) and the server side components. Its client side counterpart is
* {@link ApplicationConstants}.
* {@link ApplicationConnection}.
* *
* TODO Document better! * TODO Document better!
*/ */
return; return;
} }


// Keep the root alive
root.heartbeat();

// Change all variables based on request parameters // Change all variables based on request parameters
if (!handleVariables(request, response, callback, application, root)) { if (!handleVariables(request, response, callback, application, root)) {




} }


/**
* 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, public StreamVariable getStreamVariable(String connectorId,
String variableName) { String variableName) {
Map<String, StreamVariable> map = pidToNameToStreamVariable Map<String, StreamVariable> map = pidToNameToStreamVariable

+ 6
- 1
server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java View File

import com.vaadin.ui.Root; 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 * 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 * use this file except in compliance with the License. You may obtain a copy of
return hasPathPrefix(request, ApplicationConstants.APP_REQUEST_PATH); return hasPathPrefix(request, ApplicationConstants.APP_REQUEST_PATH);
} }


public static boolean isHeartbeatRequest(WrappedRequest request) {
return hasPathPrefix(request,
ApplicationConstants.HEARTBEAT_REQUEST_PATH);
}

} }

+ 2
- 0
shared/src/com/vaadin/shared/ApplicationConstants.java View File



public static final String UIDL_REQUEST_PATH = "UIDL/"; 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 public static final String CONNECTOR_RESOURCE_PREFIX = APP_REQUEST_PATH
+ "CONNECTOR"; + "CONNECTOR";



Loading…
Cancel
Save