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