
cn.ipokerface.aps.PushClientBuilder Maven / Gradle / Ivy
Show all versions of apple-pns-java Show documentation
package cn.ipokerface.aps;
import cn.ipokerface.aps.auth.ApnsSignKey;
import cn.ipokerface.aps.utils.P12Util;
import io.netty.handler.codec.http2.Http2FrameLogger;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.*;
import io.netty.util.ReferenceCounted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.time.Duration;
/**
* Created by PokerFace
* Create Date 2020-08-24.
* Email: [email protected]
* Version 1.0.0
*
* Description:
*/
public class PushClientBuilder {
private static final Logger logger = LoggerFactory.getLogger(PushClientBuilder.class);
/**
* {@link cn.ipokerface.aps.PushClient.Environment}
*
*/
private PushClient.Environment environment;
// default topic
private String topic;
/**
* Sets the TLS credentials for the client under construction. Clients constructed with TLS credentials will use
* LS-based authentication when sending push notifications.
*/
private X509Certificate certificate;
private PrivateKey certificatePrivateKey;
private String certificatePrivateKeyPassword;
private ApnsSignKey signKey;
private Duration tokenExpiration = Duration.ofMinutes(50);
private File trustedServerCertificatePemFile;
private InputStream trustedServerCertificateInputStream;
private X509Certificate[] trustedServerCertificates;
// client build listener
private PushClientMetricsListener metricsListener;
private Duration connectionTimeout;
// idle time interval...
private Duration idleInterval = Duration.ofMinutes(1);
private Http2FrameLogger frameLogger;
private int poolCapacity = 1;
public PushClientBuilder() { }
/**
*
* {@link cn.ipokerface.aps.PushClient.Environment#Production}
* {@link cn.ipokerface.aps.PushClient.Environment#Sandbox}
*
* @see Sending Notification Requests to APNs
*
* @since 1.0.0
*
* @param env
* @return
*/
public PushClientBuilder environment(PushClient.Environment env) {
this.environment = env;
return this;
}
/**
* set default topic of this push client...
*
* @param topic
* @return
*/
public PushClientBuilder topic(String topic) {
this.topic = topic;
return this;
}
/**
*
Sets the TLS credentials for the client under construction using the contents of the given PKCS#12 file.
* Clients constructed with TLS credentials will use TLS-based authentication when sending push notifications. The
* PKCS#12 file must contain a certificate/private key pair.
*
* Clients may not have both TLS credentials and a signing key.
*
* @param p12FilePath a PKCS#12-formatted file containing the certificate and private key to be used to identify the
* client to the APNs server
* @param password the password to be used to decrypt the contents of the given PKCS#12 file; passwords may be
* blank (i.e. {@code ""}), but must not be {@code null}
*
* @throws SSLException if the given PKCS#12 file could not be loaded or if any other SSL-related problem arises
* when constructing the context
* @throws IOException if any IO problem occurred while attempting to read the given PKCS#12 file, or the PKCS#12
* file could not be found
*
* @return a reference to this builder
*
*/
public PushClientBuilder credential(String p12FilePath, String password) throws SSLException, IOException {
return this.credential(new FileInputStream(p12FilePath), password);
}
/**
* Sets the TLS credentials for the client under construction using the data from the given PKCS#12 input stream.
* Clients constructed with TLS credentials will use TLS-based authentication when sending push notifications. The
* PKCS#12 data must contain a certificate/private key pair.
*
* Clients may not have both TLS credentials and a signing key.
*
* @param p12FileInputStream an input stream to a PKCS#12-formatted file containing the certificate and private key to
* be used to identify the client to the APNs server
* @param password the password to be used to decrypt the contents of the given PKCS#12 file; passwords may be
* blank (i.e. {@code ""}), but must not be {@code null}
*
* @throws SSLException if the given PKCS#12 file could not be loaded or if any other SSL-related problem arises
* when constructing the context
* @throws IOException if any IO problem occurred while attempting to read the given PKCS#12 input stream
*
* @return a reference to this builder
*
*/
public PushClientBuilder credential(InputStream p12FileInputStream, String password) throws SSLException, IOException {
final X509Certificate x509Certificate;
final PrivateKey privateKey;
try {
final KeyStore.PrivateKeyEntry privateKeyEntry = P12Util.getFirstPrivateKeyEntryFromP12InputStream(p12FileInputStream, password);
final Certificate certificate = privateKeyEntry.getCertificate();
if (!(certificate instanceof X509Certificate)) {
throw new KeyStoreException("Found a certificate in the provided PKCS#12 file, but it was not an X.509 certificate.");
}
x509Certificate = (X509Certificate) certificate;
privateKey = privateKeyEntry.getPrivateKey();
} catch (final KeyStoreException e) {
throw new SSLException(e);
}
return this.clientCredentials(x509Certificate, privateKey, password);
}
/**
* Sets the TLS credentials for the client under construction. Clients constructed with TLS credentials will use
* TLS-based authentication when sending push notifications.
*
* Clients may not have both TLS credentials and a signing key.
*
* @param certificate the certificate to be used to identify the client to the APNs server
* @param privateKey the private key for the client certificate
* @param privateKeyPassword the password to be used to decrypt the private key; may be {@code null} if the private
* key does not require a password
*
* @return a reference to this builder
*
* @since 0.8
*/
public PushClientBuilder clientCredentials(final X509Certificate certificate, final PrivateKey privateKey, final String privateKeyPassword) {
this.certificate = certificate;
this.certificatePrivateKey = privateKey;
this.certificatePrivateKeyPassword = privateKeyPassword;
return this;
}
/**
* Sets the signing key for the client under construction. Clients constructed with a signing key will use
* token-based authentication when sending push notifications.
*
* Clients may not have both a signing key and TLS credentials.
*
* @param signKey the signing key to be used by the client under construction
*
* @return a reference to this builder
*
* @see ApnsSignKey#loadFromPkcs8File(File, String, String)
* @see ApnsSignKey#loadFromInputStream(InputStream, String, String)
*
*/
public PushClientBuilder signingKey(final ApnsSignKey signKey) {
this.signKey = signKey;
return this;
}
/**
* Sets the duration after which authentication tokens should expire and be regenerated from the signing key for
* clients using token-based authentication. Has no effect for clients using TLS-based authentication.
*
* At the time of writing, the APNs server will treat tokens as "expired" after 60 minutes. The default
* expiration duration for clients using token-based authentication is 50 minutes. Callers should not set a
* non-default value unless the upstream behavior changes.
*
* @param tokenExpiration the duration after which authentication tokens should expire
*
* @return a reference to this builder
*
* @since 0.13.11
*/
public PushClientBuilder tokenExpiration(final Duration tokenExpiration) {
this.tokenExpiration = tokenExpiration;
return this;
}
/**
* Sets the trusted certificate chain for the client under construction using the contents of the given PEM
* file. If not set (or {@code null}), the client will use the JVM's default trust manager.
*
* Callers will generally not need to set a trusted server certificate chain in normal operation, but may wish
* to do so for certificate pinning
* or connecting to a mock server for integration testing or benchmarking.
*
* @param certificatePemFile a PEM file containing one or more trusted certificates
*
* @return a reference to this builder
*
* @since 0.8
*/
public PushClientBuilder trustedServerCertificateChain(final File certificatePemFile) {
this.trustedServerCertificatePemFile = certificatePemFile;
return this;
}
/**
* Sets the trusted certificate chain for the client under construction using the contents of the given PEM
* input stream. If not set (or {@code null}), the client will use the JVM's default trust manager.
*
* Callers will generally not need to set a trusted server certificate chain in normal operation, but may wish
* to do so for certificate pinning
* or connecting to a mock server for integration testing or benchmarking.
*
* @param certificateInputStream an input stream to PEM-formatted data containing one or more trusted certificates
*
* @return a reference to this builder
*
* @since 0.8
*/
public PushClientBuilder trustedServerCertificateChain(final InputStream certificateInputStream) {
this.trustedServerCertificateInputStream = certificateInputStream;
return this;
}
/**
* Sets the trusted certificate chain for the client under construction. If not set (or {@code null}), the
* client will use the JVM's default trust manager.
*
* Callers will generally not need to set a trusted server certificate chain in normal operation, but may wish
* to do so for certificate pinning
* or connecting to a mock server for integration testing or benchmarking.
*
* @param certificates one or more trusted certificates
*
* @return a reference to this builder
*
*/
public PushClientBuilder trustedServerCertificateChain(final X509Certificate... certificates) {
this.trustedServerCertificates = certificates;
return this;
}
/**
* Sets the metrics listener for the client under construction. Metrics listeners gather information that describes
* the performance and behavior of a client, and are completely optional.
*
* @param metricsListener the metrics listener for the client under construction, or {@code null} if this client
* should not report metrics to a listener
*
* @return a reference to this builder
*
*/
public PushClientBuilder metricsListener(final PushClientMetricsListener metricsListener) {
this.metricsListener = metricsListener;
return this;
}
/**
* Sets the maximum amount of time, in milliseconds, that the client under construction will wait to establish a
* connection with the APNs server before the connection attempt is considered a failure.
*
* @param timeout the maximum amount of time to wait for a connection attempt to complete
*
* @return a reference to this builder
*
*/
public PushClientBuilder connectionTimeout(final Duration timeout) {
this.connectionTimeout = timeout;
return this;
}
/**
* Sets the amount of idle time (in milliseconds) after which the client under construction will send a PING frame
* to the APNs server. By default, clients will send a PING frame after an idle period of 1 minutes.
*
*
* @param idleInterval the amount of idle time after which the client will send a PING frame
*
* @return a reference to this builder
*
*/
public PushClientBuilder idleInterval(final Duration idleInterval) {
this.idleInterval = idleInterval;
return this;
}
/**
* Sets the HTTP/2 frame logger for the client under construction. HTTP/2 frame loggers log all HTTP/2 frames sent
* to or from the client to the logging system of your choice via SLF4J. Frame logging is extremely verbose and is
* recommended only for debugging purposes.
*
* @param frameLogger the frame logger to be used by the client under construction or {@code null} if the client
* should not log individual HTTP/2 frames
*
* @return a reference to this builder
*
* @see SLF4J
*
*/
public PushClientBuilder http2FrameLogger(Http2FrameLogger frameLogger) {
this.frameLogger = frameLogger;
return this;
}
/**
* capacity of pooled connection channels . default 1 if not set this property.
*
* @param capacity
* @return
*/
public PushClientBuilder poolCapacity(int capacity) {
this.poolCapacity = capacity;
return this;
}
public PushClient build() throws SSLException {
if (this.environment == null) {
throw new IllegalStateException("No APNs server address specified.");
}
if (this.certificate == null && this.certificatePrivateKey == null && this.signKey == null) {
throw new IllegalStateException("No client credentials specified; either TLS credentials (a " +
"certificate/private key) or an APNs signing key must be provided before building a client.");
} else if ((this.certificate != null || this.certificatePrivateKey != null) && this.signKey != null) {
throw new IllegalStateException("Clients may not have both a signing key and TLS credentials.");
}
final SslContext sslContext;
final SslProvider sslProvider;
if (OpenSsl.isAvailable()) {
logger.info("Native SSL provider is available; will use native provider.");
sslProvider = SslProvider.OPENSSL_REFCNT;
} else {
logger.info("Native SSL provider not available; will use JDK SSL provider.");
sslProvider = SslProvider.JDK;
}
final SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()
.sslProvider(sslProvider)
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE);
if (this.certificate != null && this.certificatePrivateKey != null) {
sslContextBuilder.keyManager(this.certificatePrivateKey, this.certificatePrivateKeyPassword, this.certificate);
}
if (this.trustedServerCertificatePemFile != null) {
sslContextBuilder.trustManager(this.trustedServerCertificatePemFile);
} else if (this.trustedServerCertificateInputStream != null) {
sslContextBuilder.trustManager(this.trustedServerCertificateInputStream);
} else if (this.trustedServerCertificates != null) {
sslContextBuilder.trustManager(this.trustedServerCertificates);
}
sslContext = sslContextBuilder.build();
try {
return new PushClient(environment,topic,sslContext,signKey, tokenExpiration, connectionTimeout, idleInterval,metricsListener, frameLogger, poolCapacity );
} finally {
if (sslContext instanceof ReferenceCounted) {
((ReferenceCounted) sslContext).release();
}
}
}
}