/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.web.analytics.push; import java.io.IOException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.opengamma.util.auth.AuthUtils; /** * Manages long-polling http requests using Jetty continuations. Requests to this servlet block until there * is new data available for the client or until the connection times out. The URL is assumed to be * {@code <servlet path>/{clientId}}. */ public class LongPollingServlet extends HttpServlet { private static final Logger s_logger = LoggerFactory.getLogger(LongPollingServlet.class); /** Key for storing the results as an attribute of the continuation. */ /* package */ static final String RESULTS = "RESULTS"; /** Name of the HTTP query parameter for the client ID */ public static final String CLIENT_ID = "clientId"; /** Serialization version. */ private static final long serialVersionUID = 1L; /** Request parameter name. */ private static final String METHOD = "method"; /** Request parameter value. */ private static final String GET = "GET"; /** * Manages connections for each client. */ @Autowired private LongPollingConnectionManager _connectionManager; @Override public void init(ServletConfig config) throws ServletException { super.init(config); _connectionManager = WebPushServletContextUtils.getLongPollingConnectionManager(config.getServletContext()); } //------------------------------------------------------------------------- // this is a hack to get round a problem with browsers caching GET requests even when they're told not to @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getParameter(METHOD); if (GET.equals(method)) { doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { Continuation continuation = ContinuationSupport.getContinuation(request); if (continuation.isExpired()) { // timeout - just send a blank response and tell the connection that its continuation has timed out String clientId = (String) continuation.getAttribute(CLIENT_ID); if (clientId != null) { // TODO will this always get the correct continuation? _connectionManager.longPollHttpTimeout(clientId, continuation); } return; } String results = (String) request.getAttribute(RESULTS); // if this is the first time the request has been dispatched the results will be null. if the request has been // dispatched before and is being dispatched again after its continuation was resumed the results will be populated if (results == null) { setUpConnection(continuation, request, response); } else { // Send the results s_logger.debug("Writing results to HTTP response {}", results); response.getWriter().write(results); } } private void setUpConnection(Continuation continuation, HttpServletRequest request, HttpServletResponse response) throws IOException { // suspend the request continuation.suspend(); // always suspend before registration String userName = (AuthUtils.isPermissive() ? null : AuthUtils.getUserName()); // get the client ID from the URL and pass the continuation to the connection manager for the next updates String clientId = getClientId(request); boolean connected = (clientId != null) && _connectionManager.longPollHttpConnect(userName, clientId, continuation); if (!connected) { // couldn't get the client ID from the URL or the client ID didn't correspond to a known client // TODO how do I send something other than jetty's standard HTML error page? response.sendError(404, "Problem accessing " + request.getRequestURI() + ". Reason: Unknown client ID " + clientId); continuation.complete(); } continuation.setAttribute(CLIENT_ID, clientId); } /** * Extracts the client ID from the URL. If the URL is {@code http://<host>/<servlet path>/12345} the client ID is 12345. * @param request The request * @return The client ID from {@code request}'s URL or null if it's missing */ /* package */ static String getClientId(HttpServletRequest request) { // this is the portion of the URL after the part used to direct it to this servlet // i.e. if the full URL is http://host/subscription/abcd then suffix=/abcd String suffix = request.getRequestURI().substring(request.getServletPath().length()); String clientId; if (suffix.length() > 1) { clientId = suffix.substring(1); } else { clientId = null; } return clientId; } }