Skip to content

Commit

Permalink
sdk/java: backport Java SDK 1.1.1 to 1.1-stable
Browse files Browse the repository at this point in the history
Cherry-pick of #688, #690, and #689 (in that order)

Closes #692
  • Loading branch information
jeffomatic authored and iampogo committed Mar 3, 2017
1 parent 3e42a33 commit e600c7f
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 12 deletions.
6 changes: 3 additions & 3 deletions docs/core/get-started/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ The Java SDK is available via [Maven](https://search.maven.org/#search%7Cga%7C1%
<dependency>
<groupId>com.chain</groupId>
<artifactId>chain-sdk-java</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
</dependency>
</dependencies>
```

**Gradle** users should add the following to `build.gradle`:

```
compile 'com.chain:chain-sdk-java:1.1.0'
compile 'com.chain:chain-sdk-java:1.1.1'
```

You can also [download the JAR](https://search.maven.org/remotecontent?filepath=com/chain/chain-sdk-java/1.1.0/chain-sdk-java-1.1.0.jar) as a binary.
You can also [download the JAR](https://search.maven.org/remotecontent?filepath=com/chain/chain-sdk-java/1.1.0/chain-sdk-java-1.1.1.jar) as a binary.

## Node.js

Expand Down
25 changes: 25 additions & 0 deletions sdk/java/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Chain Java SDK changelog

## 1.1.1 (March 3, 2017)

### Trusted SSL certs

You can now specify trusted server SSL certificates via a PEM-encoded file:

```
Client client = new Client.Builder()
.setURL("https://example:443")
.setTrustedCerts("path/to/certs.pem")
.build();
```

### Request logging

To log requests, pass an `OutputStream` to the client builder:

```
OutputStream os = System.out;
Client client = new Client.Builder()
.setLogger(os)
.setLogLevel(LoggingInterceptor.Level.ALL)
.build();
```

## 1.1.0 (February 24, 2017)

This release is a minor version update, and contains **new features** and **deprecations**. It is not compatible with cored 1.0.x; please upgrade cored before updating your SDKs.
Expand Down
2 changes: 1 addition & 1 deletion sdk/java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.chain</groupId>
<artifactId>chain-sdk-java</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
<packaging>jar</packaging>

<name>${project.groupId}:${project.artifactId}</name>
Expand Down
98 changes: 90 additions & 8 deletions sdk/java/src/main/java/com/chain/http/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
import com.chain.exception.*;
import com.chain.common.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.lang.reflect.Type;
import java.net.*;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Random;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.List;
import java.util.Objects;

import com.google.gson.Gson;

Expand All @@ -27,6 +25,8 @@
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import javax.net.ssl.*;

/**
* The Client object contains all information necessary to
* perform an HTTP request against a remote API. Typically,
Expand Down Expand Up @@ -380,6 +380,11 @@ private <T> T post(String path, Object body, ResponseCreator<T> respCreator)

private OkHttpClient buildHttpClient(Builder builder) {
OkHttpClient httpClient = new OkHttpClient();

if (builder.sslSocketFactory != null) {
httpClient.setSslSocketFactory(builder.sslSocketFactory);
}

httpClient.setFollowRedirects(false);
httpClient.setReadTimeout(builder.readTimeout, builder.readTimeoutUnit);
httpClient.setWriteTimeout(builder.writeTimeout, builder.writeTimeoutUnit);
Expand All @@ -393,6 +398,9 @@ private OkHttpClient buildHttpClient(Builder builder) {
if (builder.cp != null) {
httpClient.setCertificatePinner(builder.cp);
}
if (builder.logger != null) {
httpClient.interceptors().add(new LoggingInterceptor(builder.logger, builder.logLevel));
}

return httpClient;
}
Expand Down Expand Up @@ -519,6 +527,7 @@ public static class Builder {
private List<URL> urls;
private String accessToken;
private CertificatePinner cp;
private SSLSocketFactory sslSocketFactory;
private long connectTimeout;
private TimeUnit connectTimeoutUnit;
private long readTimeout;
Expand All @@ -527,6 +536,8 @@ public static class Builder {
private TimeUnit writeTimeoutUnit;
private Proxy proxy;
private ConnectionPool pool;
private OutputStream logger;
private LoggingInterceptor.Level logLevel = LoggingInterceptor.Level.ERRORS;

public Builder() {
this.urls = new ArrayList<URL>();
Expand Down Expand Up @@ -595,6 +606,59 @@ public Builder setAccessToken(String accessToken) {
return this;
}

/**
* Trusts the given CA certs, and no others. Use this if you are running
* your own CA, or are using a self-signed server certificate.
*
* @param path The path of a file containing certificates to trust, in PEM
* format.
*/
public Builder setTrustedCerts(String path)
throws GeneralSecurityException, IOException, IllegalArgumentException,
IllegalArgumentException {
// Extract certs from PEM-encoded input.
InputStream pemStream = new FileInputStream(path);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates =
certificateFactory.generateCertificates(pemStream);
if (certificates.isEmpty()) {
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
}

// Create empty key store.
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password =
"password".toCharArray(); // The password is unimportant as long as it used consistently.
keyStore.load(null, password);

// Load certs into key store.
int index = 0;
for (Certificate certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificate);
}

// Use key store to build an X509 trust manager.
KeyManagerFactory keyManagerFactory =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException(
"Unexpected default trust managers:" + Arrays.toString(trustManagers));
}

// Finally, configure the socket factory.
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
sslSocketFactory = sslContext.getSocketFactory();

return this;
}

/**
* Sets the certificate pinner for the client
* @param provider certificate provider
Expand Down Expand Up @@ -658,6 +722,24 @@ public Builder setConnectionPool(int maxIdle, long timeout, TimeUnit unit) {
return this;
}

/**
* Sets the request logger.
* @param logger the output stream to log the requests to
*/
public Builder setLogger(OutputStream logger) {
this.logger = logger;
return this;
}

/**
* Sets the level of the request logger.
* @param level all, errors or none
*/
public Builder setLogLevel(LoggingInterceptor.Level level) {
this.logLevel = level;
return this;
}

/**
* Builds a client with all of the provided parameters.
*/
Expand Down
76 changes: 76 additions & 0 deletions sdk/java/src/main/java/com/chain/http/LoggingInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.chain.http;

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import okio.BufferedSink;
import okio.BufferedSource;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

/**
* The LoggingInterceptor object logs http requests given
* an output stream.
*/
public class LoggingInterceptor implements Interceptor {
private Level level;
private OutputStream logger;

public enum Level {
ALL,
ERRORS,
NONE,
}

public LoggingInterceptor(OutputStream logger, Level logAllRequests) {
this.logger = logger;
this.level = logAllRequests;
}

@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);

boolean isError = (response.code() / 100) == 5 || (response.code() / 100) == 4;
if ((isError && level == level.ERRORS) || level == level.ALL) {
logRequestData(request, response);
}

return response;
}

public void logRequestData(Request request, Response response) throws IOException {
String reqid = response.header("Chain-Request-Id");

String requestBody;
try {
BufferedSink reqBodySink = new okio.Buffer();
request.body().writeTo(reqBodySink);
requestBody = new String(reqBodySink.buffer().readByteArray());
} catch (IOException e) {
requestBody = "Unable to read request body.";
}

String label = "chain-request";
if (response.code() / 100 == 5) {
label = "chain-error";
}

BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);

logger.write(
String.format(
"%s:\n\treqid=%s\n\turl=%s\n\tcode=%d\n\trequest=%s\n\tresponse=%s\n",
label,
reqid,
request.urlString(),
response.code(),
requestBody,
source.buffer().clone().readString(Charset.forName("UTF-8")))
.getBytes());
}
}

0 comments on commit e600c7f

Please sign in to comment.