Code Monkey home page Code Monkey logo

pushy's Introduction

pushy

Build/test Maven Central

Pushy is a Java library for sending APNs (iOS, macOS, and Safari) push notifications.

Pushy sends push notifications using Apple's HTTP/2-based APNs protocol and supports both TLS and token-based authentication. It distinguishes itself from other push notification libraries with a focus on thorough documentation, asynchronous operation, and design for industrial-scale operation. With Pushy, it's easy and efficient to maintain multiple parallel connections to the APNs gateway to send large numbers of notifications to many different applications ("topics").

We believe that Pushy is already the best tool for sending APNs push notifications from Java applications, and we hope you'll help us make it even better via bug reports and pull requests.

If you need a simple GUI application for sending push notifications for development or testing purposes, you might also be interested in Pushy's sister project, Pushy Console.

Quick links

Getting Pushy

If you use Maven, you can add Pushy to your project by adding the following dependency declaration to your POM:

<dependency>
    <groupId>com.eatthepath</groupId>
    <artifactId>pushy</artifactId>
    <version>0.15.4</version>
</dependency>

If you don't use Maven (or something else that understands Maven dependencies, like Gradle), you can download Pushy as a .jar file and add it to your project directly. You'll also need to make sure you have Pushy's runtime dependencies on your classpath. They are:

Pushy itself requires Java 8 or newer to build and run. While not required, users may choose to use netty-native as an SSL provider for enhanced performance. To use a native provider, make sure netty-tcnative is on your classpath. Maven users may add a dependency to their project as follows:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-tcnative-boringssl-static</artifactId>
    <version>2.0.62.Final</version>
    <scope>runtime</scope>
</dependency>

Authenticating with the APNs server

Before you can get started with Pushy, you'll need to do some provisioning work with Apple to register your app and get the required certificates or signing keys (more on these shortly). For details on this process, please see the Registering Your App with APNs section of Apple's UserNotifications documentation. Please note that there are some caveats, particularly under macOS 10.13 (El Capitan).

Generally speaking, APNs clients must authenticate with the APNs server by some means before they can send push notifications. Currently, APNs (and Pushy) supports two authentication methods: TLS-based authentication and token-based authentication. The two approaches are mutually-exclusive; you'll need to pick one or the other for each client.

TLS authentication

In TLS-based authentication, clients present a TLS certificate to the server when connecting, and may send notifications to any "topic" named in the certificate. Generally, this means that a single client can only send push notifications to a single receiving app.

Once you've registered your app and have the requisite certificates, the first thing you'll need to do to start sending push notifications with Pushy is to create an ApnsClient. Clients using TLS authentication need a certificate and private key to authenticate with the APNs server. The most common way to store the certificate and key is in a password-protected PKCS#12 file (you'll wind up with a password-protected .p12 file if you follow Apple's instructions at the time of this writing). To create a client that will use TLS-based authentication:

final ApnsClient apnsClient = new ApnsClientBuilder()
        .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
        .setClientCredentials(new File("/path/to/certificate.p12"), "p12-file-password")
        .build();

Token authentication

In token-based authentication, clients still connect to the server using a TLS-secured connection, but do not present a certificate to the server when connecting. Instead, clients include a cryptographically-signed token with each notification they send (don't worry—Pushy handles this for you automatically). Clients may send push notifications to any "topic" for which they have a valid signing key.

To get started with a token-based client, you'll need to get a signing key (also called a private key in some contexts) from Apple. Once you have your signing key, you can create a new client:

final ApnsClient apnsClient = new ApnsClientBuilder()
        .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
        .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File("/path/to/key.p8"),
                "TEAMID1234", "KEYID67890"))
        .build();

Sending push notifications

Pushy's APNs clients maintain an internal pool of connections to the APNs server and create new connections on demand. As a result, clients do not need to be started explicitly. Regardless of the authentication method you choose, once you've created a client, it's ready to start sending push notifications. At minimum, push notifications need a device token (which identifies the notification's destination device and is a distinct idea from an authentication token), a topic, and a payload.

final SimpleApnsPushNotification pushNotification;

{
    final ApnsPayloadBuilder payloadBuilder = new SimpleApnsPayloadBuilder();
    payloadBuilder.setAlertBody("Example!");

    final String payload = payloadBuilder.build();
    final String token = TokenUtil.sanitizeTokenString("<efc7492 bdbd8209>");

    pushNotification = new SimpleApnsPushNotification(token, "com.example.myApp", payload);
}

Pushy includes a SimpleApnsPayloadBuilder, and payload builders based on Gson and Jackson are available as separate modules. APNs payloads are just JSON strings, and callers may produce payloads by the method of their choice; while Pushy's payload builders may be convenient, callers are not obligated to use them.

The process of sending a push notification is asynchronous; although the process of sending a notification and getting a reply from the server may take some time, the client will return a CompletableFuture right away. You can use that CompletableFuture to track the progress and eventual outcome of the sending operation. Note that sending a notification returns a PushNotificationFuture, which is a subclass of CompletableFuture that always holds a reference to the notification that was sent.

final PushNotificationFuture<SimpleApnsPushNotification, PushNotificationResponse<SimpleApnsPushNotification>>
    sendNotificationFuture = apnsClient.sendNotification(pushNotification);

The CompletableFuture will complete in one of three circumstances:

  1. The gateway accepts the notification and will attempt to deliver it to the destination device.
  2. The gateway rejects the notification; this should be considered a permanent failure, and the notification should not be sent again. Additionally, the APNs gateway may indicate a timestamp at which the destination token became invalid. If that happens, you should stop trying to send any notification to that token unless the token has been re-registered since that timestamp.
  3. The CompletableFuture fails with an exception. This should generally be considered a temporary failure, and callers should try to send the notification again when the problem has been resolved.

An example:

try {
    final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse =
            sendNotificationFuture.get();

    if (pushNotificationResponse.isAccepted()) {
        System.out.println("Push notification accepted by APNs gateway.");
    } else {
        System.out.println("Notification rejected by the APNs gateway: " +
                pushNotificationResponse.getRejectionReason());

        pushNotificationResponse.getTokenInvalidationTimestamp().ifPresent(timestamp -> {
            System.out.println("\t…and the token is invalid as of " + timestamp);
        });
    }
} catch (final ExecutionException e) {
    System.err.println("Failed to send push notification.");
    e.printStackTrace();
}

It's important to note that CompletableFuture has affordances for scheduling additional tasks to run when an operation is complete. Waiting for each individual push notification is inefficient in practice, and most users will be better served by adding follow-up tasks to the CompletableFuture instead of blocking until it completes. As an example:

sendNotificationFuture.whenComplete((response, cause) -> {
    if (response != null) {
        // Handle the push notification response as before from here.
    } else {
        // Something went wrong when trying to send the notification to the
        // APNs server. Note that this is distinct from a rejection from
        // the server, and indicates that something went wrong when actually
        // sending the notification or waiting for a reply.
        cause.printStackTrace();
    }
});

All APNs clients—even those that have never sent a message—may allocate and hold on to system resources, and it's important to release them. APNs clients are intended to be persistent, long-lived resources; you definitely don't need to shut down a client after sending a notification (or even batch of notifications), but you'll want to shut down your client (or clients) when your application is shutting down:

final CompletableFuture<Void> closeFuture = apnsClient.close();

When shutting down, clients will wait for all sent-but-not-acknowledged notifications to receive a reply from the server. Notifications that have been passed to sendNotification but not yet sent to the server (i.e. notifications waiting in an internal queue) will fail immediately when disconnecting. Callers should generally make sure that all sent notifications have been acknowledged by the server before shutting down.

Performance and best practices

Making the most of your system resources for high-throughput applications always takes some effort. To guide you through the process, we've put together a wiki page covering some best practices for using Pushy. All of these points are covered in much more detail on the wiki, but in general, our recommendations are:

  • Treat ApnsClient instances as long-lived resources
  • Add follow-up tasks to CompletableFutures if you want to track the status of your push notifications
  • Use a flow control strategy to avoid enqueueing push notifications faster than the server can respond
  • Choose a number of threads and concurrent connections that balances CPU time and network throughput

Metrics

Pushy includes an interface for monitoring metrics that provide insight into clients' behavior and performance. You can write your own implementation of the ApnsClientMetricsListener interface to record and report metrics. We also provide metrics listeners that gather and report metrics using the Dropwizard Metrics library and using the Micrometer application monitoring facade as separate modules. To begin receiving metrics, set a listener when building a new client:

final ApnsClient apnsClient = new ApnsClientBuilder()
        .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
        .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File("/path/to/key.p8"),
                "TEAMID1234", "KEYID67890"))
        .setMetricsListener(new MyCustomMetricsListener())
        .build();

Please note that the metric-handling methods in your listener implementation should never call blocking code. It's appropriate to increment counters directly in the handler methods, but calls to databases or remote monitoring endpoints should be dispatched to separate threads.

Using a proxy

If you need to use a proxy for outbound connections, you may specify a ProxyHandlerFactory when building your ApnsClient instance. Concrete implementations of ProxyHandlerFactory are provided for HTTP, SOCKS4, and SOCKS5 proxies.

An example:

final ApnsClient apnsClient = new ApnsClientBuilder()
    .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
    .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File("/path/to/key.p8"),
            "TEAMID1234", "KEYID67890"))
    .setProxyHandlerFactory(new Socks5ProxyHandlerFactory(
        new InetSocketAddress("my.proxy.com", 1080), "username", "password"))
    .build();

If using HTTP proxies configured via JVM system properties, you can also use:

final ApnsClient apnsClient = new ApnsClientBuilder()
    .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
    .setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File("/path/to/key.p8"),
            "TEAMID1234", "KEYID67890"))
    .setProxyHandlerFactory(HttpProxyHandlerFactory.fromSystemProxies(
            ApnsClientBuilder.DEVELOPMENT_APNS_HOST))
    .build();

Logging

Pushy uses SLF4J for logging. If you're not already familiar with it, SLF4J is a facade that allows users to choose which logging library to use at deploy time by adding a specific "binding" to the classpath. To avoid making the choice for you, Pushy itself does not depend on any SLF4J bindings; you'll need to add one on your own (either by adding it as a dependency in your own project or by installing it directly). If you have no SLF4J bindings on your classpath, you'll probably see a warning that looks something like this:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

For more information, see the SLF4J user manual.

Pushy uses logging levels as follows:

Level Events logged
error Serious, unrecoverable errors; recoverable errors that likely indicate a bug in Pushy
warn Serious, but recoverable errors; errors that may indicate a bug in caller's code
info Important lifecycle events
debug Minor lifecycle events; expected exceptions
trace Individual IO operations

Using a mock server

Pushy includes a mock APNs server that callers may use in integration tests and benchmarks. It is not necessary to use a mock server (or any related classes) in normal operation.

To build a mock server, callers should use a MockApnsServerBuilder. All servers require a PushNotificationHandler (built by a PushNotificationHandlerFactory provided to the builder) that decides whether the mock server will accept or reject each incoming push notification. Pushy includes an AcceptAllPushNotificationHandlerFactory that is helpful for benchmarking and a ValidatingPushNotificationHandlerFactory that may be helpful for integration testing.

Callers may also provide a MockApnsServerListener when building a mock server; listeners are notified whenever the mock server accepts or rejects a notification from a client.

License and status

Pushy is available under the MIT License.

The current version of Pushy is 0.15.4. It's fully functional and widely used in production environments, but the public API may change significantly before a 1.0 release.

pushy's People

Contributors

arunan-interlink avatar b0rengar avatar dant3 avatar dckcode avatar eager avatar fairenough83 avatar iryndin avatar itripn avatar jchambers avatar leelynne avatar liuchong avatar louise-scully avatar mattozer avatar matzew avatar mohamedhafez avatar netofontes avatar nowheresly avatar panchenko avatar pankajsinghal avatar petrdvorak avatar s1g53gv avatar scienjus avatar sebfz1 avatar simon04 avatar stepheno avatar sunng87 avatar turtleship avatar u-238 avatar xuwei-k avatar ylgrgyq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pushy's Issues

SSLException is thrown sometimes when SSL is already valid.

Hi,

I am firing up 6 instances of PushManger in my current project. It happens rarely, but SSLException is thrown when SSL is actually valid. I tried to re-produce it, but I was unable to because I couldn't find any apparent pattern where such incorrect exception was raised.

I think it could be either a bug in

  • how SSLContext is created or
  • Netty itself.

Either way, I think it would be better to tell users of pushy just log the SSLException and NOT shutdown push mangers.

Also, I was wondering if any other users of pushy has experienced this issue.

Thanks!

Tomcat Memory Leaks on netty

hi,

I think this is what I did very similar to this thread:
#29

On the current pushy 0.3, it is Spring 4.0 using Tomcat 7.0.53 with jdk1.7.0_55 x64.
Already put Thread.sleep(2000) after pushManager.shutdown():

INFO: Push manager shutting down.
Apr 21, 2014 11:50:40 AM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [/xxxapps] created a ThreadLocal with key of type [io.netty.channel.ChannelHandlerAdapter$1] (value [io.netty.channel.ChannelHandlerAdapter$1@52d67995]) and a value of type [java.util.WeakHashMap] (value [{class com.relayrides.pushy.apns.ApnsConnection$1=true}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
Apr 21, 2014 11:50:40 AM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [/xxxapps] created a ThreadLocal with key of type [io.netty.util.internal.ThreadLocalRandom$2] (value [io.netty.util.internal.ThreadLocalRandom$2@7ec84faf]) and a value of type [io.netty.util.internal.ThreadLocalRandom] (value [io.netty.util.internal.ThreadLocalRandom@10e7ab30]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
Apr 21, 2014 11:50:40 AM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
SEVERE: The web application [/xxxapps] created a ThreadLocal with key of type [io.netty.util.Recycler$1] (value [io.netty.util.Recycler$1@5997e4fb]) and a value of type [io.netty.util.Recycler.Stack] (value [io.netty.util.Recycler$Stack@33a48b1]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

Tried several times with the same SEVERE memory leek alerts there.

I thought it could be harmless, but I am not familiar with netty framework. I have to make sure otherwise I need to restart tomcat.

Any suggestions?
I would see if there is a listener to remove the netty from the Tomcat context (eg. on web.xml).

Many Thanks

shutdown with timeout closes socket immediately and waits after

While testing pushy, I am doing (simplified version):

pushManager.start();
pushManager.enqueuePushNotification(notification);
List<ApnsPushNotification> unsent = pushManager.shutdown(3000);

I know this is not the right way to use it and that I should keep the pushManager started for much longer. I'm just testing how shutdown with timeout behaves, and it looks like a weird behavior to me:

[2013-11-19 09:08:05,252] [DEBUG] [SQSEventListener-447537958] [] [] SimplePushManagerProvider:100 Starting pushManager
[2013-11-19 09:08:05,271] [DEBUG] [SQSEventListener-447537958] [] [] SimplePushManagerProvider:103 Started pushManager
[2013-11-19 09:08:05,272] [DEBUG] [SQSEventListener-447537958] [] [] PushyAPNSSendServiceImpl:116 Sending push notification (token=f44edf678a36a3e145e0eeb489925d996aacabeefb9f6e078f0299009b515583, payload={"aps":{},"whatever":"blabla"})
[2013-11-19 09:08:05,274] [DEBUG] [SQSEventListener-447537958] [] [] SimplePushManagerProvider:57 Stopping pushManager
[2013-11-19 09:08:05,472] [DEBUG] [pool-1-thread-3] [] [] APNSMockServer:145 EOF received while reading command
[2013-11-19 09:08:05,472] [INFO] [pool-1-thread-3] [] [] APNSMockServer:241 Closing socket...
[2013-11-19 09:08:07,675] [DEBUG] [SQSEventListener-447537958] [] [] SimplePushManagerProvider:62 Stopped pushManager (unsent=[SimpleApnsPushNotification [token=f44edf678a36a3e145e0eeb489925d996aacabeefb9f6e078f0299009b515583, payload={"aps":{},"whatever":"blabla"}, expiration=null]])

I would expect it to have enough time in 3 seconds to drain the buffer, given that it contains just one notification...

Errant shutdown notification causing mass resends

One of our machines running Pushy got some shutdown rejections. We got a normal log message first:
[Netty-worker-1] com.relayrides.pushy.apns.ApnsClientThread APNs gateway rejected notification with sequence number 1135 from ApnsClientThread-3 (SHUTDOWN).

Then six minutes later we got another rejection log message:
[Netty-worker-0] com.relayrides.pushy.apns.ApnsClientThread APNs gateway rejected notification with sequence number 0 from ApnsClientThread-3 (SHUTDOWN).

Since the sequence number was zero it caused a resend of the 1135 notifications in the queue all at once.

We don't have a ton of APNS volume right now so the APNS notification for sequence number zero was around 2 days earlier.

First, any thoughts as to the cause? It would appear the Netty call back was hanging around for at least two days.
Second, any remediations? Can we timeout the callbacks?

Link failure detection [question]

Hi, we are using a long running pushy 0.3 connection for apple push and it works great by far. But one thing I'm not quite confident about is the link failure detection.

As you may know, there could be a situation when the connection is broken without a FIN sent to client (e.g. net wire unplugged). Then there's no way to know the connection failure on pushy side. Pushy will still hold the ApnsConnection, and Netty's writeAndFlush will always success in both handler and its listener. However, no data will actually be sent to APNS.

Basically, we used heart-beat packet to detect failure in a long-live connection. However, Apple doesn't provide such kind of packet in their protocol. And even worse, there's no response for a success call. So we are totally unaware of the packets' state.

Although we haven't got this issue, but I'm afraid it's possible. So I just want to know if any of you and pushy users had this issue, and how do you deal with that? Do you keep the connection in your application, or just close it after some time ?

Thanks everybody!

Tests got scrambled in merges

This is absolutely my fault, but tests are a little screwy right now (I blame merge conflicts). PushManagerTest#testRegisterFailedNotificationListener(), for instance, appears to be testing connection with bad credentials.

Pushy example for beginner

Hi,
Good day.

I have been trying to use Pushy & so far have not been successfully getting the notification out.

Do you have any example?

So far I have tried on the steps given in gitHub as well as in http://relayrides.github.io/pushy/

Pushy v0.2 doesn't have the class PushManagerFactory.java while the current master in github support different arguements in PushManager() class.

The attached files are what I have tried for both of the version.
https://www.dropbox.com/s/yyqra0pxnrndrj2/Mirage_Push_master.java
https://www.dropbox.com/s/mjusx7xfcgtavya/Mirage_Push_v0.2.java

Besides that, there are warnings (as listed below) when I run the program.
log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

Regards,
LK

Request to make ApnsPushNotificationEncoder and ApnsErrorHandler static public

Background:
The project I'm working uses a jms queue system built in and I have to use it. I'd like to use as much of pushy as I can, but I can't use your queue implementation. I need pushy to be more synchronous. I push a message to pushy and it doesn't return until it's written it over the wire. (Or throw an exception).

Request:
By making the following classes to be public (and static), I can reuse them

ApnsClientThread.ApnsPushNotificationEncoder
ApnsClientThread.ApnsErrorHandler
SslHandlerUtils
SentNotificationBuffer
RejectedNotification

What's the correct way to use `PushManager.shutdown()`?

README.md says "When you're done sending push notifications, make sure to shut down the PushManager", but there is no way to know when the sending is done, since enqueueing a notification and its actual sending aren't synchronous.

Shutting down the push manager immediately after enqueueing sometimes results in notifications not being sent, so I was wondering, what's the correct moment to invoke shutdown() and, in general, to use the PushManager?

I currently implemented a helper class that uses it as a singleton and basically never shuts it down, but I guess it would be better to shut it down when the notifications queue is empty and restart it when a new notification needs to be sent; however, the API doesn't have a method to get the queue status.

What am I missing about the "correct" way to use it, then?

ConnectFuture blocked forever in FeebackServiceClient

First of all, we are using 0.3 from maven central.

Yesterday we had some network issue and after that we find one of our worker threads blocked at FeedbackServiceClient,getExpiredTokens, line 216. Since it's a synchronized method, a lot of workers threads are also blocked.

"clojure-agent-send-off-pool-12" prio=10 tid=0x00007facb9369800 nid=0xf32 in Object.wait() [0x00007fac0f5f3000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at io.netty.util.concurrent.DefaultPromise.await(DefaultPromise.java:260)
- locked <0x00000000df75f2f8> (a io.netty.channel.DefaultChannelPromise)
at io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:129)
at io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:28)
at com.relayrides.pushy.apns.FeedbackServiceClient.getExpiredTokens(FeedbackServiceClient.java:216)
- locked <0x00000000d1737820> (a com.relayrides.pushy.apns.FeedbackServiceClient)
at com.relayrides.pushy.apns.PushManager.getExpiredTokens(PushManager.java:526)

This seems to be a Netty bug because after CONNECT_TIMEOUT, the ChannelFuture should be resolved with exception.

However, for Pushy, I'm afraid it's a little dangerous to call await() without a timeout argument. The timeout parameter is for socket read only, however, connection establishment and SSL handshake would cost more time.

I'll send a pull request for this soon.

Optimize APNs payload construction

To make sure an APNs payload fits within a certain size, we repeatedly regenerate the payload JSON object with different message body lengths. Our current approach could be improved in two important ways:

  1. Just regenerate the message body string instead of the entire payload
  2. Use a binary search instead of a linear walk to seek to the right message body length (maybe?)

Client keeps connecting when SSL certificate is missing

ApnsClientThread keeps opening connections when the SSL certificate is missing. I believe it is caused by the continue on this catch:

try {
  finishedConnecting = this.connectOrContinueConnecting();
} catch (InterruptedException e) {
  continue;
}

Question - blocking behavior of getExpiredTokens()

A question about getExpiredTokens:

I see in the implementation that it opens a channel and wait for it to be closed (and, in the nio eventloopgroup threads, keep reading feedback entries and add them to expiredTokens).

However, I have seen sometimes APNS feedback service not closing the socket and leaving it open until there is some feedback to write, and only then close it (I had to add a read-timeout to java-apns library because of that).

How will pushy behave if that happens?

High CPU usage when using sandbox certificate in production environment

Hello, we are using pushy 0.3 . we were experiencing very high CPU usage . After investigating the problem, we found out that it was caused by a netty thread that keeps looping even though there are no notifications being pushed consuming 30 to 40 % CPU. Apparently it's a known issue in netty netty/netty#327 . We are using JDK 1.7u55 , do you think there's a solution to this problem, or perhaps a way around it ?

"nioEventLoopGroup-5-1" prio=10 tid=0x08ffec00 nid=0x22ec runnable [0x89cca000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:215)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
- locked <0xa0dcb158> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0xa0dcb178> (a java.util.Collections$UnmodifiableSet)
- locked <0xa0dcb118> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:596)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:306)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:101)
at java.lang.Thread.run(Thread.java:619)

Thanks

Expose size of the push manager's queue

When adding messages asynchronously, the entire list of messages (~200k) is added to the queue almost immediately. Whilst this is ok just now, it would be nice to be able to choose to wait to enqueue more messages once the size of the queue had reduced to an acceptable size. Exposing the size of the queue would allow this to happen and would help prevent it growing too large, which could result in out of memory errors.

Push Notifications are not being resent after I/O problems are resolved.

I disabled any connection to the internet. By doing this while trying to push one notification i get the following error:

2013-11-11 19:18:51,928 | push | DEBUG | ApnsClientThread
|ApnsClientThread-1 beginning connection process.
2013-11-11 19:18:51,939 | push | ERROR | ApnsClientThread
|ApnsClientThread-1 failed to connect to APNs gateway.
java.nio.channels.UnresolvedAddressException: null

Pushy tries then infinitely to establish a connection to APNS.

I enabled the connection to the internet: Now Pushy logs following:

2013-11-11 19:18:51,939 | push | DEBUG | ApnsClientThread
|ApnsClientThread-1 beginning connection process.
2013-11-11 19:18:54,272 | push | DEBUG | ApnsClientThread
|ApnsClientThread-1 connected.
2013-11-11 19:18:54,273 | push | DEBUG | ApnsClientThread
|ApnsClientThread-1 waiting for TLS handshake.
2013-11-11 19:18:55,052 | push | DEBUG | ApnsClientThread
|ApnsClientThread-1 successfully completed TLS handshake.

So up to now everything is working like predicted. Pushy managed to reconnect to the APNS server after internet connection was active again.

Nevertheless, the push notification which was sent while the internet connection was inactive, is not being resent. It seems like the push notification was never enqueued. I am not getting any error here, still the old push notification gets lost forever. All other new push notification requests are being sent to APNS successfully.

Thank you a lot for your time and I hope I was clear enough to describe the issue i am facing.

Regards,
Henri

Losing invalid token message if a write exception occurs

Scenerio: Keep writing invalid device notifications to apple, eventually a write error occurs, but we don't get a RejectedNotification message.

In ApnsClientThread: run()

this.channel.write(sendableNotification).addListener(new GenericFutureListener<ChannelFuture>() {
    public void operationComplete(final ChannelFuture future) {
        if (future.cause() != null) {
             if (log.isTraceEnabled()) {
                  trace(String.format("%s failed to write notification %s", threadName, sendableNotification), future.cause());
              }

              reconnect();

The problem with netty is that if an exception occurs on write, it will close the socket before the read occurs. Thereby losing the 6 byte packet from apple identifying the bad notification.

The good news is that the above listener is fired before netty tries to close the socket. By calling a read on the unsafe object of the channel we can force it to read from the socket, which retrieves the 6 byte packet and we receive a RejectionNotification

((NioUnsafe)future.channel().unsafe()).read();
reconnect();

And we can't call future.channel().read() because that just schedules a read event to occur in the next event loop which will happen after netty closes the socket.

Main ApnsClientThread terminates on connection failure

If an error occurs connecting to apple (ssl handshake, unknown host, etc), the main ApnsClientThread will terminate and never try to reconnect.

in run():

                case CONNECT: {
                    try {
                        this.connect();
                    } catch (InterruptedException e) {
                        continue;
                    }

In my case, a javax.net.ssl.SSLException is thrown and the thread permenantly exists.

A simple test case is to disable any connection to the internet. Then try to start the push manager. The thread will stop on first attempted connect.

Add the ability to provide a custom TrustManager while creating SSLContext

Currently SSLHandlerUtil provides the default Trust Managers. While this works with servers such as JBoss and SUN/Oracle JVM, we have had security exceptions (SSL trust) when using Pushy within other corporate server environments such as SAP NetWeaver and SAP JVM.

The only solution is to replace the SSLHandlerUtil class and provide a custom Trust Manager implementation that trusts the APNS certificates.

Enhancement request: Rather than having to patch Pushy for this, it would be better if a mechanism to provide a custom TrustManager is provided via ApnsEnvironment

Handle pipeline().get( SslHandler.class) returning null

I've come across a rare problem where channel.pipeline().get( SslHandler.class) can return null. The situation is that the channel connection completes but the ssl side fails (handshake error, ex. the remote side rejects the certificate). SslHandler will close the connection and then it (SslHandler) is removed from the pipeline. Thereby, it won't be possible to get a handshakeFuture

This code should check if this.channel.pipeline().get(SslHandler.class) is null, otherwise a null pointer exception could occur.

ApnsClientThread.java:423

this.handshakeFuture = this.channel.pipeline().get(SslHandler.class).handshakeFuture();

This should be rare since it's a tiny window between the channel opening and handshakeFuture is called, but yesterday I had it happen consistently happen.

npe thrown if shutdown state is run with a null channel

line 310: of ApnsClientThread.java
the Start of the "SHUTDOWN" state checks that this.channel is not null

if (this.channel != null && this.channel.isOpen()) {

However, later on in the same SHUTDOWN section:
line 325:

if (this.channel.closeFuture().cause() != null) {

It doesn't check that this.channel is not null before calling closeFuture().

Add timeout when querying APNs feedback service

Evidently, it's possible for the APNs feedback service to just hold the connection open indefinitely if there's no data to send. See #12 for the initial explanation of this problem. On closer inspection, the docs never actually claim that the feedback service will close the connection at all, so we should probably plan on taking matters into our own hands as a matter of general practice.

Apns Client Thread failed to complete TLS handshake with APNs gateway

Hi guys,

We are getting some APNS/SSL errors when using Pushy in production, and I don't know how to debug or diagnose what is going on. Any help you can offer would be much appreciated.

The exceptions are being logged with the following call-stacks, and we seem to be getting about one error per minute.

Is this familiar to anyone?

Thanks,

Marc

2014-01-31 13:33:55,364 [ApnsClientThread-2] ERROR ApnsClientThread  - ApnsClientThread-2 failed to complete TLS handshake with APNs gateway.
java.nio.channels.ClosedChannelException
2014-01-31 13:33:55,497 [ApnsClientThread-2] ERROR ApnsClientThread  - ApnsClientThread-2 failed to complete TLS handshake with APNs gateway.
javax.net.ssl.SSLException: Received fatal alert: certificate_unknown
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:190)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1429)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1397)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1563)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1023)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:837)
    at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:713)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:883)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:828)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:803)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:226)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:139)
    at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:338)
    at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:324)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:785)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:122)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
    at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:101)
    at java.lang.Thread.run(Thread.java:662)

javax.net.ssl.SSLException: Received fatal alert: certificate_unknown

I'm trying to send push notifications to iOS devices.
I've created a developer certificate for the APNS and bonded it with the application identifier.

I then proceeded to use Pushy to establish a connection to the APNS server, using the examples provided:

final PushManagerFactory<SimpleApnsPushNotification> pushManagerFactory =
        new PushManagerFactory<SimpleApnsPushNotification>(
                ApnsEnvironment.getSandboxEnvironment(),
                PushManagerFactory.createDefaultSSLContext( DEV_CERT_P12__PATH, DEV_CERT_P12__PASSWORD )
                );

final PushManager<SimpleApnsPushNotification> pushManager = pushManagerFactory.buildPushManager();

pushManager.registerFailedConnectionListener(new MyFailedConnectionListener());

pushManager.start();
public static class MyFailedConnectionListener implements FailedConnectionListener<SimpleApnsPushNotification> {

    public void handleFailedConnection(
            final PushManager<? extends SimpleApnsPushNotification> pushManager,
            final Throwable cause) {

        System.out.println("ERROR  -  "+ cause.toString());

        if (cause instanceof SSLHandshakeException) {

            // This is probably a permanent failure, and we should shut down
            // the PushManager.
        }
    }
}

I get this error: javax.net.ssl.SSLException: Received fatal alert: certificate_unknown.

I tried to ask on stackoverflow, by so far no luck: http://stackoverflow.com/questions/24101643/apple-apns-and-entrust-secure-ca-root-certificate
Could it be an issue with Pushy ?

Can pushy drain "public queue" as well when PushManager#shutdown() is called?

Hi, @jchambers

I understand that it is already documented that retryQueue will be drained when pushManager#shutdown() is called. However, it wasn't quite obvious to me from pushy documentation that publicQueue will NOT be drained until I wrote an integration test.

I was wondering why pushy doesn't drain publicQueue along with retryQueue.

For now, I am doing the below when shutdown() is called.

  1. check if publicQueue is empty.
  2. If publicQueue is not empty, sleep for half a second, and the go back to step 1.

I am repeating step 2 for a certain number of times just in case publicQueue never gets completely drained by pushy ( i.e. notifications in publicQueue gets dequeued and either gets sent or put into retryQueue).

Pseudo code is something as below

int currentSleepCount = 0;
while(currentSleepCount < maxSleepCount && !pushManager.getQueue().isEmpty()) {
     Thread.sleep(500);
     currentSleepCount++;
}
pushManager.shutdown();
eventLoopGroup.shutdownGracefully().await();

The above code is not ideal and quite hacky, but I wanted to ensure that my service processes all requests through Pushy before it is shutdown.

I thought about manually putting all notifications from publicQueue to RetryQueue, but it wasn't possible because RetryQueue was protected. Is there a better way to handle this?

To sum up, my questions are

  1. Why does pushy not drain publicQueue when shutdown() is called?
  2. Is there a plan to drain publicQueue at next iteration?
  3. Is there a recommended way of draining publicQueue when shutdown() is called?

Thanks!

Support custom properties with a different json structure

Currently when I add a custom property the payload looks as follows,

{
  "aps": {
    "alert": "Hello World"
  },
  "customProperties": {
    "I": "asdf"
  }
}

However I have a request to make it look like this,

{
  "aps": {
    "alert": "Hello World",
    "customProperties": {
      "I": "asdf"
    }
  }
}

Is this possible?

have to add Thread.sleep() to send apns notification

Hi there,

I am new to pushy. I found I have to add "Thread.sleep(750);" between add notification to queue and shutdown push manager to send notifications. Is this a bug or I did something wrong? Or should I aways wait a while before shutting down push manager? Following is my code snippet:

        final PushManagerFactory<SimpleApnsPushNotification> pushManagerFactory =
                new PushManagerFactory<SimpleApnsPushNotification>(
                        ApnsEnvironment.getSandboxEnvironment(),
                        PushManagerFactory.createDefaultSSLContext(
                                "mycertificate.p12", "password"));

        final PushManager<SimpleApnsPushNotification> pushManager =
                pushManagerFactory.buildPushManager();

        pushManager.start();

        final byte[] token = TokenUtil.tokenStringToByteArray(
                "mydevicetoken");

        final ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();

        payloadBuilder.setAlertBody("Ring ring, Neo.");

        final String payload = payloadBuilder.buildWithDefaultMaximumLength();

        pushManager.getQueue().put(new SimpleApnsPushNotification(token, payload));

        Thread.sleep(750);

        pushManager.shutdown();

tokenStringToByteArray throws NullPointerException in 2 cases

util#tokenStringToByteArray method throws NullPointerException in two cases.

  1. when tokenString is null.
  2. when the size of tokenString is odd.

Case 1 is obvious, but case 2 wasn't quite obvious until I saw a few NullPointerExceptions being logged in my server and looked into the method code.

It will be good if you can

  1. Add to documentation that the method will throw NullPointerException when the length of tokenString is odd.
  2. Define a custom exception ( ex> InvalidTokenStringException ), and let the method throw the custom exception when either ( tokenString is null or empty ) or ( the length of tokenString is odd ).

Personally, I would much prefer option #2 because it forces the user of Pushy to take of the exception. If you decide to go with Option #2, I am more than happy to make the changes and send in a pull request! :-)

@jchambers

Endless loop if certificate is expired

{ApnsClientThread-1} [c.r.pushy.apns.ApnsClientThread]: ApnsClientThread-1 failed to complete TLS handshake with APNs gateway.
javax.net.ssl.SSLException: Received fatal alert: certificate_expired

...

{ApnsClientThread-1} [c.r.pushy.apns.ApnsClientThread]: ApnsClientThread-1 failed to complete TLS handshake with APNs gateway.
javax.net.ssl.SSLException: Received fatal alert: certificate_expired

...

{ApnsClientThread-1} [c.r.pushy.apns.ApnsClientThread]: ApnsClientThread-1 failed to complete TLS handshake with APNs gateway.
javax.net.ssl.SSLException: Received fatal alert: certificate_expired

Netty isn't being shut down properly?

This issue requires more investigation, but here is a brief summary:

I have a Java web app running on Apache Tomcat 7.0.47 using JDK 1.7.45.
When you shut down Tomcat, the following errors appear:

SEVERE: The web application [] appears to have started a thread named [globalEventExecutor-1-2] but has failed to stop it. This is very likely to create a memory leak.

I know that the GlobalEventExecutor thread is created by Netty.

When Pushy is being shut down via PushManager.shutdown(), is Netty (and other resources) being shut down properly as well?

Spread connections across multiple gateways

According to the APNs docs:

You may establish multiple connections to the same gateway or to multiple gateway instances. If you need to send a large number of push notifications, spread them out over connections to several different gateways. This improves performance compared to using a single connection: it lets you send the push notifications faster, and it lets APNs deliver them faster.

No further guidance is given as to how that might happen, though. We had previously assumed that connection-spreading would happen automatically, but that does not appear to be the case in testing. It looks like there are a number of IPs associated with the gateway's DNS entry:

$ dig gateway.push.apple.com

; <<>> DiG 9.8.3-P1 <<>> gateway.push.apple.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64507
;; flags: qr rd ra; QUERY: 1, ANSWER: 9, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;gateway.push.apple.com.        IN  A

;; ANSWER SECTION:
gateway.push.apple.com. 181 IN  CNAME   gateway.push-apple.com.akadns.net.
gateway.push-apple.com.akadns.net. 3 IN A   17.172.232.33
gateway.push-apple.com.akadns.net. 3 IN A   17.172.232.30
gateway.push-apple.com.akadns.net. 3 IN A   17.172.233.149
gateway.push-apple.com.akadns.net. 3 IN A   17.172.232.35
gateway.push-apple.com.akadns.net. 3 IN A   17.172.232.231
gateway.push-apple.com.akadns.net. 3 IN A   17.172.233.155
gateway.push-apple.com.akadns.net. 3 IN A   17.172.233.147
gateway.push-apple.com.akadns.net. 3 IN A   17.172.238.221

To spread connections across multiple gateways, it seems like we'll need to do our own DNS resolution and connect directly by IP.

Change number of netty IO threads for feedback client

As of now, Netty will create 2*availableProcessers threads for the FeedbackServiceClient's bootstrap group. This is somewhat of a waste since pushy will only use 1 connection for the feedback service (it's obviously pointless to use more than one).

We can't set the io.netty.eventLoopThreads as we'll definitely want more than one for the regular apns client.

In FeedbackServiceClient constructor:

this.bootstrap.group(new NioEventLoopGroup(1));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.