Pushy 是一个用于发送 APN(iOS、macOS 和 Safari)推送通知的 Java 库。
Pushy 使用 Apple 基于 HTTP/2 的 APNs 协议发送推送通知,并支持 TLS 和基于令牌的身份验证。它与其他推送通知库的区别在于,它专注于完整的文档、异步操作和工业规模操作的设计。借助 Pushy,可以轻松高效地维护与 APNs 网关的多个并行连接,以向许多不同的应用程序(“主题”)发送大量通知。
我们相信 Pushy 已经是从 Java 应用程序发送 APN 推送通知的最佳工具,我们希望您能通过错误报告和拉取请求帮助我们让它变得更好。
如果您需要一个简单的 GUI 应用程序来发送推送通知以用于开发或测试目的,您可能也会对 Pushy 的姊妹项目 Pushy Console 感兴趣。
如果您使用 Maven,则可以通过将以下依赖项声明添加到 POM 来将 Pushy 添加到您的项目中:
< dependency >
< groupId >com.eatthepath</ groupId >
< artifactId >pushy</ artifactId >
< version >0.15.4</ version >
</ dependency >
如果您不使用 Maven(或其他了解 Maven 依赖项的工具,如 Gradle),您可以将 Pushy 下载为.jar
文件并将其直接添加到您的项目中。您还需要确保您的类路径上有 Pushy 的运行时依赖项。他们是:
Pushy 本身需要 Java 8 或更高版本才能构建和运行。虽然不是必需的,但用户可以选择使用 netty-native 作为 SSL 提供程序来增强性能。要使用本机提供程序,请确保 netty-tcnative 在您的类路径上。 Maven 用户可以向其项目添加依赖项,如下所示:
< dependency >
< groupId >io.netty</ groupId >
< artifactId >netty-tcnative-boringssl-static</ artifactId >
< version >2.0.62.Final</ version >
< scope >runtime</ scope >
</ dependency >
在开始使用 Pushy 之前,您需要向 Apple 进行一些配置工作,以注册您的应用程序并获取所需的证书或签名密钥(稍后将详细介绍)。有关此过程的详细信息,请参阅 Apple UserNotifications 文档的“使用 APNs 注册您的应用程序”部分。请注意,有一些警告,特别是在 macOS 10.13 (El Capitan) 下。
一般来说,APNs 客户端必须通过某种方式向 APNs 服务器进行身份验证,然后才能发送推送通知。目前,APNs(和 Pushy)支持两种身份验证方法:基于 TLS 的身份验证和基于令牌的身份验证。这两种方法是互斥的;您需要为每个客户选择一个或另一个。
在基于 TLS 的身份验证中,客户端在连接时向服务器提供 TLS 证书,并且可以向证书中指定的任何“主题”发送通知。一般来说,这意味着单个客户端只能将推送通知发送到单个接收应用程序。
注册应用程序并拥有必要的证书后,要开始使用 Pushy 发送推送通知,您需要做的第一件事就是创建一个ApnsClient
。使用 TLS 身份验证的客户端需要证书和私钥才能与 APNs 服务器进行身份验证。存储证书和密钥的最常见方法是存储在受密码保护的 PKCS#12 文件中(如果您在撰写本文时按照 Apple 的说明进行操作,您将得到一个受密码保护的 .p12 文件)。要创建将使用基于 TLS 的身份验证的客户端:
final ApnsClient apnsClient = new ApnsClientBuilder ()
. setApnsServer ( ApnsClientBuilder . DEVELOPMENT_APNS_HOST )
. setClientCredentials ( new File ( "/path/to/certificate.p12" ), "p12-file-password" )
. build ();
在基于令牌的身份验证中,客户端仍然使用 TLS 安全连接来连接到服务器,但在连接时不会向服务器提供证书。相反,客户端在发送的每个通知中都包含一个加密签名的令牌(不用担心,Pushy 会自动为您处理这个问题)。客户端可以向他们拥有有效签名密钥的任何“主题”发送推送通知。
要开始使用基于令牌的客户端,您需要从 Apple 获取签名密钥(在某些情况下也称为私钥)。获得签名密钥后,您可以创建新客户端:
final ApnsClient apnsClient = new ApnsClientBuilder ()
. setApnsServer ( ApnsClientBuilder . DEVELOPMENT_APNS_HOST )
. setSigningKey ( ApnsSigningKey . loadFromPkcs8File ( new File ( "/path/to/key.p8" ),
"TEAMID1234" , "KEYID67890" ))
. build ();
Pushy 的 APNs 客户端维护与 APNs 服务器的内部连接池,并根据需要创建新连接。因此,客户端不需要显式启动。无论您选择哪种身份验证方法,创建客户端后,就可以开始发送推送通知。推送通知至少需要一个设备令牌(它标识通知的目标设备,并且与身份验证令牌不同)、一个主题和一个有效负载。
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 包含SimpleApnsPayloadBuilder
,并且基于 Gson 和 Jackson 的有效负载构建器可作为单独的模块使用。 APNs 有效负载只是 JSON 字符串,调用者可以通过自己选择的方法生成有效负载;虽然 Pushy 的有效负载构建器可能很方便,但调用者没有义务使用它们。
发送推送通知的过程是异步的;尽管发送通知和从服务器获取回复的过程可能需要一些时间,但客户端会立即返回一个CompletableFuture
。您可以使用CompletableFuture
来跟踪发送操作的进度和最终结果。请注意,发送通知会返回PushNotificationFuture
,它是CompletableFuture
的子类,始终保存对已发送通知的引用。
final PushNotificationFuture < SimpleApnsPushNotification , PushNotificationResponse < SimpleApnsPushNotification >>
sendNotificationFuture = apnsClient . sendNotification ( pushNotification );
CompletableFuture
将在以下三种情况之一下完成:
CompletableFuture
因异常而失败。这通常应被视为暂时故障,调用者应在问题解决后尝试再次发送通知。一个例子:
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 ();
}
值得注意的是, CompletableFuture
可以在操作完成时安排额外的任务运行。在实践中,等待每个单独的推送通知效率很低,通过向CompletableFuture
添加后续任务而不是阻塞直到任务完成,可以更好地为大多数用户提供服务。举个例子:
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 ();
}
});
所有 APNs 客户端(即使是那些从未发送过消息的客户端)都可能分配并保留系统资源,释放它们非常重要。 APNs 客户端旨在成为持久的、长期存在的资源;您绝对不需要在发送通知(甚至批量通知)后关闭客户端,但您需要在应用程序关闭时关闭客户端(或多个客户端):
final CompletableFuture < Void > closeFuture = apnsClient . close ();
关闭时,客户端将等待所有已发送但未确认的通知以接收服务器的回复。已传递给sendNotification
但尚未发送到服务器的通知(即在内部队列中等待的通知)在断开连接时将立即失败。调用者通常应确保在关闭之前服务器已确认所有发送的通知。
充分利用系统资源来实现高吞吐量应用程序总是需要付出一些努力。为了指导您完成整个过程,我们整理了一个 wiki 页面,其中涵盖了使用 Pushy 的一些最佳实践。所有这些要点在 wiki 上都有更详细的介绍,但总的来说,我们的建议是:
ApnsClient
实例视为长期资源CompletableFutures
Pushy 包含一个用于监控指标的界面,可深入了解客户的行为和绩效。您可以编写自己的ApnsClientMetricsListener
接口实现来记录和报告指标。我们还提供指标监听器,使用 Dropwizard Metrics 库收集和报告指标,并使用 Micrometer 应用程序监控外观作为单独的模块。要开始接收指标,请在构建新客户端时设置侦听器:
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 ();
请注意,侦听器实现中的指标处理方法不应调用阻塞代码。直接在处理程序方法中增加计数器是合适的,但对数据库或远程监视端点的调用应分派到单独的线程。
如果您需要使用代理进行出站连接,您可以在构建ApnsClient
实例时指定ProxyHandlerFactory
。为 HTTP、SOCKS4 和 SOCKS5 代理提供了ProxyHandlerFactory
的具体实现。
一个例子:
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 ();
如果使用通过 JVM 系统属性配置的 HTTP 代理,您还可以使用:
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 ();
Pushy 使用 SLF4J 进行日志记录。如果您还不熟悉它,SLF4J 是一个外观,允许用户通过向类路径添加特定的“绑定”来选择在部署时使用哪个日志库。为了避免为您做出选择,Pushy 本身不依赖于任何 SLF4J 绑定;您需要自己添加一个(通过将其添加为您自己的项目中的依赖项或直接安装)。如果您的类路径上没有 SLF4J 绑定,您可能会看到如下所示的警告:
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.
有关更多信息,请参阅 SLF4J 用户手册。
Pushy 使用日志记录级别如下:
等级 | 记录的事件 |
---|---|
error | 严重的、不可恢复的错误;可恢复的错误可能表明 Pushy 中存在错误 |
warn | 严重但可恢复的错误;可能表明调用者代码中存在错误的错误 |
info | 重要的生命周期事件 |
debug | 较小的生命周期事件;预期的例外情况 |
trace | 单独的IO操作 |
Pushy 包含一个模拟 APNs 服务器,调用者可以在集成测试和基准测试中使用它。在正常操作中没有必要使用模拟服务器(或任何相关类)。
要构建模拟服务器,调用者应使用MockApnsServerBuilder
。所有服务器都需要一个PushNotificationHandler
(由提供给构建器的PushNotificationHandlerFactory
构建)来决定模拟服务器是否接受或拒绝每个传入的推送通知。 Pushy 包含一个有助于基准测试的AcceptAllPushNotificationHandlerFactory
和一个可能有助于集成测试的ValidatingPushNotificationHandlerFactory
。
调用者还可以在构建模拟服务器时提供MockApnsServerListener
;每当模拟服务器接受或拒绝来自客户端的通知时,侦听器都会收到通知。
Pushy 可在 MIT 许可证下使用。
Pushy 的当前版本是 0.15.4。它功能齐全并广泛应用于生产环境,但公共 API 在 1.0 版本发布之前可能会发生重大变化。