Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2008-2014 the original author or authors.
*
* 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.cometd.server.transport;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
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.Message;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.AbstractServerTransport;
import org.cometd.server.BayeuxServerImpl;
import org.cometd.server.ServerSessionImpl;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationListener;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/* ------------------------------------------------------------ */
/**
* Abstract Long Polling Transport.
*
* Transports based on this class can be configured with servlet init parameters:
*
*
browserId
The Cookie name used to save a browser ID.
*
maxSessionsPerBrowser
The maximum number of long polling sessions allowed per browser.
*
multiSessionInterval
The polling interval to use once max session per browser is exceeded.
*
autoBatch
If true a batch will be automatically created to span the handling of messages received from a session.
*
allowMultiSessionsNoBrowser
Allows multiple sessions even when the browser identifier cannot be retrieved.
*
*/
public abstract class LongPollingTransport extends HttpTransport
{
public final static String PREFIX = "long-polling";
public final static String BROWSER_ID_OPTION = "browserId";
public final static String BROWSER_COOKIE_NAME_OPTION = "browserCookieName";
public final static String BROWSER_COOKIE_DOMAIN_OPTION = "browserCookieDomain";
public final static String BROWSER_COOKIE_PATH_OPTION = "browserCookiePath";
public final static String MAX_SESSIONS_PER_BROWSER_OPTION = "maxSessionsPerBrowser";
public final static String MULTI_SESSION_INTERVAL_OPTION = "multiSessionInterval";
public final static String AUTOBATCH_OPTION = "autoBatch";
public final static String ALLOW_MULTI_SESSIONS_NO_BROWSER_OPTION = "allowMultiSessionsNoBrowser";
private final Logger _logger = LoggerFactory.getLogger(getClass());
private final ConcurrentHashMap _browserMap = new ConcurrentHashMap();
private final Map _browserSweep = new ConcurrentHashMap();
private String _browserCookieName;
private String _browserCookieDomain;
private String _browserCookiePath;
private int _maxSessionsPerBrowser;
private long _multiSessionInterval;
private boolean _autoBatch;
private boolean _allowMultiSessionsNoBrowser;
private long _lastSweep;
protected LongPollingTransport(BayeuxServerImpl bayeux, String name)
{
super(bayeux, name);
setOptionPrefix(PREFIX);
}
@Override
protected void init()
{
super.init();
_browserCookieName = getOption(BROWSER_COOKIE_NAME_OPTION, getOption(BROWSER_ID_OPTION, "BAYEUX_BROWSER"));
_browserCookieDomain = getOption(BROWSER_COOKIE_DOMAIN_OPTION, null);
_browserCookiePath = getOption(BROWSER_COOKIE_PATH_OPTION, "/");
_maxSessionsPerBrowser = getOption(MAX_SESSIONS_PER_BROWSER_OPTION, 1);
_multiSessionInterval = getOption(MULTI_SESSION_INTERVAL_OPTION, 2000);
_autoBatch = getOption(AUTOBATCH_OPTION, true);
_allowMultiSessionsNoBrowser = getOption(ALLOW_MULTI_SESSIONS_NO_BROWSER_OPTION, false);
}
protected String findBrowserId(HttpServletRequest request)
{
Cookie[] cookies = request.getCookies();
if (cookies != null)
{
for (Cookie cookie : cookies)
{
if (_browserCookieName.equals(cookie.getName()))
return cookie.getValue();
}
}
return null;
}
protected String setBrowserId(HttpServletRequest request, HttpServletResponse response)
{
String browserId = Long.toHexString(request.getRemotePort()) +
Long.toString(getBayeux().randomLong(), 36) +
Long.toString(System.currentTimeMillis(), 36) +
Long.toString(request.getRemotePort(), 36);
Cookie cookie = new Cookie(_browserCookieName, browserId);
if (_browserCookieDomain != null)
cookie.setDomain(_browserCookieDomain);
cookie.setPath(_browserCookiePath);
cookie.setMaxAge(-1);
response.addCookie(cookie);
return browserId;
}
/**
* Increment the browser ID count.
*
* @param browserId the browser ID to increment the count for
* @return true if the browser ID count is below the max sessions per browser value.
* If false is returned, the count is not incremented.
*/
protected boolean incBrowserId(String browserId)
{
if (_maxSessionsPerBrowser < 0)
return true;
if (_maxSessionsPerBrowser == 0)
return false;
AtomicInteger count = _browserMap.get(browserId);
if (count == null)
{
AtomicInteger newCount = new AtomicInteger();
count = _browserMap.putIfAbsent(browserId, newCount);
if (count == null)
count = newCount;
}
// Increment
int sessions = count.incrementAndGet();
// If was zero, remove from the sweep
if (sessions == 1)
_browserSweep.remove(browserId);
// TODO, the maxSessionsPerBrowser should be parameterized on user-agent
if (sessions > _maxSessionsPerBrowser)
{
count.decrementAndGet();
return false;
}
return true;
}
protected void decBrowserId(String browserId)
{
if (browserId == null)
return;
AtomicInteger count = _browserMap.get(browserId);
if (count != null && count.decrementAndGet() == 0)
{
_browserSweep.put(browserId, new AtomicInteger(0));
}
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// Is this a resumed connect?
LongPollScheduler scheduler = (LongPollScheduler)request.getAttribute(LongPollScheduler.ATTRIBUTE);
if (scheduler == 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;
boolean connect = false;
try
{
ServerMessage.Mutable[] messages = parseMessages(request);
if (messages == null)
return;
PrintWriter writer = null;
for (ServerMessage.Mutable message : messages)
{
// Is this a connect?
connect = Channel.META_CONNECT.equals(message.getChannel());
// Get the session from the message
String client_id = message.getClientId();
if (session == null || client_id != null && !client_id.equals(session.getId()))
{
session = (ServerSessionImpl)getBayeux().getSession(client_id);
if (_autoBatch && !batch && session != null && !connect && !message.isMeta())
{
// start a batch to group all resulting messages into a single response.
batch = true;
session.startBatch();
}
}
else if (!session.isHandshook())
{
batch = false;
session = null;
}
if (connect && session != null)
{
// cancel previous scheduler to cancel any prior waiting long poll
// this should also dec the browser ID
session.setScheduler(null);
}
boolean wasConnected = session != null && session.isConnected();
// Forward handling of the message.
// The actual reply is return from the call, but other messages may
// also be queued on the session.
ServerMessage.Mutable reply = bayeuxServerHandle(session, message);
// Do we have a reply ?
if (reply != null)
{
if (session == null)
{
// This must be a handshake, extract a session from the reply
session = (ServerSessionImpl)getBayeux().getSession(reply.getClientId());
// Add the browser ID cookie
if (session != null)
{
String browserId = findBrowserId(request);
if (browserId == null)
setBrowserId(request, response);
}
}
else
{
// Special handling for connect
if (connect)
{
try
{
if (!session.hasNonLazyMessages() && reply.isSuccessful())
{
// Detect if we have multiple sessions from the same browser
// Note that CORS requests do not send cookies, so we need to handle them specially
// CORS requests always have the Origin header
String browserId = findBrowserId(request);
boolean allowSuspendConnect;
if (browserId != null)
allowSuspendConnect = incBrowserId(browserId);
else
allowSuspendConnect = _allowMultiSessionsNoBrowser || request.getHeader("Origin") != null;
if (allowSuspendConnect)
{
long timeout = session.calculateTimeout(getTimeout());
// Support old clients that do not send advice:{timeout:0} on the first connect
if (timeout > 0 && wasConnected && session.isConnected())
{
// Between the last time we checked for messages in the queue
// (which was false, otherwise we would not be in this branch)
// and now, messages may have been added to the queue.
// We will suspend anyway, but setting the scheduler on the
// session will decide atomically if we need to resume or not.
// Suspend and wait for messages
Continuation continuation = ContinuationSupport.getContinuation(request);
continuation.setTimeout(timeout);
continuation.suspend(response);
scheduler = newLongPollScheduler(session, continuation, reply, browserId);
request.setAttribute(LongPollScheduler.ATTRIBUTE, scheduler);
session.setScheduler(scheduler);
reply = null;
metaConnectSuspended(request, session, timeout);
}
else
{
decBrowserId(browserId);
}
}
else
{
// There are multiple sessions from the same browser
Map advice = reply.getAdvice(true);
if (browserId != null)
advice.put("multiple-clients", true);
if (_multiSessionInterval > 0)
{
advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_RETRY_VALUE);
advice.put(Message.INTERVAL_FIELD, _multiSessionInterval);
}
else
{
advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_NONE_VALUE);
reply.setSuccessful(false);
}
session.reAdvise();
}
}
}
finally
{
if (reply != null)
writer = writeQueueForMetaConnect(request, response, session, writer);
}
}
else
{
if (!isMetaConnectDeliveryOnly() && !session.isMetaConnectDeliveryOnly())
{
writer = writeQueue(request, response, session, writer);
}
}
}
// If the reply has not been otherwise handled, send it
if (reply != null)
{
if (connect && session != null && session.isDisconnected())
reply.getAdvice(true).put(Message.RECONNECT_FIELD, Message.RECONNECT_NONE_VALUE);
reply = getBayeux().extendReply(session, session, reply);
if (reply != null)
{
getBayeux().freeze(reply);
writer = writeMessage(request, response, writer, session, reply);
}
}
}
// Disassociate the reply
message.setAssociated(null);
}
if (writer != null)
finishWrite(writer, session);
}
catch (ParseException x)
{
handleJSONParseException(request, response, x.getMessage(), x.getCause());
}
finally
{
// If we started a batch, end it now
if (batch)
{
// Flush session if not done by the batch, since some browser order