Skip to content

Commit

Permalink
Introduced an OkHttp sync client
Browse files Browse the repository at this point in the history
  • Loading branch information
judepereira committed Apr 18, 2016
1 parent 53cefa2 commit 10a37ca
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 1 deletion.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
<version>2.1.4</version>
</dependency>

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.2.0</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@
package com.clevertap.jetty.apns.http2.clients;

import com.clevertap.jetty.apns.http2.ApnsClient;
import okhttp3.ConnectionPool;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.concurrent.Semaphore;

Expand All @@ -52,6 +55,19 @@ public class ApnsClientBuilder {
private Semaphore semaphore;
private String defaultTopic = null;

private boolean useOkHttp3 = false;
private ConnectionPool connectionPool;

public ApnsClientBuilder withOkHttp3() {
useOkHttp3 = true;
return this;
}

public ApnsClientBuilder withOkHttp3ConnectionPool(ConnectionPool connectionPool) {
this.connectionPool = connectionPool;
return this;
}

public ApnsClientBuilder withCertificate(InputStream inputStream) {
certificate = inputStream;
return this;
Expand Down Expand Up @@ -104,10 +120,15 @@ public ApnsClientBuilder withMaxQueuedNotifications(int maxQueuedNotifications)
}

public ApnsClient build() throws CertificateException,
NoSuchAlgorithmException, KeyStoreException, IOException {
NoSuchAlgorithmException, KeyStoreException, IOException,
UnrecoverableKeyException, KeyManagementException {

if (certificate == null) throw new CertificateException("Certificate cannot be null");

if (useOkHttp3) {
return new SyncOkHttpApnsClient(certificate, password, production, defaultTopic, connectionPool);
}

if (asynchronous) {
if (semaphore == null) {
semaphore = new Semaphore(maxQueuedNotifications);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright (c) 2016, CleverTap
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of CleverTap nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.clevertap.jetty.apns.http2.clients;

import com.clevertap.jetty.apns.http2.*;
import com.clevertap.jetty.apns.http2.internal.Constants;
import okhttp3.*;
import okio.BufferedSink;
import org.eclipse.jetty.client.HttpClient;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
* User: Jude Pereira
* Date: 23/03/2016
* Time: 20:44
*/
public class SyncOkHttpApnsClient implements ApnsClient {

private final String defaultTopic;
private final OkHttpClient client;
private final String gateway;
private static final MediaType mediaType = MediaType.parse("application/json");

private static AtomicLong nullResponse = new AtomicLong();
private static final Executor es = Executors.newScheduledThreadPool(1);

static {

}

/**
* Creates a new client and automatically loads the key store
* with the push certificate read from the input stream.
*
* @param certificate The client certificate to be used
* @param password The password (if required, else null)
* @param production Whether to use the production endpoint or the sandbox endpoint
* @param defaultTopic A default topic (can be changed per message)
*/
public SyncOkHttpApnsClient(InputStream certificate, String password, boolean production,
String defaultTopic, ConnectionPool connectionPool)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException,
IOException, UnrecoverableKeyException, KeyManagementException {
password = password == null ? "" : password;
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certificate, password.toCharArray());

final X509Certificate cert = (X509Certificate) ks.getCertificate(ks.aliases().nextElement());
CertificateUtils.validateCertificate(production, cert);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");

final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
sslContext.init(keyManagers, tmf.getTrustManagers(), null);

final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

OkHttpClient.Builder builder = new OkHttpClient.Builder();

builder.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS);

builder.sslSocketFactory(sslSocketFactory);

connectionPool = connectionPool == null ? new ConnectionPool(10, 10, TimeUnit.MINUTES) : connectionPool;
builder.connectionPool(connectionPool);

this.defaultTopic = defaultTopic;

client = builder.build();

if (production) {
gateway = Constants.ENDPOINT_PRODUCTION;
} else {
gateway = Constants.ENDPOINT_SANDBOX;
}
}

@Override
public boolean isSynchronous() {
return true;
}

@Override
public HttpClient getHttpClient() {
return null;
}

@Override
public void push(Notification notification, NotificationResponseListener listener) {
throw new UnsupportedOperationException("Asynchronous requests are not supported by this client");
}

@Override
public NotificationResponse push(Notification notification) {
final String topic = notification.getTopic() != null ? notification.getTopic() : defaultTopic;
Request.Builder rb = new Request.Builder()
.url(gateway + "/3/device/" + notification.getToken())

.post(new RequestBody() {
@Override
public MediaType contentType() {
return mediaType;
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(notification.getPayload().getBytes(Constants.UTF_8));
}
})
.header("content-length", notification.getPayload().getBytes(Constants.UTF_8).length + "");

if (topic != null) {
rb.header("apns-topic", topic);
}

final Request request = rb.build();
Response response = null;
NotificationRequestError error = null;
String contentBody = null;
int statusCode = -1;

Throwable cause = null;
try {
response = client.newCall(request).execute();
statusCode = response.code();

if (response.code() != 200) {
error = NotificationRequestError.get(statusCode);
contentBody = response.body() != null ? response.body().string() : null;
}
} catch (Throwable t) {
cause = t;
} finally {
if (response != null) {
response.body().close();
}
}

return new NotificationResponse(error, statusCode, contentBody, cause);
}

@Override
public void start() throws IOException {

}

@Override
public void shutdown() throws Exception {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2016, CleverTap
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of CleverTap nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.clevertap.jetty.apns.http2.exceptions;

import java.security.cert.CertificateException;

/**
* User: Jude Pereira
* Date: 18/04/2016
* Time: 17:34
*/
public class CertificateEnvironmentMismatchException extends CertificateException {
}

0 comments on commit 10a37ca

Please sign in to comment.