Skip to content

Commit

Permalink
1. Added support for specifying Ethereum client versions when working…
Browse files Browse the repository at this point in the history
… with Infura.

2. Updated network diagram.
  • Loading branch information
conor10 committed Nov 18, 2016
1 parent 51b3202 commit 7702d1d
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 9 deletions.
Binary file modified docs/source/images/web3j_network.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 26 additions & 9 deletions src/main/java/org/web3j/protocol/http/HttpService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@


import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;

import org.web3j.protocol.Web3jService;
import org.web3j.protocol.ObjectMapperFactory;
Expand All @@ -25,23 +29,26 @@ public class HttpService implements Web3jService {

public static final String DEFAULT_URL = "http://localhost:8545/";

private CloseableHttpClient httpClient =
HttpClients.custom().setConnectionManagerShared(true).build();
private CloseableHttpClient httpClient;

private ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();

private final String url;

public HttpService() {
this.url = DEFAULT_URL;
public HttpService(String url, CloseableHttpClient httpClient) {
this.url = url;
this.httpClient = httpClient;
}

public HttpService(String url) {
this.url = url;
this(url, HttpClients.custom().setConnectionManagerShared(true).build());
}

public HttpService(String url, CloseableHttpClient httpClient) {
this.url = url;
public HttpService() {
this(DEFAULT_URL);
}

protected void setHttpClient(CloseableHttpClient httpClient) {
this.httpClient = httpClient;
}

Expand All @@ -53,7 +60,8 @@ public <T extends Response> T send(

HttpPost httpPost = new HttpPost(this.url);
httpPost.setEntity(new ByteArrayEntity(payload));
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
Header[] headers = buildHeaders();
httpPost.setHeaders(headers);

ResponseHandler<T> responseHandler = getResponseHandler(responseType);
try {
Expand All @@ -63,6 +71,15 @@ public <T extends Response> T send(
}
}

private Header[] buildHeaders() {
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("Content-Type", "application/json; charset=UTF-8"));
addHeaders(headers);
return headers.toArray(new Header[0]);
}

protected void addHeaders(List<Header> headers) { }

public <T> ResponseHandler<T> getResponseHandler(Class<T> type) {
return response -> {
int status = response.getStatusLine().getStatusCode();
Expand Down
150 changes: 150 additions & 0 deletions src/main/java/org/web3j/protocol/infura/CertificateManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package org.web3j.protocol.infura;

import javax.net.ssl.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
* Certificate manager to simplify working with TLS endpoints.
*/
public class CertificateManager {

static File buildKeyStore(String url, char[] keyStorePassword) {
KeyStore keyStore;
try {
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, keyStorePassword);

CertificateChainTrustManager certificateChainTrustManager =
createCertificateChainTrustManager(keyStore);
URI endpoint = new URI(url);
SSLSocket sslSocket = createSslSocket(endpoint, certificateChainTrustManager);

if (!isTrustedEndPoint(sslSocket)) {
X509Certificate[] x509Certificates = certificateChainTrustManager.x509Certificates;
if (x509Certificates == null) {
throw new RuntimeException("Unable to obtain x509 certificate from server");
}

for (int i = 0; i < x509Certificates.length; i++) {
keyStore.setCertificateEntry(endpoint.getHost() + i, x509Certificates[i]);
}
}

SecureRandom random = new SecureRandom();
File keyFile = File.createTempFile("web3j-", "" + random.nextLong());

FileOutputStream fileOutputStream = new FileOutputStream(keyFile);
keyStore.store(fileOutputStream, keyStorePassword);
fileOutputStream.close();

deleteFileOnShutdown(keyFile);

return keyFile;

} catch (KeyStoreException e) {
throw new RuntimeException(e);
} catch (CertificateException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (KeyManagementException e) {
throw new RuntimeException(e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

private static CertificateChainTrustManager createCertificateChainTrustManager(
KeyStore keyStore) throws NoSuchAlgorithmException, KeyStoreException {

TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
X509TrustManager defaultTrustManager =
(X509TrustManager) trustManagerFactory.getTrustManagers()[0];
return new CertificateChainTrustManager(defaultTrustManager);
}

private static SSLSocket createSslSocket(
URI endpoint, CertificateChainTrustManager certificateChainTrustManager)
throws NoSuchAlgorithmException, KeyManagementException, IOException,
URISyntaxException, KeyStoreException {

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{certificateChainTrustManager}, null);
SSLSocketFactory factory = context.getSocketFactory();

SSLSocket sslSocket = (SSLSocket) factory.createSocket(
endpoint.getHost(), 443);
sslSocket.setSoTimeout(10000);

return sslSocket;
}

private static boolean isTrustedEndPoint(SSLSocket socket) throws IOException {
try {
socket.startHandshake();
socket.close();
return true;
} catch (SSLException e) {
return false;
}
}

private static void deleteFileOnShutdown(File file) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
deleteTempFile(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}

private static void deleteTempFile(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new RuntimeException("Unable to remove file: " + file.getCanonicalPath());
}
}

/**
* Based on:
* http://blogs.sun.com/andreas/resource/InstallCert.java
*/
private static class CertificateChainTrustManager implements X509TrustManager {

private final X509TrustManager x509TrustManager;
private X509Certificate[] x509Certificates;

CertificateChainTrustManager(X509TrustManager x509TrustManager) {
this.x509TrustManager = x509TrustManager;
}

public X509Certificate[] getAcceptedIssuers() {
// Called by Java 7+ see http://infposs.blogspot.kr/2013/06/installcert-and-java-7.html
return new X509Certificate[0];
}

public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new UnsupportedOperationException();
}

public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
this.x509Certificates = chain;
x509TrustManager.checkServerTrusted(chain, authType);
}
}
}
100 changes: 100 additions & 0 deletions src/main/java/org/web3j/protocol/infura/InfuraHttpService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.web3j.protocol.infura;

import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.List;

import org.apache.http.Header;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContexts;

import org.web3j.protocol.http.HttpService;

/**
* HttpService for working with <a href="https://infura.io/">Infura</a> clients.
*/
public class InfuraHttpService extends HttpService {

private static final String INFURA_ETHEREUM_PREFERRED_CLIENT =
"Infura-Ethereum-Preferred-Client";

private static final char[] TEMP_KEY_STORE_PASSWORD = "web3j runtime cert store".toCharArray();

private final Header clientVersionHeader;

public InfuraHttpService(String url, String clientVersion, boolean required) {
super(url);
setHttpClient(createTrustTlsHttpClient(url));
clientVersionHeader = buildHeader(clientVersion, required);
}

public InfuraHttpService(String url, String clientVersion) {
this(url, clientVersion, true);
}

@Override
protected void addHeaders(List<Header> headers) {
if (clientVersionHeader != null) {
headers.add(clientVersionHeader);
}
}

static Header buildHeader(String clientVersion, boolean required) {
if (clientVersion == null || clientVersion.equals("")) {
return null;
}

if (required) {
return new BasicHeader(INFURA_ETHEREUM_PREFERRED_CLIENT, clientVersion);
} else {
return new BasicHeader(
INFURA_ETHEREUM_PREFERRED_CLIENT, clientVersion + "; required=false");
}
}

/**
* Create an {@link InfuraHttpService} instance with a local keystore that implicitly trusts
* the provided endpoint.
*
* This is achieved by creating a local temporary keystore file which we add the certificate
* of the endpoint to upon application startup.
*
* @param url we wish to connect to
* @return the file containing the keystore
* @throws UnrecoverableKeyException if keystore file cannot be loaded
* @throws NoSuchAlgorithmException if keystore file cannot be loaded
* @throws KeyStoreException if keystore file cannot be loaded
* @throws KeyManagementException if keystore file cannot be loaded
*/
private static CloseableHttpClient createTrustTlsHttpClient(String url) {

File keyFile = CertificateManager.buildKeyStore(url, TEMP_KEY_STORE_PASSWORD);

SSLContext sslContext;
try {
sslContext = SSLContexts.custom()
.loadTrustMaterial(keyFile, TEMP_KEY_STORE_PASSWORD)
.build();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (KeyManagementException e) {
throw new RuntimeException(e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
} catch (CertificateException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}

return HttpClients.custom()
.setConnectionManagerShared(true)
.setSSLContext(sslContext)
.build();
}
}
29 changes: 29 additions & 0 deletions src/test/java/org/web3j/protocol/infura/InfuraHttpServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.web3j.protocol.infura;


import org.apache.http.message.BasicHeader;
import org.junit.Test;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.web3j.protocol.infura.InfuraHttpService.buildHeader;

public class InfuraHttpServiceTest {

@Test
public void testBuildHeader() {
assertNull(buildHeader("", false));
assertNull(buildHeader(null, false));

assertThat(buildHeader("geth 1.4.19", true).toString(),
is(new BasicHeader(
"Infura-Ethereum-Preferred-Client",
"geth 1.4.19").toString()));

assertThat(buildHeader("geth 1.4.19", false).toString(),
is(new BasicHeader(
"Infura-Ethereum-Preferred-Client",
"geth 1.4.19; required=false").toString()));
}
}

0 comments on commit 7702d1d

Please sign in to comment.