예제 #1
0
  private synchronized void sendMessage(ApnsNotification m, boolean fromBuffer)
      throws NetworkIOException {
    logger.debug("sendMessage {} fromBuffer: {}", m, fromBuffer);
    UILogger.getInstance().add("sendMessage " + m.toString() + " fromBuffer " + fromBuffer);
    if (delegate instanceof StartSendingApnsDelegate) {
      ((StartSendingApnsDelegate) delegate).startSending(m, fromBuffer);
    }

    int attempts = 0;
    while (true) {
      try {
        attempts++;
        Socket socket = getOrCreateSocket(fromBuffer);
        socket.getOutputStream().write(m.marshall());
        socket.getOutputStream().flush();
        cacheNotification(m);

        delegate.messageSent(m, fromBuffer);

        // logger.debug("Message \"{}\" sent", m);
        attempts = 0;
        break;
      } catch (IOException e) {
        System.out.println("IOException0" + e.toString());
        Utilities.close(socket);
        if (attempts >= RETRIES) {
          logger.error("Couldn't send message after " + RETRIES + " retries." + m, e);
          UILogger.getInstance()
              .add("Couldn't send message after " + RETRIES + " retries." + m.toString());
          delegate.messageSendFailed(m, e);
          Utilities.wrapAndThrowAsRuntimeException(e);
        }
        // The first failure might be due to closed connection (which in turn might be caused by
        // a message containing a bad token), so don't delay for the first retry.
        //
        // Additionally we don't want to spam the log file in this case, only after the second retry
        // which uses the delay.

        if (attempts != 1) {
          logger.info("Failed to send message " + m + "... trying again after delay", e);
          UILogger.getInstance()
              .add("Failed to send message " + m + "... trying again after delay" + e.toString());
          Utilities.sleep(DELAY_IN_MS);
        }
      }
    }
  }
예제 #2
0
 private void cacheNotification(ApnsNotification notification) {
   cachedNotifications.add(notification);
   while (cachedNotifications.size() > cacheLength) {
     cachedNotifications.poll();
     logger.debug("Removing notification from cache " + notification);
     UILogger.getInstance().add("Removing notification from cache " + notification);
   }
 }
예제 #3
0
  private synchronized Socket getOrCreateSocket(boolean resend) throws NetworkIOException {
    if (reconnectPolicy.shouldReconnect()) {
      logger.debug("Reconnecting due to reconnectPolicy dictating it");
      UILogger.getInstance().add("Reconnecting due to reconnectPolicy dictating it");
      Utilities.close(socket);
      socket = null;
    }

    if (socket == null || socket.isClosed()) {
      try {
        if (proxy == null) {
          socket = factory.createSocket(host, port);
          logger.debug("Connected new socket {}", socket);
          UILogger.getInstance().add("Connected new socket" + socket.toString());
        } else if (proxy.type() == Proxy.Type.HTTP) {
          TlsTunnelBuilder tunnelBuilder = new TlsTunnelBuilder();
          socket =
              tunnelBuilder.build(
                  (SSLSocketFactory) factory, proxy, proxyUsername, proxyPassword, host, port);
          logger.debug("Connected new socket through http tunnel {}", socket);
          UILogger.getInstance()
              .add("Connected new socket through http tunnel " + socket.toString());
        } else {
          boolean success = false;
          Socket proxySocket = null;
          try {
            proxySocket = new Socket(proxy);
            proxySocket.connect(new InetSocketAddress(host, port), connectTimeout);
            socket = ((SSLSocketFactory) factory).createSocket(proxySocket, host, port, false);
            success = true;
          } finally {
            if (!success) {
              Utilities.close(proxySocket);
            }
          }
          logger.debug("Connected new socket through socks tunnel {}", socket);
          UILogger.getInstance()
              .add("Connected new socket through socks tunnel " + socket.toString());
        }

        socket.setSoTimeout(readTimeout);
        socket.setKeepAlive(true);

        if (errorDetection) {
          monitorSocket(socket);
        }

        reconnectPolicy.reconnected();
        logger.debug("Made a new connection to APNS");
        UILogger.getInstance().add("Made a new connection to APNS");
      } catch (IOException e) {
        logger.error("Couldn't connect to APNS server", e);
        UILogger.getInstance().add("Couldn't connect to APNS server" + e.toString());
        // indicate to clients whether this is a resend or initial send
        throw new NetworkIOException(e, resend);
      }
    }
    return socket;
  }
예제 #4
0
 private synchronized void drainBuffer() {
   logger.debug("draining buffer");
   UILogger.getInstance().add("draining buffer");
   while (!notificationsBuffer.isEmpty()) {
     final ApnsNotification notification = notificationsBuffer.poll();
     try {
       sendMessage(notification, true);
     } catch (NetworkIOException ex) {
       // at this point we are retrying the submission of messages but failing to connect to APNS,
       // therefore
       // notify the client of this
       delegate.messageSendFailed(notification, ex);
     }
   }
 }
예제 #5
0
  private void monitorSocket(final Socket socket) {
    logger.debug("Launching Monitoring Thread for socket {}", socket);
    UILogger.getInstance().add("Launching Monitoring Thread for socket " + socket.toString());

    Thread t =
        threadFactory.newThread(
            new Runnable() {
              static final int EXPECTED_SIZE = 6;

              @SuppressWarnings("InfiniteLoopStatement")
              @Override
              public void run() {
                logger.debug("Started monitoring thread");
                UILogger.getInstance().add("Started monitoring thread");
                try {
                  InputStream in;
                  try {
                    in = socket.getInputStream();
                  } catch (IOException ioe) {
                    in = null;
                  }

                  byte[] bytes = new byte[EXPECTED_SIZE];
                  while (in != null && readPacket(in, bytes)) {
                    logger.debug("Error-response packet {}", Utilities.encodeHex(bytes));
                    UILogger.getInstance()
                        .add("Error-response packet " + Utilities.encodeHex(bytes));
                    // Quickly close socket, so we won't ever try to send push notifications
                    // using the defective socket.
                    Utilities.close(socket);

                    int command = bytes[0] & 0xFF;
                    if (command != 8) {
                      throw new IOException("Unexpected command byte " + command);
                    }
                    int statusCode = bytes[1] & 0xFF;
                    DeliveryError e = DeliveryError.ofCode(statusCode);

                    int id = Utilities.parseBytes(bytes[2], bytes[3], bytes[4], bytes[5]);

                    logger.debug("Closed connection cause={}; id={}", e, id);
                    UILogger.getInstance().add("Closed connection cause " + e.toString());
                    UILogger.getInstance().add("Closed connection id  " + id);
                    delegate.connectionClosed(e, id);

                    Queue<ApnsNotification> tempCache = new LinkedList<ApnsNotification>();
                    ApnsNotification notification = null;
                    boolean foundNotification = false;

                    while (!cachedNotifications.isEmpty()) {
                      notification = cachedNotifications.poll();
                      logger.debug(
                          "Candidate for removal, message id {}", notification.getIdentifier());
                      UILogger.getInstance()
                          .add("Candidate for removal, message id " + notification.getIdentifier());

                      if (notification.getIdentifier() == id) {
                        logger.debug("Bad message found {}", notification.getIdentifier());
                        UILogger.getInstance()
                            .add("Bad message found " + notification.getIdentifier());
                        foundNotification = true;
                        break;
                      }
                      tempCache.add(notification);
                    }

                    if (foundNotification) {
                      logger.debug(
                          "delegate.messageSendFailed, message id {}",
                          notification.getIdentifier());
                      UILogger.getInstance()
                          .add(
                              "delegate.messageSendFailed, message id "
                                  + notification.getIdentifier());
                      delegate.messageSendFailed(notification, new ApnsDeliveryErrorException(e));
                    } else {
                      cachedNotifications.addAll(tempCache);
                      int resendSize = tempCache.size();
                      logger.warn("Received error for message that wasn't in the cache...");
                      UILogger.getInstance()
                          .add("Received error for message that wasn't in the cache...");

                      if (autoAdjustCacheLength) {
                        cacheLength = cacheLength + (resendSize / 2);
                        delegate.cacheLengthExceeded(cacheLength);
                      }
                      logger.debug("delegate.messageSendFailed, unknown id");
                      UILogger.getInstance().add("delegate.messageSendFailed, unknown id");
                      delegate.messageSendFailed(null, new ApnsDeliveryErrorException(e));
                    }

                    int resendSize = 0;

                    while (!cachedNotifications.isEmpty()) {

                      resendSize++;
                      final ApnsNotification resendNotification = cachedNotifications.poll();
                      logger.debug("Queuing for resend {}", resendNotification.getIdentifier());
                      UILogger.getInstance()
                          .add("Queuing for resend " + resendNotification.getIdentifier());
                      notificationsBuffer.add(resendNotification);
                    }
                    logger.debug("resending {} notifications", resendSize);
                    UILogger.getInstance().add("resending {} notifications " + resendSize);
                    delegate.notificationsResent(resendSize);
                  }
                  logger.debug("Monitoring input stream closed by EOF");
                  UILogger.getInstance().add("Monitoring input stream closed by EOF");

                } catch (IOException e) {
                  // An exception when reading the error code is non-critical, it will cause another
                  // retry
                  // sending the message. Other than providing a more stable network connection to
                  // the APNS
                  // server we can't do much about it - so let's not spam the application's error
                  // log.
                  logger.info("Exception while waiting for error code", e);
                  UILogger.getInstance().add("Exception while waiting for error code " + e);
                  delegate.connectionClosed(DeliveryError.UNKNOWN, -1);
                } finally {
                  close();
                  drainBuffer();
                }
              }

              /**
               * Read a packet like in.readFully(bytes) does - but do not throw an exception and
               * return false if nothing could be read at all.
               *
               * @param in the input stream
               * @param bytes the array to be filled with data
               * @return true if a packet as been read, false if the stream was at EOF right at the
               *     beginning.
               * @throws IOException When a problem occurs, especially EOFException when there's an
               *     EOF in the middle of the packet.
               */
              private boolean readPacket(final InputStream in, final byte[] bytes)
                  throws IOException {
                final int len = bytes.length;
                int n = 0;
                while (n < len) {
                  try {
                    int count = in.read(bytes, n, len - n);
                    if (count < 0) {
                      throw new EOFException("EOF after reading " + n + " bytes of new packet.");
                    }
                    n += count;
                  } catch (IOException ioe) {
                    if (n == 0) return false;
                    throw new IOException("Error after reading " + n + " bytes of packet", ioe);
                  }
                }
                return true;
              }
            });
    t.start();
  }