@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);
    }
  }