All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.github.yawenok.apns.http2.impl.NettyApnsHttp2Connection Maven / Gradle / Ivy
package io.github.yawenok.apns.http2.impl;
import io.github.yawenok.apns.http2.concurrent.FutureCallback;
import io.github.yawenok.apns.http2.config.auth.JWTAuthConfig;
import io.github.yawenok.apns.http2.impl.initializer.Http2ClientInitializer;
import io.github.yawenok.apns.http2.impl.model.ResponseFuture;
import io.github.yawenok.apns.http2.impl.model.ResponseFutureCallback;
import io.github.yawenok.apns.http2.utils.P12Utils;
import io.github.yawenok.apns.http2.config.AuthConfig;
import io.github.yawenok.apns.http2.config.auth.TLSAuthConfig;
import io.github.yawenok.apns.http2.ApnsConstant;
import io.github.yawenok.apns.http2.config.ProxyConfig;
import io.github.yawenok.apns.http2.ApnsHttp2Connection;
import io.github.yawenok.apns.http2.enums.auth.AuthMode;
import io.github.yawenok.apns.http2.Notification;
import io.github.yawenok.apns.http2.NotificationResponse;
import io.github.yawenok.apns.http2.exceptions.AuthenticationException;
import io.github.yawenok.apns.http2.exceptions.ConnectionException;
import io.github.yawenok.apns.http2.utils.JWTUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.*;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import javax.net.ssl.SSLException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
/**
* Creates An APNs connection based on the HTTP/2 network protocol.
*
* @author yawen
*
* @see Communicating with APNs
*/
public class NettyApnsHttp2Connection implements ApnsHttp2Connection {
private String host;
private int port;
private String topic;
private String jwt;
private long scopeExpiration = 30 * 60 * 1000;
private long scopeBegin = 0;
private AuthConfig authConfig;
private Http2ClientInitializer http2ClientInitializer;
private Channel channel;
private EventLoopGroup workerGroup;
@Override
public void init(final String host, final int port, final AuthConfig authConfig, final ProxyConfig proxyConfig) throws AuthenticationException, ConnectionException {
this.host = host;
this.port = port;
this.authConfig = authConfig;
try {
if (authConfig.getAuthMode() == AuthMode.TLS) {
TLSAuthConfig tlsAuthConfig = (TLSAuthConfig) authConfig;
KeyStore keyStore = P12Utils.loadKeyStoreFromPCKS12File(tlsAuthConfig.getLicenceFile(), tlsAuthConfig.getPassword());
List identities = P12Utils.getIdentitiesFromKeyStore(keyStore);
this.topic = identities.get(0);
this.http2ClientInitializer = new Http2ClientInitializer(this.createSslContext(tlsAuthConfig.isJdkDefault(), keyStore, tlsAuthConfig.getPassword()), proxyConfig);
} else if (authConfig.getAuthMode() == AuthMode.JWT) {
JWTAuthConfig jwtAuthConfig = (JWTAuthConfig) authConfig;
this.jwt = JWTUtils.createJWT(jwtAuthConfig.getPrivateKeyFile(), jwtAuthConfig.getKeyId(), jwtAuthConfig.getTeamId());
this.scopeBegin = System.currentTimeMillis();
this.http2ClientInitializer = new Http2ClientInitializer(this.createSslContext(jwtAuthConfig.isJdkDefault(), jwtAuthConfig.getCaFile()), proxyConfig);
} else {
throw new AuthenticationException("AuthConfig can't get AuthMode!");
}
} catch (Exception e) {
throw new AuthenticationException("Init authentication error!", e);
}
try {
this.doConnect(host, port);
} catch (Exception e) {
throw new ConnectionException("Connect error!", e);
}
}
@Override
public void destroy() {
this.disConnect();
}
@Override
public Future sendNotification(final Notification notification) throws ConnectionException {
if (!this.isActive()) {
this.reActive();
}
final String uuid = UUID.randomUUID().toString();
final FullHttpRequest request = buildRequest(notification, uuid);
final ResponseFuture responseFuture = new ResponseFuture();
http2ClientInitializer.putResponseFuture(uuid, responseFuture);
try {
channel.writeAndFlush(request);
} catch (Exception e) {
http2ClientInitializer.cleanApns(uuid);
throw new ConnectionException("Channel is not active!");
}
return responseFuture;
}
@Override
public void sendNotification(final Notification notification, final FutureCallback callback) throws ConnectionException {
if (!this.isActive()) {
this.reActive();
}
final String uuid = UUID.randomUUID().toString();
final FullHttpRequest request = buildRequest(notification, uuid);
final ResponseFutureCallback responseFutureCallback = new ResponseFutureCallback(notification, callback);
http2ClientInitializer.putResponseFutureCallback(uuid, responseFutureCallback);
try {
channel.writeAndFlush(request);
} catch (Exception e) {
http2ClientInitializer.cleanApns(uuid);
throw new ConnectionException("Channel is not active!");
}
}
@Override
public boolean isActive() {
if (authConfig.getAuthMode() == AuthMode.JWT) {
// JWT only can use 1 hour for APNs
if (System.currentTimeMillis() - scopeBegin > scopeExpiration) {
return false;
}
}
if (channel != null) {
return channel.isActive();
}
return false;
}
@Override
public synchronized void reActive() throws ConnectionException {
if (authConfig.getAuthMode() == AuthMode.JWT) {
try {
JWTAuthConfig jwtAuthConfig = (JWTAuthConfig) authConfig;
jwt = JWTUtils.createJWT(jwtAuthConfig.getPrivateKeyFile(), jwtAuthConfig.getKeyId(), jwtAuthConfig.getTeamId());
scopeBegin = System.currentTimeMillis();
} catch (Exception e) {
throw new ConnectionException("Create a JWT error!", e);
}
}
this.disConnect();
try {
this.doConnect(host, port);
} catch (Exception e) {
throw new ConnectionException("Re connect error!", e);
}
}
private FullHttpRequest buildRequest(final Notification notification, final String uuid) {
// The request for Http1.1 will automatically converted to Http2.
final String uri = "https://" + host + "/3/device/" + notification.getToken().replace(" ", "");
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri, Unpooled.copiedBuffer(notification.getPayload().toString().getBytes()));
final HttpHeaders headers = request.headers();
headers.add(ApnsConstant.APNS_NOTIFICATION_ID, uuid);
if (authConfig.getAuthMode() == AuthMode.JWT) {
headers.add(ApnsConstant.APNS_AUTHORIZATION_HEADER, "bearer " + jwt);
}
if (notification.getTopic() != null) {
headers.add(ApnsConstant.APNS_NOTIFICATION_TOPIC, notification.getTopic());
} else if (topic != null) {
headers.add(ApnsConstant.APNS_NOTIFICATION_TOPIC, topic);
}
if (notification.getExpiration() != null) {
headers.addInt(ApnsConstant.APNS_NOTIFICATION_EXPIRATION, notification.getExpiration());
}
if (notification.getPriority() != null) {
headers.addInt(ApnsConstant.APNS_NOTIFICATION_PRIORITY, notification.getPriority());
}
return request;
}
private void doConnect(final String host, final int port) throws SSLException {
this.workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(this.workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.remoteAddress(host, port);
bootstrap.handler(this.http2ClientInitializer);
this.channel = bootstrap.connect().syncUninterruptibly().channel();
this.http2ClientInitializer.awaitSettings(30, TimeUnit.SECONDS);
}
private void disConnect() {
if (workerGroup != null) {
workerGroup.shutdownGracefully();
workerGroup = null;
}
if (channel != null) {
channel.closeFuture().syncUninterruptibly();
channel = null;
}
}
private SslContext createSslContext(final boolean jdkDefault, final File caFile) throws FileNotFoundException, SSLException {
SslContextBuilder sslContextBuilder = this.createSslContextBuilder(jdkDefault);
return sslContextBuilder.trustManager(new FileInputStream(caFile)).build();
}
private SslContext createSslContext(final boolean jdkDefault, final KeyStore keyStore, final String password) throws SSLException {
SslContextBuilder sslContextBuilder = this.createSslContextBuilder(jdkDefault);
X509Certificate x509Certificate;
PrivateKey privateKey;
try {
final KeyStore.PrivateKeyEntry privateKeyEntry = P12Utils.getFirstPrivateKeyEntryFromKeyStore(keyStore, 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 (KeyStoreException | IOException e) {
throw new SSLException(e);
}
return sslContextBuilder.keyManager(privateKey, x509Certificate).build();
}
private SslContextBuilder createSslContextBuilder(final boolean jdkDefault) {
SslProvider provider;
if (jdkDefault) {
provider = SslProvider.JDK;
} else {
provider = OpenSsl.isAvailable() && OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK;
}
return SslContextBuilder.forClient()
.sslProvider(provider)
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.applicationProtocolConfig(new ApplicationProtocolConfig(
ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1));
}
}