* Added SessionExpiredHandler which any RequestHandler can implement to customize session expired handling * The issue can be tested by creating a non-serializable UI (e.g. new Object() in a field) and restarting the server. Change-Id: I3eb6bc56298e025bcde088af53ea656fb44e897btags/7.1.0.beta1
import com.vaadin.ui.UI; | import com.vaadin.ui.UI; | ||||
/** | /** | ||||
* Handler for producing a response to non-UIDL requests. Handlers can be added | |||||
* to service sessions using | |||||
* {@link VaadinSession#addRequestHandler(RequestHandler)} | |||||
* Handler for producing a response to HTTP requests. Handlers can be either | |||||
* added on a {@link VaadinService service} level, common for all users, or on a | |||||
* {@link VaadinSession session} level for only a single user. | |||||
*/ | */ | ||||
public interface RequestHandler extends Serializable { | public interface RequestHandler extends Serializable { | ||||
/** | /** | ||||
* Handles a non-UIDL request. If a response is written, this method should | |||||
* return <code>true</code> to indicate that no more request handlers should | |||||
* be invoked for the request. | |||||
* Called when a request needs to be handled. If a response is written, this | |||||
* method should return <code>true</code> to indicate that no more request | |||||
* handlers should be invoked for the request. | |||||
* <p> | * <p> | ||||
* Note that request handlers by default do not lock the session. If you are | * Note that request handlers by default do not lock the session. If you are | ||||
* using VaadinSession or anything inside the VaadinSession you must ensure | * using VaadinSession or anything inside the VaadinSession you must ensure | ||||
* the session is locked. This can be done by extending | * the session is locked. This can be done by extending | ||||
* {@link SynchronizedRequestHandler} or by using | * {@link SynchronizedRequestHandler} or by using | ||||
* {@link VaadinSession#runSafelyInContext(Runnable)} or | |||||
* {@link UI#runSafelyInContext(Runnable)}. | |||||
* {@link VaadinSession#runSafely(Runnable)} or | |||||
* {@link UI#runSafely(Runnable)}. | |||||
* </p> | * </p> | ||||
* | * | ||||
* @param session | * @param session |
/* | |||||
* Copyright 2000-2013 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.server; | |||||
import java.io.IOException; | |||||
/** | |||||
* A specialized RequestHandler which is capable of sending session expiration | |||||
* messages to the user. | |||||
* | |||||
* @since 7.1 | |||||
* @author Vaadin Ltd | |||||
*/ | |||||
public interface SessionExpiredHandler extends RequestHandler { | |||||
/** | |||||
* Called when the a session expiration has occured and a notification needs | |||||
* to be sent to the user. If a response is written, this method should | |||||
* return <code>true</code> to indicate that no more | |||||
* {@link SessionExpiredHandler} handlers should be invoked for the request. | |||||
* | |||||
* @param request | |||||
* The request to handle | |||||
* @param response | |||||
* The response object to which a response can be written. | |||||
* @return true if a response has been written and no further request | |||||
* handlers should be called, otherwise false | |||||
* @throws IOException | |||||
* If an IO error occurred | |||||
* @since 7.1 | |||||
*/ | |||||
boolean handleSessionExpired(VaadinRequest request, VaadinResponse response) | |||||
throws IOException; | |||||
} |
/** | /** | ||||
* Handles the incoming request and writes the response into the response | * Handles the incoming request and writes the response into the response | ||||
* object. Uses {@link #getRequestHandlers()} for handling the request. | * object. Uses {@link #getRequestHandlers()} for handling the request. | ||||
* <p> | |||||
* If a session expiration is detected during request handling then each | |||||
* {@link RequestHandler request handler} has an opportunity to handle the | |||||
* expiration event if it implements {@link SessionExpiredHandler}. If no | |||||
* request handler handles session expiration a default expiration message | |||||
* will be written. | |||||
* </p> | |||||
* | * | ||||
* @param request | * @param request | ||||
* The incoming request | * The incoming request | ||||
* Thrown if there was any problem handling the expiration of | * Thrown if there was any problem handling the expiration of | ||||
* the session | * the session | ||||
*/ | */ | ||||
protected abstract void handleSessionExpired(VaadinRequest request, | |||||
VaadinResponse response) throws ServiceException; | |||||
protected void handleSessionExpired(VaadinRequest request, | |||||
VaadinResponse response) throws ServiceException { | |||||
SystemMessages systemMessages = getSystemMessages( | |||||
ServletPortletHelper.findLocale(null, null, request), request); | |||||
for (RequestHandler handler : getRequestHandlers()) { | |||||
if (handler instanceof SessionExpiredHandler) { | |||||
try { | |||||
if (((SessionExpiredHandler) handler).handleSessionExpired( | |||||
request, response)) { | |||||
return; | |||||
} | |||||
} catch (IOException e) { | |||||
throw new ServiceException( | |||||
"Handling of session expired failed", e); | |||||
} | |||||
} | |||||
} | |||||
// No request handlers handled the request. Write a normal HTTP response | |||||
try { | |||||
// If there is a URL, try to redirect there | |||||
String sessionExpiredURL = systemMessages.getSessionExpiredURL(); | |||||
if (sessionExpiredURL != null | |||||
&& (response instanceof VaadinServletResponse)) { | |||||
((VaadinServletResponse) response) | |||||
.sendRedirect(sessionExpiredURL); | |||||
} else { | |||||
/* | |||||
* Session expired as a result of a standard http request and we | |||||
* have nowhere to redirect. Reloading would likely cause an | |||||
* endless loop. This can at least happen if refreshing a | |||||
* resource when the session has expired. | |||||
*/ | |||||
response.sendError(HttpServletResponse.SC_GONE, | |||||
"Session expired"); | |||||
} | |||||
} catch (IOException e) { | |||||
throw new ServiceException(e); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Creates a JSON message which, when sent to client as-is, will cause a | * Creates a JSON message which, when sent to client as-is, will cause a |
package com.vaadin.server; | package com.vaadin.server; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | |||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.net.MalformedURLException; | import java.net.MalformedURLException; | ||||
import java.net.URL; | import java.net.URL; | ||||
import javax.servlet.ServletContext; | import javax.servlet.ServletContext; | ||||
import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
import javax.servlet.http.HttpServletResponse; | |||||
import org.atmosphere.util.Version; | import org.atmosphere.util.Version; | ||||
import com.vaadin.server.communication.PushRequestHandler; | import com.vaadin.server.communication.PushRequestHandler; | ||||
import com.vaadin.server.communication.ServletBootstrapHandler; | import com.vaadin.server.communication.ServletBootstrapHandler; | ||||
import com.vaadin.server.communication.ServletUIInitHandler; | import com.vaadin.server.communication.ServletUIInitHandler; | ||||
import com.vaadin.shared.JsonConstants; | |||||
import com.vaadin.ui.UI; | import com.vaadin.ui.UI; | ||||
public class VaadinServletService extends VaadinService { | public class VaadinServletService extends VaadinService { | ||||
return appId; | return appId; | ||||
} | } | ||||
/* | |||||
* (non-Javadoc) | |||||
* | |||||
* @see | |||||
* com.vaadin.server.VaadinService#handleSessionExpired(com.vaadin.server | |||||
* .VaadinRequest, com.vaadin.server.VaadinResponse) | |||||
*/ | |||||
@Override | |||||
protected void handleSessionExpired(VaadinRequest request, | |||||
VaadinResponse response) throws ServiceException { | |||||
if (!(request instanceof VaadinServletRequest)) { | |||||
throw new ServiceException(new IllegalArgumentException( | |||||
"handleSessionExpired called with a non-VaadinServletRequest: " | |||||
+ request.getClass().getName())); | |||||
} | |||||
VaadinServletRequest servletRequest = (VaadinServletRequest) request; | |||||
VaadinServletResponse servletResponse = (VaadinServletResponse) response; | |||||
try { | |||||
SystemMessages ci = getSystemMessages( | |||||
ServletPortletHelper.findLocale(null, null, request), | |||||
request); | |||||
if (ServletPortletHelper.isUIDLRequest(request)) { | |||||
/* | |||||
* Invalidate session (weird to have session if we're saying | |||||
* that it's expired) | |||||
* | |||||
* Session must be invalidated before criticalNotification as it | |||||
* commits the response. | |||||
*/ | |||||
servletRequest.getSession().invalidate(); | |||||
writeStringResponse( | |||||
response, | |||||
JsonConstants.JSON_CONTENT_TYPE, | |||||
createCriticalNotificationJSON( | |||||
ci.getSessionExpiredCaption(), | |||||
ci.getSessionExpiredMessage(), null, | |||||
ci.getSessionExpiredURL())); | |||||
} else if (ServletPortletHelper.isHeartbeatRequest(request)) { | |||||
response.sendError(HttpServletResponse.SC_GONE, | |||||
"Session expired"); | |||||
} else { | |||||
// 'plain' http req - e.g. browser reload; | |||||
// just go ahead redirect the browser | |||||
String sessionExpiredURL = ci.getSessionExpiredURL(); | |||||
if (sessionExpiredURL != null) { | |||||
servletResponse.sendRedirect(sessionExpiredURL); | |||||
} else { | |||||
/* | |||||
* Session expired as a result of a standard http request | |||||
* and we have nowhere to redirect. Reloading would likely | |||||
* cause an endless loop. This can at least happen if | |||||
* refreshing a resource when the session has expired. | |||||
*/ | |||||
response.sendError(HttpServletResponse.SC_GONE, | |||||
"Session expired"); | |||||
} | |||||
} | |||||
} catch (IOException e) { | |||||
throw new ServiceException(e); | |||||
} | |||||
} | |||||
private static final Logger getLogger() { | private static final Logger getLogger() { | ||||
return Logger.getLogger(VaadinServletService.class.getName()); | return Logger.getLogger(VaadinServletService.class.getName()); | ||||
} | } |
import javax.servlet.http.HttpServletResponse; | import javax.servlet.http.HttpServletResponse; | ||||
import com.vaadin.server.ServletPortletHelper; | import com.vaadin.server.ServletPortletHelper; | ||||
import com.vaadin.server.SessionExpiredHandler; | |||||
import com.vaadin.server.SynchronizedRequestHandler; | import com.vaadin.server.SynchronizedRequestHandler; | ||||
import com.vaadin.server.VaadinRequest; | import com.vaadin.server.VaadinRequest; | ||||
import com.vaadin.server.VaadinResponse; | import com.vaadin.server.VaadinResponse; | ||||
* @author Vaadin Ltd | * @author Vaadin Ltd | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
public class HeartbeatHandler extends SynchronizedRequestHandler { | |||||
public class HeartbeatHandler extends SynchronizedRequestHandler implements | |||||
SessionExpiredHandler { | |||||
/** | /** | ||||
* Handles a heartbeat request for the given session. Reads the GET | * Handles a heartbeat request for the given session. Reads the GET | ||||
return true; | return true; | ||||
} | } | ||||
/* | |||||
* (non-Javadoc) | |||||
* | |||||
* @see | |||||
* com.vaadin.server.SessionExpiredHandler#handleSessionExpired(com.vaadin | |||||
* .server.VaadinRequest, com.vaadin.server.VaadinResponse) | |||||
*/ | |||||
@Override | |||||
public boolean handleSessionExpired(VaadinRequest request, | |||||
VaadinResponse response) throws IOException { | |||||
if (!ServletPortletHelper.isHeartbeatRequest(request)) { | |||||
return false; | |||||
} | |||||
response.sendError(HttpServletResponse.SC_GONE, "Session expired"); | |||||
return true; | |||||
} | |||||
} | } |
import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException; | import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException; | ||||
import com.vaadin.server.ServiceException; | import com.vaadin.server.ServiceException; | ||||
import com.vaadin.server.ServletPortletHelper; | |||||
import com.vaadin.server.SessionExpiredException; | import com.vaadin.server.SessionExpiredException; | ||||
import com.vaadin.server.SystemMessages; | |||||
import com.vaadin.server.VaadinRequest; | import com.vaadin.server.VaadinRequest; | ||||
import com.vaadin.server.VaadinService; | import com.vaadin.server.VaadinService; | ||||
import com.vaadin.server.VaadinServletRequest; | import com.vaadin.server.VaadinServletRequest; | ||||
try { | try { | ||||
session = service.findVaadinSession(vaadinRequest); | session = service.findVaadinSession(vaadinRequest); | ||||
} catch (ServiceException e) { | } catch (ServiceException e) { | ||||
// TODO Auto-generated catch block | |||||
e.printStackTrace(); | |||||
return; | |||||
getLogger().log(Level.SEVERE, | |||||
"Could not get session. This should never happen", e); | |||||
} catch (SessionExpiredException e) { | } catch (SessionExpiredException e) { | ||||
// TODO Auto-generated catch block | |||||
e.printStackTrace(); | |||||
SystemMessages msg = service.getSystemMessages( | |||||
ServletPortletHelper.findLocale(null, null, | |||||
vaadinRequest), vaadinRequest); | |||||
try { | |||||
resource.getResponse() | |||||
.getWriter() | |||||
.write(VaadinService | |||||
.createCriticalNotificationJSON( | |||||
msg.getSessionExpiredCaption(), | |||||
msg.getSessionExpiredMessage(), | |||||
null, msg.getSessionExpiredURL())); | |||||
} catch (IOException e1) { | |||||
getLogger() | |||||
.log(Level.WARNING, | |||||
"Failed to notify client about unavailable session", | |||||
e); | |||||
} | |||||
return; | return; | ||||
} | } | ||||
import com.vaadin.server.RequestHandler; | import com.vaadin.server.RequestHandler; | ||||
import com.vaadin.server.ServiceException; | import com.vaadin.server.ServiceException; | ||||
import com.vaadin.server.ServletPortletHelper; | import com.vaadin.server.ServletPortletHelper; | ||||
import com.vaadin.server.SessionExpiredHandler; | |||||
import com.vaadin.server.VaadinRequest; | import com.vaadin.server.VaadinRequest; | ||||
import com.vaadin.server.VaadinResponse; | import com.vaadin.server.VaadinResponse; | ||||
import com.vaadin.server.VaadinServletRequest; | import com.vaadin.server.VaadinServletRequest; | ||||
* @author Vaadin Ltd | * @author Vaadin Ltd | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
public class PushRequestHandler implements RequestHandler { | |||||
public class PushRequestHandler implements RequestHandler, | |||||
SessionExpiredHandler { | |||||
private AtmosphereFramework atmosphere; | private AtmosphereFramework atmosphere; | ||||
private PushHandler pushHandler; | private PushHandler pushHandler; | ||||
public PushRequestHandler(VaadinServletService service) throws ServiceException { | |||||
public PushRequestHandler(VaadinServletService service) | |||||
throws ServiceException { | |||||
atmosphere = new AtmosphereFramework(); | atmosphere = new AtmosphereFramework(); | ||||
public void destroy() { | public void destroy() { | ||||
atmosphere.destroy(); | atmosphere.destroy(); | ||||
} | } | ||||
/* | |||||
* (non-Javadoc) | |||||
* | |||||
* @see | |||||
* com.vaadin.server.SessionExpiredHandler#handleSessionExpired(com.vaadin | |||||
* .server.VaadinRequest, com.vaadin.server.VaadinResponse) | |||||
*/ | |||||
@Override | |||||
public boolean handleSessionExpired(VaadinRequest request, | |||||
VaadinResponse response) throws IOException { | |||||
// Websockets request must be handled by accepting the websocket | |||||
// connection and then sending session expired so we let | |||||
// PushRequestHandler handle it | |||||
return handleRequest(null, request, response); | |||||
} | |||||
} | } |