@Override public boolean sendMeta(ServerSession to, Mutable message) { String channel = message.getChannel(); if (channel == null) return true; if (Channel.META_HANDSHAKE.equals(channel) && Boolean.TRUE.equals(message.get(Message.SUCCESSFUL_FIELD))) { Message rcv = message.getAssociated(); Map<String, Object> ext = rcv.getExt(); boolean clientRequestedAcks = ext != null && ext.get("ack") == Boolean.TRUE; if (clientRequestedAcks && to != null) { _logger.debug("Enabled message acknowledgement for client {}", to); to.addExtension(new AcknowledgedMessagesClientExtension(to)); ((ServerSessionImpl) to).setMetaConnectDeliveryOnly(true); } Map<String, Object> mext = message.getExt(); if (mext != null) mext.put("ack", Boolean.TRUE); else message.put(Message.EXT_FIELD, _replyExt); } return true; }
protected void dump(StringBuilder b, String indent) { b.append(toString()); b.append(isLazy() ? " lazy" : ""); b.append('\n'); int leaves = _subscribers.size() + _listeners.size() + _authorizers.size(); int i = 0; for (ServerSession child : _subscribers) { b.append(indent); b.append(" +-"); ((ServerSessionImpl) child).dump(b, indent + ((++i == leaves) ? " " : " | ")); } for (ServerChannelListener child : _listeners) { b.append(indent); b.append(" +-"); b.append(child); b.append('\n'); } for (Authorizer auth : _authorizers) { b.append(indent); b.append(" +-"); b.append(auth); b.append('\n'); } }
public void remove() { if (_bayeux.removeServerChannel(this)) { for (ServerSession subscriber : _subscribers) ((ServerSessionImpl) subscriber).unsubscribedFrom(this); _subscribers.clear(); } _listeners.clear(); }
private PrintWriter sendQueue( HttpServletRequest request, HttpServletResponse response, ServerSessionImpl session, PrintWriter writer) throws IOException { final List<ServerMessage> queue = session.takeQueue(); for (ServerMessage m : queue) writer = send(request, response, writer, m); return writer; }
private boolean unsubscribe(ServerSessionImpl session) { if (_subscribers.remove(session)) { session.unsubscribedFrom(this); for (ServerChannelListener listener : _listeners) if (listener instanceof SubscriptionListener) notifyUnsubscribed((SubscriptionListener) listener, session, this); for (BayeuxServer.BayeuxServerListener listener : _bayeux.getListeners()) if (listener instanceof BayeuxServer.SubscriptionListener) notifyUnsubscribed((BayeuxServer.SubscriptionListener) listener, session, this); } return true; }
private boolean subscribe(ServerSessionImpl session) { resetSweeperPasses(); if (_subscribers.add(session)) { session.subscribedTo(this); for (ServerChannelListener listener : _listeners) if (listener instanceof SubscriptionListener) notifySubscribed((SubscriptionListener) listener, session, this); for (BayeuxServer.BayeuxServerListener listener : _bayeux.getListeners()) if (listener instanceof BayeuxServer.SubscriptionListener) notifySubscribed((BayeuxServer.SubscriptionListener) listener, session, this); } return true; }
public void onTimeout(Continuation continuation) { _session.setScheduler(null); }
@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()); // Get the user agent while we are at it, and add the browser ID cookie if (session != null) { String userAgent = request.getHeader("User-Agent"); session.setUserAgent(userAgent); String browserId = findBrowserId(request); if (browserId == null) setBrowserId(request, response); } } else { // Special handling for connect if (connect) { try { writer = sendQueue(request, response, session, writer); // If the writer is non null, we have already started sending a response, so we // should not suspend if (writer == null && reply.isSuccessful() && session.isQueueEmpty()) { // 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()) { // Suspend and wait for messages Continuation continuation = ContinuationSupport.getContinuation(request); continuation.setTimeout(timeout); continuation.suspend(response); scheduler = new LongPollScheduler(session, continuation, reply, browserId); session.setScheduler(scheduler); request.setAttribute(LongPollScheduler.ATTRIBUTE, scheduler); reply = null; metaConnectSuspended(request, session, timeout); } else { decBrowserId(browserId); } } else { // There are multiple sessions from the same browser Map<String, Object> 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 && session.isConnected()) session.startIntervalTimeout(getInterval()); } } else { if (!isMetaConnectDeliveryOnly() && !session.isMetaConnectDeliveryOnly()) { writer = sendQueue(request, response, session, writer); } } } // If the reply has not been otherwise handled, send it if (reply != null) { if (connect && session != null && !session.isConnected()) reply.getAdvice(true).put(Message.RECONNECT_FIELD, Message.RECONNECT_NONE_VALUE); reply = getBayeux().extendReply(session, session, reply); if (reply != null) { getBayeux().freeze(reply); writer = send(request, response, writer, reply); } } } // Disassociate the reply message.setAssociated(null); } if (writer != null) complete(writer); } catch (ParseException x) { handleJSONParseException(request, response, x.getMessage(), x.getCause()); } finally { // If we started a batch, end it now if (batch) { boolean ended = session.endBatch(); // Flush session if not done by the batch, since some browser order <script> requests if (!ended && isAlwaysFlushingAfterHandle()) session.flush(); } else if (session != null && !connect && isAlwaysFlushingAfterHandle()) { session.flush(); } } } else { // Get the resumed session ServerSessionImpl session = scheduler.getSession(); metaConnectResumed(request, session); PrintWriter writer; try { // Send the message queue writer = sendQueue(request, response, session, null); } finally { // We need to start the interval timeout before the connect reply // otherwise we open up a race condition where the client receives // the connect reply and sends a new connect request before we start // the interval timeout, which will be wrong. // We need to put this into a finally block in case sending the queue // throws an exception (for example because the client is gone), so that // we start the interval timeout that is important to sweep the session if (session.isConnected()) session.startIntervalTimeout(getInterval()); } // Send the connect reply ServerMessage.Mutable reply = scheduler.getReply(); if (!session.isConnected()) reply.getAdvice(true).put(Message.RECONNECT_FIELD, Message.RECONNECT_NONE_VALUE); reply = getBayeux().extendReply(session, session, reply); if (reply != null) { getBayeux().freeze(reply); writer = send(request, response, writer, reply); } complete(writer); } }