All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cometd.server.transports.LongPollingTransport Maven / Gradle / Ivy

package org.cometd.server.transports;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.cometd.bayeux.Channel;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.server.BayeuxServerImpl;
import org.cometd.server.ServerSessionImpl;
import org.cometd.server.ServerTransport;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationListener;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.eclipse.jetty.util.log.Log;

public abstract class LongPollingTransport extends HttpTransport
{
    private final static AtomicInteger __zero = new AtomicInteger(0);
    
    protected final static String BROWSER_ID_OPTION="browserId";
    protected final static String MAX_SESSIONS_PER_BROWSER_OPTION="maxSessionsPerBrowser";
    protected final static String MULTI_SESSION_INTERVAL_OPTION="multiSessionInterval";

    private final ConcurrentHashMap _browserMap=new ConcurrentHashMap();

    protected String _browserId="BAYEUX_BROWSER";
    private int _maxSessionsPerBrowser=1;
    private long _multiSessionInterval=2000;
    
    protected LongPollingTransport(BayeuxServerImpl bayeux,String name,Map options)
    {
        super(bayeux,name,options);
        _prefix.add("long-polling");
        setOption(BROWSER_ID_OPTION,_browserId);
        setOption(MAX_SESSIONS_PER_BROWSER_OPTION,_maxSessionsPerBrowser);
        setOption(MULTI_SESSION_INTERVAL_OPTION,_multiSessionInterval);
    }

    @Override
    protected void init()
    {
        super.init();
        _browserId=getOption(BROWSER_ID_OPTION,_browserId);
        _maxSessionsPerBrowser=getOption(MAX_SESSIONS_PER_BROWSER_OPTION,_maxSessionsPerBrowser);
        _multiSessionInterval=getOption(MULTI_SESSION_INTERVAL_OPTION,_multiSessionInterval);
    }
    

    protected String getBrowserId(HttpServletRequest request, HttpServletResponse response)
    {
        Cookie[] cookies=request.getCookies();
        if (cookies != null)
        {
            for (Cookie cookie : cookies)
            {
                if (_browserId.equals(cookie.getName()))
                    return cookie.getValue();
            }
        }

        String browser_id=Long.toHexString(request.getRemotePort()) + Long.toString(_bayeux.randomLong(),36) + Long.toString(System.currentTimeMillis(),36)
                + Long.toString(request.getRemotePort(),36);
        Cookie cookie=new Cookie(_browserId,browser_id);
        cookie.setPath("/");
        cookie.setMaxAge(-1);
        response.addCookie(cookie);
        return browser_id;
    }

    protected boolean incBrowserId(String browserId,HttpServletRequest request, ServerMessage reply)
    {
        if (_maxSessionsPerBrowser<0)
            return true;
        
        AtomicInteger count = _browserMap.get(browserId);
        if (count==null)
        {
            AtomicInteger new_count = new AtomicInteger();
            count=_browserMap.putIfAbsent(browserId,new_count);
            if (count==null)
                count=new_count;
        }
        
        if (count.incrementAndGet()>_maxSessionsPerBrowser)
        {
            Map advice=reply.asMutable().getAdvice(true);
            advice.put("multiple-clients",Boolean.TRUE);
            if (_multiSessionInterval > 0)
            {
                advice.put("reconnect","retry");
                advice.put("interval",_multiSessionInterval);
            }
            else
                advice.put("reconnect","none");
            count.decrementAndGet();
            return false;
        }
        
        return true;   
    }
    
    protected void decBrowserId(String browserId)
    {
        AtomicInteger count = _browserMap.get(browserId);
        if (count!=null && count.decrementAndGet()==0)
        {
            _browserMap.remove(browserId,__zero);
        }
    }
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
    {
        // is this a resumed connect?
        LongPollDispatcher dispatcher=(LongPollDispatcher)request.getAttribute("dispatcher");
        if (dispatcher==null)
        {
            // No - process messages
            
            // remember if we start a batch
            boolean batch=false;
            
            // Don't know the session until first message or handshake response.
            ServerSessionImpl session=null;

            try
            {
                ServerMessage.Mutable[] messages = parseMessages(request);
                if (messages==null)
                    return;

                PrintWriter writer=null;

                // for each message
                for (ServerMessage.Mutable message : messages)
                {
                    // reference it (this should be ref=1)
                    message.incRef();

                    // Get the session from the message
                    if (session==null)
                    {
                        session=(ServerSessionImpl)_bayeux.getSession(message.getClientId());
                        if (session!=null && !message.isMeta())
                        {
                            // start a batch to group all resulting messages into a single response.
                            batch=true;
                            session.startBatch();
                        }
                    }

                    // remember the connected status
                    boolean was_connected=session!=null && session.isConnected();
                    boolean connect = Channel.META_CONNECT.equals(message.getChannel());

                    // handle the message
                    // the actual reply is return from the call, but other messages may
                    // also be queued on the session.
                    ServerMessage reply = _bayeux.handle(session,message);

                    // Do we have a reply
                    if (reply!=null)
                    {
                        if (session==null)
                            // This must be a handshake
                            // extract a session from the reply (if we don't already know it
                            session=(ServerSessionImpl)_bayeux.getSession(reply.getClientId());
                        else
                        {
                            // If this is a connect or we can send messages with any response
                            if  (connect || !(isMetaConnectDeliveryOnly()||session.isMetaConnectDeliveryOnly()))
                            {
                                // send the queued messages
                                Queue queue = session.getQueue();
                                synchronized (queue)
                                {
                                    session.dequeue();
                                    for (int i=queue.size();i-->0;)
                                    {
                                        ServerMessage m=queue.poll();
                                        writer=send(request,response,writer, m);
                                    }
                                }
                            }

                            // special handling for connect
                            if (connect)
                            {
                                // Should we suspend? 
                                // If the writer is non null, we have already started sending a response, so we should not suspend
                                if(was_connected && writer==null && reply.isSuccessful())
                                {
                                    session.cancelDispatch();
                                    String browserId=getBrowserId(request,response);
                                    if (incBrowserId(browserId,request,reply))
                                    {
                                        Continuation continuation = ContinuationSupport.getContinuation(request);
                                        long timeout=session.getTimeout();
                                        continuation.setTimeout(timeout==-1?_timeout:timeout); 
                                        continuation.suspend();
                                        dispatcher=new LongPollDispatcher(session,continuation,reply,browserId);
                                        if (session.setDispatcher(dispatcher))
                                        {
                                            // suspend successful
                                            request.setAttribute("dispatcher",dispatcher);
                                            reply=null;
                                        }
                                        else
                                        {
                                            // a message was already added - so handle it now;
                                            continuation.complete();
                                            dispatcher=null;
                                            if (session.isConnected())
                                                session.startIntervalTimeout();
                                        }
                                    }
                                    else 
                                    {
                                        session.reAdvise();
                                    }
                                }
                                else if (session.isConnected())
                                    session.startIntervalTimeout();
                            }
                        }
                        
                        // If the reply has not been otherwise handled, send it
                        if (reply!=null)
                        {
                            reply=_bayeux.extendReply(session,reply);
                            
                            if (reply!=null)
                                writer=send(request,response,writer, reply);
                        }
                    }
                    
                    // disassociate the reply
                    message.setAssociated(null);
                    // dec our own ref, this should be to 0 unless message was ref'd elsewhere.
                    message.decRef();
                }
                if (writer!=null)
                    complete(writer);
            }
            finally
            {
                // if we started a batch - end it now
                if (batch)
                    session.endBatch();
            }
        }
        else
        {
            // Get the resumed session
            ServerSessionImpl session=dispatcher.getSession();
            if (session.isConnected())
                session.startIntervalTimeout();

            // Send the message queue
            Queue queue = session.getQueue();
            PrintWriter writer=null;
            synchronized (queue)
            {
                session.dequeue();
                for (int i=queue.size();i-->0;)
                {
                    ServerMessage m=queue.poll();
                    writer=send(request,response,writer, m);
                }
            }
            
            // send the connect reply
            ServerMessage reply=dispatcher.getReply();
            reply=_bayeux.extendReply(session,reply);
            writer=send(request,response,writer, reply);
            
            complete(writer);
        }
    }

    abstract protected PrintWriter send(HttpServletRequest request,HttpServletResponse response,PrintWriter writer, ServerMessage message) throws IOException;
    
    abstract protected void complete(PrintWriter writer) throws IOException;
    
    
    private class LongPollDispatcher implements ServerTransport.Dispatcher, ContinuationListener
    {
        private final ServerSessionImpl _session;
        private final Continuation _continuation;
        private final ServerMessage _reply;
        private final String _browserId;
        
        public LongPollDispatcher(ServerSessionImpl session, Continuation continuation, ServerMessage reply,String browserId)
        {
            _session = session;
            _continuation = continuation;
            _continuation.addContinuationListener(this);
            _reply = reply;
            reply.incRef();
            _browserId=browserId;
        }

        public void cancelDispatch()
        {
            if (_continuation!=null && _continuation.isSuspended() )
            {
                try
                {
                    ((HttpServletResponse)_continuation.getServletResponse()).sendError(503);
                }
                catch(IOException e)
                {
                    Log.ignore(e);
                }
                
                try
                {
                    _continuation.complete();
                }
                catch(Exception e)
                {
                    Log.ignore(e);
                }
            }
        }

        public void dispatch()
        {
            _continuation.resume();
        }

        public ServerSessionImpl getSession()
        {
            return _session;
        }

        public Continuation getContinuation()
        {
            return _continuation;
        }

        public ServerMessage getReply()
        {
            return _reply;
        }

        public void onComplete(Continuation continuation)
        {
            decBrowserId(_browserId);
        }

        public void onTimeout(Continuation continuation)
        {
            _session.setDispatcher(null);
        }
        
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy