@Override
 public boolean sendMeta(ServerSession session, ServerMessage.Mutable message) {
   if (Channel.META_CONNECT.equals(message.getChannel())) {
     int connects = this.connects.incrementAndGet();
     if (connects == 1) {
       // Fake the removal of the session due to timeout
       bayeuxServer.removeServerSession(session, true);
     }
   }
   return true;
 }
 @Override
 public boolean rcvMeta(ServerSession from, ServerMessage.Mutable message) {
   if (Channel.META_CONNECT.equals(message.getChannel())) {
     int connects = this.connects.incrementAndGet();
     if (connects == 2) {
       try {
         Thread.sleep(delay);
       } catch (InterruptedException x) {
         return false;
       }
     }
   }
   return true;
 }
Exemple #3
0
 @Override
 public boolean sendMeta(ServerSession to, Mutable message) {
   if (Channel.META_HANDSHAKE.equals(message.getChannel())
       || Channel.META_CONNECT.equals(message.getChannel())
       || Channel.META_DISCONNECT.equals(message.getChannel())) {
     AbstractHttpTransport transport = (AbstractHttpTransport) bayeux.getCurrentTransport();
     HttpServletRequest request = transport.getCurrentRequest();
     if (request != null) {
       String uri = request.getRequestURI();
       message.getExt(true).put("uri", uri);
     }
   }
   return true;
 }
Exemple #4
0
 @Override
 public boolean sendMeta(ClientSession session, Mutable message) {
   if (Channel.META_HANDSHAKE.equals(message.getChannel())) {
     _serverSupportsAcks = false;
     _transientBatch = 0;
     _batch = 0;
     _size = 0;
     message.getExt(true).put(ACK_FIELD, Boolean.TRUE);
   } else if (Channel.META_CONNECT.equals(message.getChannel())) {
     if (_serverSupportsAcks) {
       message.getExt(true).put(ACK_FIELD, _batch);
     }
   }
   return true;
 }
Exemple #5
0
  @Override
  public boolean rcvMeta(ClientSession session, Mutable message) {
    if (Channel.META_HANDSHAKE.equals(message.getChannel())) {
      Map<String, Object> ext = message.getExt(false);
      if (ext != null) {
        Object field = ext.get(ACK_FIELD);
        if (field instanceof Map) {
          // New format.
          @SuppressWarnings("unchecked")
          Map<String, Object> ack = (Map<String, Object>) field;
          _serverSupportsAcks = Boolean.TRUE.equals(ack.get("enabled"));

          // Check if there are messages.
          Object batch = ack.get("batch");
          Object size = ack.get("size");
          if (batch instanceof Number && size instanceof Number) {
            _transientBatch = ((Number) batch).longValue();
            _size = ((Number) size).intValue();
          }
        } else {
          // Old format.
          _serverSupportsAcks = Boolean.TRUE.equals(field);
        }
      }
    } else if (Channel.META_CONNECT.equals(message.getChannel())
        && message.isSuccessful()
        && _serverSupportsAcks) {
      Map<String, Object> ext = message.getExt(false);
      if (ext != null) {
        Object ack = ext.get(ACK_FIELD);
        if (ack instanceof Number) {
          _batch = ((Number) ack).longValue();
        }
      }
    }
    return true;
  }
  @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);
    }
  }
Exemple #7
0
  @Test
  public void testStateUnSubscribes() throws Exception {
    final BlockingArrayQueue<Object> results = new BlockingArrayQueue<>();

    final AtomicBoolean failHandShake = new AtomicBoolean(true);

    LongPollingTransport transport =
        new LongPollingTransport(null, httpClient) {
          @Override
          protected void customize(Request request) {
            if (failHandShake.compareAndSet(true, false)) {
              request.listener(
                  new Request.Listener.Adapter() {
                    @Override
                    public void onBegin(Request request) {
                      request.header(HttpHeader.HOST, null);
                    }
                  });
            }
          }
        };

    BayeuxClient client = new BayeuxClient(cometdURL, transport);

    client
        .getChannel("/meta/*")
        .addListener(
            new ClientSessionChannel.MessageListener() {
              public void onMessage(ClientSessionChannel channel, Message message) {
                results.offer(message);
              }
            });

    client.handshake();

    // Subscribe without waiting
    client
        .getChannel("/foo/bar")
        .subscribe(
            new ClientSessionChannel.MessageListener() {
              public void onMessage(ClientSessionChannel channel, Message message) {}
            });

    // First handshake fails
    Message message = (Message) results.poll(10, TimeUnit.SECONDS);
    Assert.assertNotNull(message);
    Assert.assertEquals(Channel.META_HANDSHAKE, message.getChannel());
    Assert.assertFalse(message.isSuccessful());

    // Second handshake works
    message = (Message) results.poll(10, TimeUnit.SECONDS);
    Assert.assertNotNull(message);
    Assert.assertEquals(Channel.META_HANDSHAKE, message.getChannel());
    Assert.assertTrue(message.isSuccessful());
    String id = client.getId();
    Assert.assertNotNull(id);

    boolean subscribe = false;
    boolean connect = false;
    for (int i = 0; i < 2; ++i) {
      message = (Message) results.poll(10, TimeUnit.SECONDS);
      Assert.assertNotNull(message);
      subscribe |= Channel.META_SUBSCRIBE.equals(message.getChannel());
      connect |= Channel.META_CONNECT.equals(message.getChannel());
    }
    Assert.assertTrue(subscribe);
    Assert.assertTrue(connect);

    // Subscribe again
    client
        .getChannel("/foo/bar")
        .subscribe(
            new ClientSessionChannel.MessageListener() {
              public void onMessage(ClientSessionChannel channel, Message message) {}
            });

    // No second subscribe sent, be sure to wait less than the timeout
    // otherwise we get a connect message
    message = (Message) results.poll(5, TimeUnit.SECONDS);
    Assert.assertNull(message);

    client.disconnect();
    boolean disconnect = false;
    connect = false;
    for (int i = 0; i < 2; ++i) {
      message = (Message) results.poll(10, TimeUnit.SECONDS);
      Assert.assertNotNull(message);
      disconnect |= Channel.META_DISCONNECT.equals(message.getChannel());
      connect |= Channel.META_CONNECT.equals(message.getChannel());
    }
    Assert.assertTrue(disconnect);
    Assert.assertTrue(connect);
    Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.DISCONNECTED));

    // Rehandshake
    client.handshake();
    Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.CONNECTED));

    results.clear();
    // Subscribe again
    client
        .getChannel("/foo/bar")
        .subscribe(
            new ClientSessionChannel.MessageListener() {
              public void onMessage(ClientSessionChannel channel, Message message) {}
            });

    // Subscribe is sent, skip the connect message if present
    message = (Message) results.poll(10, TimeUnit.SECONDS);
    Assert.assertNotNull(message);
    if (Channel.META_CONNECT.equals(message.getChannel()))
      message = (Message) results.poll(10, TimeUnit.SECONDS);
    Assert.assertEquals(Channel.META_SUBSCRIBE, message.getChannel());

    // Restart server
    int port = connector.getLocalPort();
    server.stop();
    server.join();
    Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.UNCONNECTED));
    connector.setPort(port);
    server.start();

    bayeux = (BayeuxServerImpl) context.getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);

    Assert.assertTrue(client.waitFor(5000, BayeuxClient.State.CONNECTED));
    results.clear();

    // Subscribe again
    client
        .getChannel("/foo/bar")
        .subscribe(
            new ClientSessionChannel.MessageListener() {
              public void onMessage(ClientSessionChannel channel, Message message) {}
            });

    // Subscribe is sent, skip the connect message if present
    message = (Message) results.poll(10, TimeUnit.SECONDS);
    Assert.assertNotNull(message);
    if (Channel.META_CONNECT.equals(message.getChannel()))
      message = (Message) results.poll(10, TimeUnit.SECONDS);
    Assert.assertEquals(Channel.META_SUBSCRIBE, message.getChannel());

    disconnectBayeuxClient(client);
  }
  @Override
  public void send(final TransportListener listener, final Message.Mutable... messages) {
    String url = getURL();
    final URI uri = URI.create(url);
    if (_appendMessageType && messages.length == 1 && messages[0].isMeta()) {
      String type = messages[0].getChannel().substring(Channel.META.length());
      if (url.endsWith("/")) url = url.substring(0, url.length() - 1);
      url += type;
    }

    final Request request = _httpClient.newRequest(url).method(HttpMethod.POST);
    request.header(HttpHeader.CONTENT_TYPE.asString(), "application/json;charset=UTF-8");

    StringBuilder builder = new StringBuilder();
    for (HttpCookie cookie : getCookieStore().get(uri)) {
      builder.setLength(0);
      builder.append(cookie.getName()).append("=").append(cookie.getValue());
      request.header(HttpHeader.COOKIE.asString(), builder.toString());
    }

    request.content(new StringContentProvider(generateJSON(messages)));

    customize(request);

    synchronized (this) {
      if (_aborted) throw new IllegalStateException("Aborted");
      _requests.add(request);
    }

    request.listener(
        new Request.Listener.Empty() {
          @Override
          public void onHeaders(Request request) {
            listener.onSending(messages);
          }
        });

    long maxNetworkDelay = _maxNetworkDelay;
    if (messages.length == 1 && Channel.META_CONNECT.equals(messages[0].getChannel())) {
      Map<String, Object> advice = messages[0].getAdvice();
      if (advice == null) advice = _advice;
      if (advice != null) {
        Object timeout = advice.get("timeout");
        if (timeout instanceof Number) maxNetworkDelay += ((Number) timeout).longValue();
        else if (timeout != null) maxNetworkDelay += Long.parseLong(timeout.toString());
      }
    }
    // Set the idle timeout for this request larger than the total timeout
    // so there are no races between the two timeouts
    request.idleTimeout(maxNetworkDelay * 2, TimeUnit.MILLISECONDS);
    request.timeout(maxNetworkDelay, TimeUnit.MILLISECONDS);
    request.send(
        new BufferingResponseListener() {
          @Override
          public boolean onHeader(Response response, HttpField field) {
            // We do not allow cookies to be handled by HttpClient, since one
            // HttpClient instance is shared by multiple BayeuxClient instances.
            // Instead, we store the cookies in the BayeuxClient instance.
            switch (field.getHeader()) {
              case SET_COOKIE:
              case SET_COOKIE2:
                Map<String, List<String>> cookies = new HashMap<>(1);
                cookies.put(field.getName(), Collections.singletonList(field.getValue()));
                storeCookies(uri, cookies);
                return false;
              default:
                return true;
            }
          }

          private void storeCookies(URI uri, Map<String, List<String>> cookies) {
            try {
              _cookieManager.put(uri, cookies);
            } catch (IOException x) {
              logger.debug("", x);
            }
          }

          @Override
          public void onComplete(Result result) {
            synchronized (LongPollingTransport.this) {
              _requests.remove(result.getRequest());
            }

            if (result.isFailed()) {
              listener.onFailure(result.getFailure(), messages);
              return;
            }

            Response response = result.getResponse();
            int status = response.getStatus();
            if (status == HttpStatus.OK_200) {
              String content = getContentAsString();
              if (content != null && content.length() > 0) {
                try {
                  List<Message.Mutable> messages = parseMessages(content);
                  debug("Received messages {}", messages);
                  for (Message.Mutable message : messages) {
                    if (message.isSuccessful()
                        && Channel.META_CONNECT.equals(message.getChannel())) {
                      Map<String, Object> advice = message.getAdvice();
                      if (advice != null && advice.get("timeout") != null) _advice = advice;
                    }
                  }
                  listener.onMessages(messages);
                } catch (ParseException x) {
                  listener.onFailure(x, messages);
                }
              } else {
                Map<String, Object> failure = new HashMap<>(2);
                // Convert the 200 into 204 (no content)
                failure.put("httpCode", 204);
                TransportException x = new TransportException(failure);
                listener.onFailure(x, messages);
              }
            } else {
              Map<String, Object> failure = new HashMap<>(2);
              failure.put("httpCode", status);
              TransportException x = new TransportException(failure);
              listener.onFailure(x, messages);
            }
          }
        });
  }
 public boolean sendMeta(ServerSession to, ServerMessage.Mutable message) {
   if (Channel.META_CONNECT.equals(message.getChannel())) {
     sendMetas.add(message);
   }
   return true;
 }