View Javadoc

    1   // ========================================================================
    2   // Copyright 2007 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  //========================================================================
    14
    15  package org.mortbay.cometd;
    16
    17  import java.io.FileNotFoundException;
    18  import java.io.IOException;
    19  import java.io.InputStream;
    20  import java.io.InputStreamReader;
    21  import java.util.ArrayList;
    22  import java.util.List;
    23  import java.util.Map;
    24
    25  import javax.servlet.GenericServlet;
    26  import javax.servlet.ServletException;
    27  import javax.servlet.ServletRequest;
    28  import javax.servlet.ServletResponse;
    29  import javax.servlet.http.Cookie;
    30  import javax.servlet.http.HttpServlet;
    31  import javax.servlet.http.HttpServletRequest;
    32  import javax.servlet.http.HttpServletResponse;
    33
    34  import org.cometd.Bayeux;
    35  import org.cometd.DataFilter;
    36  import org.cometd.Message;
    37  import org.mortbay.cometd.filter.JSONDataFilter;
    38  import org.mortbay.log.Log;
    39  import org.mortbay.util.ajax.JSON;
    40
    41
    42  /**
    43   * Cometd Filter Servlet implementing the {@link AbstractBayeux} protocol.
    44   *
    45   * The Servlet can be initialized with a json file mapping channels to
    46   * {@link DataFilter} definitions. The servlet init parameter "filters" should
    47   * point to a webapplication resource containing a JSON array of filter
    48   * definitions. For example:
    49   *
    50   * <pre>
    51   *  [
    52   *    {
    53   *      &quot;channels&quot;: &quot;/**&quot;,
    54   *      &quot;class&quot;   : &quot;org.mortbay.cometd.filter.NoMarkupFilter&quot;,
    55   *      &quot;init&quot;    : {}
    56   *    }
    57   *  ]
    58   * </pre>
    59   * The following init parameters can be used to configure the servlet:<dl>
    60   * <dt>timeout</dt>
    61   * <dd>The server side poll timeout in milliseconds (default 250000). This is how
    62   * long the server will hold a reconnect request before responding.</dd>
    63   *
    64   * <dt>interval</dt>
    65   * <dd>The client side poll timeout in milliseconds (default 0). How long a client
    66   * will wait between reconnects</dd>
    67   *
    68   * <dt>maxInterval</dt>
    69   * <dd>The max client side poll timeout in milliseconds (default 30000). A client will
    70   * be removed if a connection is not received in this time.
    71   *
    72   * <dt>multiFrameInterval</dt>
    73   * <dd>the client side poll timeout
    74   * if multiple connections are detected from the same browser (default 1500).</dd>
    75   *
    76   * <dt>JSONCommented</dt>
    77   * <dd>If "true" then the server will accept JSON wrapped
    78   * in a comment and will generate JSON wrapped in a comment. This is a defence against
    79   * Ajax Hijacking.</dd>
    80   *
    81   * <dt>filters</dt>
    82   * <dd>the location of a JSON file describing {@link DataFilter} instances to be installed</dd>
    83   *
    84   * <dt>requestAvailable</dt>
    85   * <dd>If true, the current request is made available via the {@link AbstractBayeux#getCurrentRequest()} method</dd>
    86   *
    87   * <dt>loglevel</dt>
    88   * <dd>0=none, 1=info, 2=debug</dd>
    89   *
    90   * <dt>directDeliver</dt>
    91   * <dd>true if published messages are delivered directly to subscribers (default). If false, a message copy is created with only supported fields (default true).</dd>
    92   *
    93   * <dt>refsThreshold</dt>
    94   * <dd>The number of message refs at which the a single message response will be
    95   * cached instead of being generated for every client delivered to. Done to optimize
    96   * a single message being sent to multiple clients.</dd>
    97   * </dl>
    98   *
    99   * @author gregw
    100  * @author aabeling: added JSONP transport
    101  *
    102  * @see {@link AbstractBayeux}
    103  * @see {@link ChannelId}
    104  */
    105 public abstract class AbstractCometdServlet extends GenericServlet
    106 {
    107     public static final String CLIENT_ATTR="org.mortbay.cometd.client";
    108     public static final String TRANSPORT_ATTR="org.mortbay.cometd.transport";
    109     public static final String MESSAGE_PARAM="message";
    110     public static final String TUNNEL_INIT_PARAM="tunnelInit";
    111     public static final String HTTP_CLIENT_ID="BAYEUX_HTTP_CLIENT";
    112     public final static String BROWSER_ID="BAYEUX_BROWSER";
    113
    114     protected AbstractBayeux _bayeux;
    115     public final static int __DEFAULT_REFS_THRESHOLD = 1;
    116     protected int _refsThreshold=__DEFAULT_REFS_THRESHOLD;
    117
    118     public AbstractBayeux getBayeux()
    119     {
    120         return _bayeux;
    121     }
    122
    123     protected abstract AbstractBayeux newBayeux();
    124
    125     @Override
    126     public void init() throws ServletException
    127     {
    128         synchronized (AbstractCometdServlet.class)
    129         {
    130             _bayeux=(AbstractBayeux)getServletContext().getAttribute(Bayeux.DOJOX_COMETD_BAYEUX);
    131             if (_bayeux==null)
    132             {
    133                 _bayeux=newBayeux();
    134             }
    135         }
    136
    137         synchronized(_bayeux)
    138         {
    139             boolean was_initialized=_bayeux.isInitialized();
    140             _bayeux.initialize(getServletContext());
    141
    142             if (!was_initialized)
    143             {
    144                 String filters=getInitParameter("filters");
    145                 if (filters!=null)
    146                 {
    147                     try
    148                     {
    149                         InputStream is = getServletContext().getResourceAsStream(filters);
    150                         if (is==null)
    151                             throw new FileNotFoundException(filters);
    152
    153                         Object[] objects=(Object[])JSON.parse(new InputStreamReader(getServletContext().getResourceAsStream(filters),"utf-8"));
    154                         for (int i=0; objects!=null&&i<objects.length; i++)
    155                         {
    156                             Map<?,?> filter_def=(Map<?,?>)objects[i];
    157
    158                             String fc = (String)filter_def.get("class");
    159                             if (fc!=null)
    160                                 Log.warn(filters+" file uses deprecated \"class\" name. Use \"filter\" instead");
    161                             else
    162                                 fc=(String)filter_def.get("filter");
    163                             Class<?> c=Thread.currentThread().getContextClassLoader().loadClass(fc);
    164                             DataFilter filter=(DataFilter)c.newInstance();
    165
    166                             if (filter instanceof JSONDataFilter)
    167                                 ((JSONDataFilter)filter).init(filter_def.get("init"));
    168
    169                             _bayeux.getChannel((String)filter_def.get("channels"),true).addDataFilter(filter);
    170                         }
    171                     }
    172                     catch (Exception e)
    173                     {
    174                         getServletContext().log("Could not parse: "+filters,e);
    175                         throw new ServletException(e);
    176                     }
    177                 }
    178
    179                 String timeout=getInitParameter("timeout");
    180                 if (timeout!=null)
    181                     _bayeux.setTimeout(Long.parseLong(timeout));
    182
    183                 String maxInterval=getInitParameter("maxInterval");
    184                 if (maxInterval!=null)
    185                     _bayeux.setMaxInterval(Long.parseLong(maxInterval));
    186
    187                 String commentedJSON=getInitParameter("JSONCommented");
    188                 _bayeux.setJSONCommented(commentedJSON!=null && Boolean.parseBoolean(commentedJSON));
    189
    190                 String l=getInitParameter("logLevel");
    191                 if (l!=null&&l.length()>0)
    192                     _bayeux.setLogLevel(Integer.parseInt(l));
    193
    194                 String interval=getInitParameter("interval");
    195                 if (interval!=null)
    196                     _bayeux.setInterval(Long.parseLong(interval));
    197
    198                 String mfInterval=getInitParameter("multiFrameInterval");
    199                 if (mfInterval!=null)
    200                     _bayeux.setMultiFrameInterval(Integer.parseInt(mfInterval));
    201
    202                 String requestAvailable=getInitParameter("requestAvailable");
    203                 _bayeux.setRequestAvailable(requestAvailable!=null && Boolean.parseBoolean(requestAvailable));
    204
    205                 String direct=getInitParameter("directDeliver");
    206                 if (direct!=null)
    207                     _bayeux.setDirectDeliver(Boolean.parseBoolean(direct));
    208
    209                 String async=getInitParameter("asyncDeliver");
    210                 if (async!=null)
    211                     getServletContext().log("asyncDeliver no longer supported");
    212
    213                 String refsThreshold=getInitParameter("refsThreshold");
    214                 if (refsThreshold!=null)
    215                     _refsThreshold=Integer.parseInt(refsThreshold);
    216
    217                 _bayeux.generateAdvice();
    218
    219                 if (_bayeux.isLogInfo())
    220                 {
    221                     getServletContext().log("timeout="+timeout);
    222                     getServletContext().log("interval="+interval);
    223                     getServletContext().log("maxInterval="+maxInterval);
    224                     getServletContext().log("multiFrameInterval="+mfInterval);
    225                     getServletContext().log("filters="+filters);
    226                     getServletContext().log("refsThreshold="+refsThreshold);
    227                 }
    228             }
    229         }
    230
    231         getServletContext().setAttribute(Bayeux.DOJOX_COMETD_BAYEUX,_bayeux);
    232     }
    233
    234     protected abstract void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException;
    235
    236     @Override
    237     public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException
    238     {
    239         HttpServletRequest request=(HttpServletRequest)req;
    240         HttpServletResponse response=(HttpServletResponse)resp;
    241
    242         if (_bayeux.isRequestAvailable())
    243             _bayeux.setCurrentRequest(request);
    244         try
    245         {
    246             service(request,response);
    247         }
    248         finally
    249         {
    250             if (_bayeux.isRequestAvailable())
    251                 _bayeux.setCurrentRequest(null);
    252         }
    253     }
    254
    255
    256     protected String browserId(HttpServletRequest request)
    257     {
    258         Cookie[] cookies = request.getCookies();
    259         if (cookies!=null)
    260         {
    261             for (Cookie cookie : cookies)
    262             {
    263                 if (BROWSER_ID.equals(cookie.getName()))
    264                     return cookie.getValue();
    265             }
    266         }
    267
    268         return null;
    269     }
    270
    271     protected String newBrowserId(HttpServletRequest request,HttpServletResponse response)
    272     {
    273         String browser_id=Long.toHexString(request.getRemotePort())+
    274         Long.toString(_bayeux.getRandom(),36)+
    275         Long.toString(System.currentTimeMillis(),36)+
    276         Long.toString(request.getRemotePort(),36);
    277
    278         Cookie cookie = new Cookie(BROWSER_ID,browser_id);
    279         cookie.setPath("/");
    280         cookie.setMaxAge(-1);
    281         response.addCookie(cookie);
    282         return browser_id;
    283     }
    284
    285     private static Message[] __EMPTY_BATCH=new Message[0];
    286
    287     protected Message[] getMessages(HttpServletRequest request) throws IOException
    288     {
    289         String fodder=null;
    290         try
    291         {
    292             // Get message batches either as JSON body or as message parameters
    293             if (request.getContentType() != null && !request.getContentType().startsWith("application/x-www-form-urlencoded"))
    294             {
    295                 return _bayeux.parse(request.getReader());
    296             }
    297
    298             String[] batches=request.getParameterValues(MESSAGE_PARAM);
    299
    300             if (batches==null || batches.length==0)
    301                 return __EMPTY_BATCH;
    302
    303             if (batches.length==0)
    304             {
    305                 fodder=batches[0];
    306                 return _bayeux.parse(fodder);
    307             }
    308
    309             List<Message> messages = new ArrayList<Message>();
    310             for (int i=0;i<batches.length;i++)
    311             {
    312                 if (batches[i]==null)
    313                     continue;
    314
    315                 fodder=batches[i];
    316                 _bayeux.parseTo(fodder,messages);
    317
    318             }
    319
    320             return messages.toArray(new Message[messages.size()]);
    321         }
    322         catch(IOException e)
    323         {
    324             throw e;
    325         }
    326         catch(Exception e)
    327         {
    328             throw new Error(fodder,e);
    329         }
    330     }
    331
    332 }