// ======================================================================== // Copyright 2007 Mort Bay Consulting Pty. 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 org.mortbay.cometd; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.GenericServlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.cometd.Bayeux; import org.cometd.DataFilter; import org.cometd.Message; import org.mortbay.cometd.filter.JSONDataFilter; import org.mortbay.log.Log; import org.mortbay.util.ajax.JSON; /** * Cometd Filter Servlet implementing the {@link AbstractBayeux} protocol. * * The Servlet can be initialized with a json file mapping channels to * {@link DataFilter} definitions. The servlet init parameter "filters" should * point to a webapplication resource containing a JSON array of filter * definitions. For example: * *
 *  [
 *    {
 *      "channels": "/**",
 *      "class"   : "org.mortbay.cometd.filter.NoMarkupFilter",
 *      "init"    : {}
 *    }
 *  ]
 * 
* * The following init parameters can be used to configure the servlet: *
*
timeout
*
The server side poll timeout in milliseconds (default 250000). This is * how long the server will hold a reconnect request before responding.
* *
interval
*
The client side poll timeout in milliseconds (default 0). How long a * client will wait between reconnects
* *
maxInterval
*
The max client side poll timeout in milliseconds (default 30000). A * client will be removed if a connection is not received in this time. * *
maxLazyLatency
*
The max time in ms(default 0) that a client with lazy messages will wait before * sending a response. If 0, then the client will wait until the next timeout or * non-lazy message. * *
multiFrameInterval
*
the client side poll timeout if multiple connections are detected from * the same browser (default 1500).
* *
JSONCommented
*
If "true" then the server will accept JSON wrapped in a comment and will * generate JSON wrapped in a comment. This is a defence against Ajax Hijacking. *
* *
filters
*
the location of a JSON file describing {@link DataFilter} instances to be * installed
* *
requestAvailable
*
If true, the current request is made available via the * {@link AbstractBayeux#getCurrentRequest()} method
* *
loglevel
*
0=none, 1=info, 2=debug
* *
refsThreshold
*
The number of message refs at which the a single message response will be * cached instead of being generated for every client delivered to. Done to * optimize a single message being sent to multiple clients.
*
* * @author gregw * @author aabeling: added JSONP transport * * @see {@link AbstractBayeux} * @see {@link ChannelId} */ public abstract class AbstractCometdServlet extends GenericServlet { public static final String CLIENT_ATTR="org.mortbay.cometd.client"; public static final String TRANSPORT_ATTR="org.mortbay.cometd.transport"; public static final String MESSAGE_PARAM="message"; public static final String TUNNEL_INIT_PARAM="tunnelInit"; public static final String HTTP_CLIENT_ID="BAYEUX_HTTP_CLIENT"; public final static String BROWSER_ID="BAYEUX_BROWSER"; protected AbstractBayeux _bayeux; public final static int __DEFAULT_REFS_THRESHOLD=0; protected int _refsThreshold=__DEFAULT_REFS_THRESHOLD; public AbstractBayeux getBayeux() { return _bayeux; } protected abstract AbstractBayeux newBayeux(); @Override public void init() throws ServletException { synchronized(AbstractCometdServlet.class) { _bayeux=(AbstractBayeux)getServletContext().getAttribute(Bayeux.ATTRIBUTE); if (_bayeux == null) { _bayeux=newBayeux(); } } synchronized(_bayeux) { boolean was_initialized=_bayeux.isInitialized(); _bayeux.initialize(getServletContext()); if (!was_initialized) { String filters=getInitParameter("filters"); if (filters != null) { try { InputStream is=getServletContext().getResourceAsStream(filters); if (is == null) throw new FileNotFoundException(filters); Object[] objects=(Object[])JSON.parse(new InputStreamReader(getServletContext().getResourceAsStream(filters),"utf-8")); for (int i=0; objects != null && i < objects.length; i++) { Map filter_def=(Map)objects[i]; String fc=(String)filter_def.get("class"); if (fc != null) Log.warn(filters + " file uses deprecated \"class\" name. Use \"filter\" instead"); else fc=(String)filter_def.get("filter"); Class c=Thread.currentThread().getContextClassLoader().loadClass(fc); DataFilter filter=(DataFilter)c.newInstance(); if (filter instanceof JSONDataFilter) ((JSONDataFilter)filter).init(filter_def.get("init")); _bayeux.getChannel((String)filter_def.get("channels"),true).addDataFilter(filter); } } catch(Exception e) { getServletContext().log("Could not parse: " + filters,e); throw new ServletException(e); } } String timeout=getInitParameter("timeout"); if (timeout != null) _bayeux.setTimeout(Long.parseLong(timeout)); String maxInterval=getInitParameter("maxInterval"); if (maxInterval != null) _bayeux.setMaxInterval(Long.parseLong(maxInterval)); String commentedJSON=getInitParameter("JSONCommented"); _bayeux.setJSONCommented(commentedJSON != null && Boolean.parseBoolean(commentedJSON)); String l=getInitParameter("logLevel"); if (l != null && l.length() > 0) _bayeux.setLogLevel(Integer.parseInt(l)); String interval=getInitParameter("interval"); if (interval != null) _bayeux.setInterval(Long.parseLong(interval)); String maxLazy=getInitParameter("maxLazyLatency"); if (maxLazy != null) _bayeux.setMaxLazyLatency(Integer.parseInt(maxLazy)); String mfInterval=getInitParameter("multiFrameInterval"); if (mfInterval != null) _bayeux.setMultiFrameInterval(Integer.parseInt(mfInterval)); String requestAvailable=getInitParameter("requestAvailable"); _bayeux.setRequestAvailable(requestAvailable != null && Boolean.parseBoolean(requestAvailable)); String async=getInitParameter("asyncDeliver"); if (async != null) getServletContext().log("asyncDeliver no longer supported"); String refsThreshold=getInitParameter("refsThreshold"); if (refsThreshold != null) _refsThreshold=Integer.parseInt(refsThreshold); _bayeux.generateAdvice(); if (_bayeux.isLogInfo()) { getServletContext().log("timeout=" + timeout); getServletContext().log("interval=" + interval); getServletContext().log("maxInterval=" + maxInterval); getServletContext().log("multiFrameInterval=" + mfInterval); getServletContext().log("filters=" + filters); getServletContext().log("refsThreshold=" + refsThreshold); } } } getServletContext().setAttribute(Bayeux.ATTRIBUTE,_bayeux); } protected abstract void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException; @Override public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException { HttpServletRequest request=(HttpServletRequest)req; HttpServletResponse response=(HttpServletResponse)resp; if (_bayeux.isRequestAvailable()) _bayeux.setCurrentRequest(request); try { service(request,response); } finally { if (_bayeux.isRequestAvailable()) _bayeux.setCurrentRequest(null); } } protected String findBrowserId(HttpServletRequest request) { Cookie[] cookies=request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (BROWSER_ID.equals(cookie.getName())) return cookie.getValue(); } } return null; } protected String setBrowserId(HttpServletRequest request, HttpServletResponse response) { String browser_id=Long.toHexString(request.getRemotePort()) + Long.toString(_bayeux.getRandom(),36) + Long.toString(System.currentTimeMillis(),36) + Long.toString(request.getRemotePort(),36); Cookie cookie=new Cookie(BROWSER_ID,browser_id); cookie.setPath("/"); cookie.setMaxAge(-1); response.addCookie(cookie); return browser_id; } private static Message[] __EMPTY_BATCH=new Message[0]; protected Message[] getMessages(HttpServletRequest request) throws IOException { String fodder=null; try { // Get message batches either as JSON body or as message parameters if (request.getContentType() != null && !request.getContentType().startsWith("application/x-www-form-urlencoded")) { return _bayeux.parse(request.getReader()); } String[] batches=request.getParameterValues(MESSAGE_PARAM); if (batches == null || batches.length == 0) return __EMPTY_BATCH; if (batches.length == 0) { fodder=batches[0]; return _bayeux.parse(fodder); } List messages=new ArrayList(); for (int i=0; i < batches.length; i++) { if (batches[i] == null) continue; fodder=batches[i]; _bayeux.parseTo(fodder,messages); } return messages.toArray(new Message[messages.size()]); } catch(IOException e) { throw e; } catch(Exception e) { throw new Error(fodder,e); } } }