Merge pull request #1 from cprice404/feature/master/java-api
Add java API and integration tests
This commit is contained in:
commit
7196f316c5
27 changed files with 1280 additions and 10 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
target
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
language: clojure
|
||||
lein: lein2
|
||||
jdk:
|
||||
- oraclejdk7
|
||||
- openjdk7
|
||||
- openjdk6
|
||||
script: ./ext/travisci/test.sh
|
||||
notifications:
|
||||
email: false
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
[![Build Status](https://travis-ci.org/puppetlabs/clj-http-client.png?branch=master)](https://travis-ci.org/puppetlabs/clj-http-client)
|
||||
|
||||
# puppetlabs/http-client
|
||||
|
||||
This is a wrapper around the [http-kit](http://http-kit.org/) client
|
||||
|
|
3
ext/travisci/test.sh
Executable file
3
ext/travisci/test.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
lein2 test
|
15
project.clj
15
project.clj
|
@ -1,3 +1,6 @@
|
|||
(def ks-version "0.5.3")
|
||||
(def tk-version "0.3.8")
|
||||
|
||||
(defproject puppetlabs/http-client "0.1.3-SNAPSHOT"
|
||||
:description "HTTP client wrapper"
|
||||
:license {:name "Apache License, Version 2.0"
|
||||
|
@ -5,7 +8,17 @@
|
|||
|
||||
:dependencies [[org.clojure/clojure "1.5.1"]
|
||||
[http-kit "2.1.16"]
|
||||
[puppetlabs/kitchensink "0.5.2"]]
|
||||
[puppetlabs/kitchensink ~ks-version]
|
||||
[org.clojure/tools.logging "0.2.6"]
|
||||
[org.slf4j/slf4j-api "1.7.6"]]
|
||||
|
||||
:source-paths ["src/clj"]
|
||||
:java-source-paths ["src/java"]
|
||||
|
||||
:profiles {:dev {:dependencies [[puppetlabs/kitchensink ~ks-version :classifier "test"]
|
||||
[puppetlabs/trapperkeeper ~tk-version]
|
||||
[puppetlabs/trapperkeeper ~tk-version :classifier "test"]
|
||||
[puppetlabs/trapperkeeper-webserver-jetty9 "0.3.5"]]}}
|
||||
|
||||
:deploy-repositories [["releases" {:url "https://clojars.org/repo"
|
||||
:username :env/clojars_jenkins_username
|
||||
|
|
28
src/java/com/puppetlabs/http/client/HttpMethod.java
Normal file
28
src/java/com/puppetlabs/http/client/HttpMethod.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package com.puppetlabs.http.client;
|
||||
|
||||
// This is really dumb, but I didn't want to leak the HTTPKit class into the
|
||||
// API for now.
|
||||
|
||||
public enum HttpMethod {
|
||||
GET(org.httpkit.HttpMethod.GET),
|
||||
HEAD(org.httpkit.HttpMethod.HEAD),
|
||||
POST(org.httpkit.HttpMethod.POST),
|
||||
PUT(org.httpkit.HttpMethod.PUT),
|
||||
DELETE(org.httpkit.HttpMethod.DELETE),
|
||||
TRACE(org.httpkit.HttpMethod.TRACE),
|
||||
OPTIONS(org.httpkit.HttpMethod.OPTIONS),
|
||||
CONNECT(org.httpkit.HttpMethod.CONNECT),
|
||||
PATCH(org.httpkit.HttpMethod.PATCH);
|
||||
|
||||
|
||||
private org.httpkit.HttpMethod httpKitMethod;
|
||||
|
||||
HttpMethod(org.httpkit.HttpMethod httpKitMethod) {
|
||||
this.httpKitMethod = httpKitMethod;
|
||||
}
|
||||
|
||||
public org.httpkit.HttpMethod getValue() {
|
||||
return this.httpKitMethod;
|
||||
}
|
||||
|
||||
}
|
46
src/java/com/puppetlabs/http/client/HttpResponse.java
Normal file
46
src/java/com/puppetlabs/http/client/HttpResponse.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
package com.puppetlabs.http.client;
|
||||
|
||||
import com.puppetlabs.http.client.HttpResponse;
|
||||
import com.puppetlabs.http.client.RequestOptions;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpResponse {
|
||||
private RequestOptions options;
|
||||
private Throwable error;
|
||||
private Object body;
|
||||
private Map<String, Object> headers;
|
||||
private Integer status;
|
||||
|
||||
public HttpResponse(RequestOptions options, Throwable error) {
|
||||
this.options = options;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public HttpResponse(RequestOptions options, Object body, Map<String, Object> headers, int status) {
|
||||
this.options = options;
|
||||
this.body = body;
|
||||
this.headers = headers;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public RequestOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public Throwable getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public Object getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Map<String, Object> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
258
src/java/com/puppetlabs/http/client/RequestOptions.java
Normal file
258
src/java/com/puppetlabs/http/client/RequestOptions.java
Normal file
|
@ -0,0 +1,258 @@
|
|||
package com.puppetlabs.http.client;
|
||||
|
||||
import com.puppetlabs.http.client.impl.*;
|
||||
import org.httpkit.client.HttpClient;
|
||||
|
||||
import org.httpkit.client.IFilter;
|
||||
import org.httpkit.client.MultipartEntity;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class RequestOptions {
|
||||
private HttpClient client = DefaultClient.getInstance();
|
||||
private int timeout = 60000;
|
||||
private boolean followRedirects = true;
|
||||
private int maxRedirects = 10;
|
||||
// TODO: we are technically leaking this http-kit class into our API,
|
||||
// but since we're not using it anywhere I decided not to worry about it yet.
|
||||
private IFilter filter = IFilter.ACCEPT_ALL;
|
||||
private ExecutorService workerPool = DefaultWorkerPool.getInstance();
|
||||
private Promise<HttpResponse> promise = new Promise<HttpResponse>();
|
||||
private int keepalive = 120000;
|
||||
private ResponseBodyType as = ResponseBodyType.AUTO;
|
||||
|
||||
private String url;
|
||||
private HttpMethod method = null;
|
||||
private List<String> traceRedirects = new ArrayList<String>();
|
||||
private Map<String, Object> headers;
|
||||
private Map<String, String> formParams;
|
||||
private BasicAuth basicAuth;
|
||||
private String oauthToken;
|
||||
private String userAgent;
|
||||
private Map<String, String> queryParams;
|
||||
private SSLEngine sslEngine;
|
||||
private SSLContext sslContext;
|
||||
private String sslCert;
|
||||
private String sslKey;
|
||||
private String sslCaCert;
|
||||
private boolean insecure = false;
|
||||
private Object body;
|
||||
// TODO: we are technically leaking this http-kit class into our API,
|
||||
// but since we're not using it anywhere I decided not to worry about it yet.
|
||||
private List<MultipartEntity> multipartEntities;
|
||||
|
||||
|
||||
public RequestOptions(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public HttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
public RequestOptions setClient(HttpClient client) {
|
||||
this.client = client;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
public RequestOptions setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
public RequestOptions setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getKeepalive() {
|
||||
return keepalive;
|
||||
}
|
||||
public RequestOptions setKeepalive(int keepalive) {
|
||||
this.keepalive = keepalive;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getFollowRedirects() {
|
||||
return followRedirects;
|
||||
}
|
||||
public RequestOptions setFollowRedirects(boolean followRedirects) {
|
||||
this.followRedirects = followRedirects;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getMaxRedirects() {
|
||||
return maxRedirects;
|
||||
}
|
||||
public RequestOptions setMaxRedirects(int maxRedirects) {
|
||||
this.maxRedirects = maxRedirects;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResponseBodyType getAs() {
|
||||
return as;
|
||||
}
|
||||
public RequestOptions setAs(ResponseBodyType as) {
|
||||
this.as = as;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
public RequestOptions setMethod(HttpMethod method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IFilter getFilter() {
|
||||
return filter;
|
||||
}
|
||||
public RequestOptions setFilter(IFilter filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExecutorService getWorkerPool() {
|
||||
return workerPool;
|
||||
}
|
||||
|
||||
public Promise<HttpResponse> getPromise() {
|
||||
return this.promise;
|
||||
}
|
||||
public RequestOptions setPromise(Promise<HttpResponse> promise) {
|
||||
this.promise = promise;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getTraceRedirects() {
|
||||
return traceRedirects;
|
||||
}
|
||||
public RequestOptions addTraceRedirect(String url) {
|
||||
traceRedirects.add(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
public RequestOptions setHeaders(Map<String, Object> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getFormParams() {
|
||||
return formParams;
|
||||
}
|
||||
public RequestOptions setFormParams(Map<String, String> formParams) {
|
||||
this.formParams = formParams;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BasicAuth getBasicAuth() {
|
||||
return basicAuth;
|
||||
}
|
||||
public RequestOptions setBasicAuth(BasicAuth basicAuth) {
|
||||
this.basicAuth = basicAuth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getOAuthToken() {
|
||||
return oauthToken;
|
||||
}
|
||||
public RequestOptions setOAuthToken(String oauthToken) {
|
||||
this.oauthToken = oauthToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
public RequestOptions setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getQueryParams() {
|
||||
return queryParams;
|
||||
}
|
||||
public RequestOptions setQueryParams(Map<String, String> queryParams) {
|
||||
this.queryParams = queryParams;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SSLEngine getSslEngine() {
|
||||
return sslEngine;
|
||||
}
|
||||
public RequestOptions setSslEngine(SSLEngine sslEngine) {
|
||||
this.sslEngine = sslEngine;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SSLContext getSslContext() {
|
||||
return sslContext;
|
||||
}
|
||||
public RequestOptions setSslContext(SSLContext sslContext) {
|
||||
this.sslContext = sslContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSslCert() {
|
||||
return sslCert;
|
||||
}
|
||||
public RequestOptions setSslCert(String sslCert) {
|
||||
this.sslCert = sslCert;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSslKey() {
|
||||
return sslKey;
|
||||
}
|
||||
public RequestOptions setSslKey(String sslKey) {
|
||||
this.sslKey = sslKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSslCaCert() {
|
||||
return sslCaCert;
|
||||
}
|
||||
public RequestOptions setSslCaCert(String sslCaCert) {
|
||||
this.sslCaCert = sslCaCert;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean getInsecure() {
|
||||
return insecure;
|
||||
}
|
||||
public RequestOptions setInsecure(boolean insecure) {
|
||||
this.insecure = insecure;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getBody() {
|
||||
return body;
|
||||
}
|
||||
public RequestOptions setBody(Object body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<MultipartEntity> getMultipartEntities() {
|
||||
return multipartEntities;
|
||||
}
|
||||
public RequestOptions setMultipartEntities(List<MultipartEntity> entities) {
|
||||
this.multipartEntities = entities;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
19
src/java/com/puppetlabs/http/client/ResponseBodyType.java
Normal file
19
src/java/com/puppetlabs/http/client/ResponseBodyType.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package com.puppetlabs.http.client;
|
||||
|
||||
public enum ResponseBodyType {
|
||||
AUTO(1),
|
||||
TEXT(2),
|
||||
STREAM(3),
|
||||
BYTE_ARRAY(4);
|
||||
|
||||
private int value;
|
||||
ResponseBodyType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
|
||||
}
|
117
src/java/com/puppetlabs/http/client/SyncHttpClient.java
Normal file
117
src/java/com/puppetlabs/http/client/SyncHttpClient.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
package com.puppetlabs.http.client;
|
||||
|
||||
import com.puppetlabs.http.client.HttpResponse;
|
||||
import com.puppetlabs.http.client.impl.JavaClient;
|
||||
import com.puppetlabs.http.client.impl.Promise;
|
||||
import com.puppetlabs.http.client.impl.SslUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class SyncHttpClient {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SyncHttpClient.class);
|
||||
|
||||
|
||||
private static void logAndRethrow(String msg, Throwable t) {
|
||||
LOGGER.error(msg, t);
|
||||
throw new RuntimeException(msg, t);
|
||||
}
|
||||
|
||||
private static RequestOptions configureSslFromContext(RequestOptions options) {
|
||||
options.setSslEngine(options.getSslContext().createSSLEngine());
|
||||
options.setSslContext(null);
|
||||
return options;
|
||||
}
|
||||
|
||||
// TODO: move this into the async java API if we ever add one
|
||||
private static RequestOptions configureSsl(RequestOptions options) {
|
||||
if (options.getSslEngine() != null) {
|
||||
return options;
|
||||
}
|
||||
|
||||
if (options.getSslContext() != null) {
|
||||
return configureSslFromContext(options);
|
||||
}
|
||||
|
||||
if ((options.getSslCert() != null) &&
|
||||
(options.getSslKey() != null) &&
|
||||
(options.getSslCaCert() != null)) {
|
||||
try {
|
||||
options.setSslContext(
|
||||
SslUtils.pemsToSSLContext(
|
||||
new FileReader(options.getSslCert()),
|
||||
new FileReader(options.getSslKey()),
|
||||
new FileReader(options.getSslCaCert()))
|
||||
);
|
||||
} catch (KeyStoreException e) {
|
||||
logAndRethrow("Error while configuring SSL", e);
|
||||
} catch (CertificateException e) {
|
||||
logAndRethrow("Error while configuring SSL", e);
|
||||
} catch (IOException e) {
|
||||
logAndRethrow("Error while configuring SSL", e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logAndRethrow("Error while configuring SSL", e);
|
||||
} catch (KeyManagementException e) {
|
||||
logAndRethrow("Error while configuring SSL", e);
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
logAndRethrow("Error while configuring SSL", e);
|
||||
}
|
||||
options.setSslCert(null);
|
||||
options.setSslKey(null);
|
||||
options.setSslCaCert(null);
|
||||
return configureSslFromContext(options);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static HttpResponse request(RequestOptions options) {
|
||||
// TODO: if we end up implementing an async version of the java API,
|
||||
// we should refactor this implementation so that it is based on the
|
||||
// async one, as Patrick has done in the clojure API.
|
||||
|
||||
options = configureSsl(options);
|
||||
|
||||
Promise<HttpResponse> promise = null;
|
||||
try {
|
||||
promise = JavaClient.request(options, null);
|
||||
} catch (IOException e) {
|
||||
logAndRethrow("Error submitting http request", e);
|
||||
|
||||
}
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
response = promise.deref();
|
||||
} catch (InterruptedException e) {
|
||||
logAndRethrow("Error while waiting for http response", e);
|
||||
}
|
||||
if (response.getError() != null) {
|
||||
logAndRethrow("Error executing http request", response.getError());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
public static HttpResponse get(String url) {
|
||||
return get(new RequestOptions(url));
|
||||
}
|
||||
|
||||
public static HttpResponse get(RequestOptions requestOptions) {
|
||||
return request(requestOptions.setMethod(HttpMethod.GET));
|
||||
}
|
||||
|
||||
public static HttpResponse post(String url) {
|
||||
return post(new RequestOptions(url));
|
||||
}
|
||||
|
||||
public static HttpResponse post(RequestOptions requestOptions) {
|
||||
return request(requestOptions.setMethod(HttpMethod.POST));
|
||||
}
|
||||
}
|
19
src/java/com/puppetlabs/http/client/impl/BasicAuth.java
Normal file
19
src/java/com/puppetlabs/http/client/impl/BasicAuth.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
public class BasicAuth {
|
||||
private final String user;
|
||||
private final String password;
|
||||
|
||||
public BasicAuth(String user, String password) {
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import org.httpkit.HttpMethod;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.util.Map;
|
||||
|
||||
public class CoercedRequestOptions {
|
||||
private final String url;
|
||||
private final HttpMethod method;
|
||||
private final Map<String, Object> headers;
|
||||
private final Object body;
|
||||
private final SSLEngine sslEngine;
|
||||
|
||||
|
||||
public CoercedRequestOptions(String url,
|
||||
HttpMethod method,
|
||||
Map<String, Object> headers,
|
||||
Object body,
|
||||
SSLEngine sslEngine) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
this.headers = headers;
|
||||
this.body = body;
|
||||
this.sslEngine = sslEngine;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public HttpMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Map<String, Object> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public Object getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public SSLEngine getSslEngine() {
|
||||
return sslEngine;
|
||||
}
|
||||
}
|
20
src/java/com/puppetlabs/http/client/impl/DefaultClient.java
Normal file
20
src/java/com/puppetlabs/http/client/impl/DefaultClient.java
Normal file
|
@ -0,0 +1,20 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import org.httpkit.client.HttpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DefaultClient {
|
||||
private static HttpClient instance;
|
||||
|
||||
public synchronized static HttpClient getInstance() {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = new HttpClient();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error attempting to instantiate HttpClient", e);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import org.httpkit.PrefixThreadFactory;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class DefaultWorkerPool {
|
||||
|
||||
private static ExecutorService instance;
|
||||
|
||||
public static synchronized ExecutorService getInstance() {
|
||||
if (instance == null) {
|
||||
int max = Runtime.getRuntime().availableProcessors();
|
||||
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
|
||||
PrefixThreadFactory factory = new PrefixThreadFactory("client-worker-");
|
||||
|
||||
instance = new ThreadPoolExecutor(0, max, 60, TimeUnit.SECONDS, queue, factory);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import com.puppetlabs.http.client.HttpResponse;
|
||||
|
||||
public interface IResponseCallback {
|
||||
HttpResponse handleResponse(HttpResponse response);
|
||||
}
|
146
src/java/com/puppetlabs/http/client/impl/JavaClient.java
Normal file
146
src/java/com/puppetlabs/http/client/impl/JavaClient.java
Normal file
|
@ -0,0 +1,146 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import com.puppetlabs.http.client.HttpResponse;
|
||||
import com.puppetlabs.http.client.RequestOptions;
|
||||
import org.httpkit.HttpMethod;
|
||||
import org.httpkit.client.*;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JavaClient {
|
||||
|
||||
private static HttpClient defaultClient = null;
|
||||
|
||||
private static HttpClient getDefaultClient() throws IOException {
|
||||
if (defaultClient == null) {
|
||||
defaultClient = new HttpClient();
|
||||
}
|
||||
return defaultClient;
|
||||
}
|
||||
|
||||
private static String buildQueryString(Map<String, String> params) {
|
||||
// TODO: add support for nested query params. For now we assume a flat,
|
||||
// String->String data structure.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
if (!first) {
|
||||
sb.append("&");
|
||||
}
|
||||
first = false;
|
||||
try {
|
||||
sb.append(URLEncoder.encode(entry.getKey(), "utf8"));
|
||||
sb.append("=");
|
||||
sb.append(URLEncoder.encode(entry.getValue(), "utf8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Error while url-encoding query string", e);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String getBasicAuthValue(BasicAuth auth) {
|
||||
String userPasswordStr = auth.getUser() + ":" + auth.getPassword();
|
||||
try {
|
||||
return "Basic " + DatatypeConverter.printBase64Binary(userPasswordStr.getBytes("utf8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Error while attmempting to encode basic auth", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> prepareHeaders(RequestOptions options) {
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
if (options.getHeaders() != null) {
|
||||
for (Map.Entry<String, Object> entry : options.getHeaders().entrySet()) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (options.getFormParams() != null) {
|
||||
result.put("Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
if (options.getBasicAuth() != null) {
|
||||
result.put("Authorization", getBasicAuthValue(options.getBasicAuth()));
|
||||
}
|
||||
if (options.getOAuthToken() != null) {
|
||||
result.put("Authorization", "Bearer " + options.getOAuthToken());
|
||||
}
|
||||
if (options.getUserAgent() != null) {
|
||||
result.put("User-Agent", options.getUserAgent());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static CoercedRequestOptions coerceRequestOptions(RequestOptions options) throws IOException {
|
||||
String url;
|
||||
if (options.getQueryParams() != null) {
|
||||
if (options.getUrl().indexOf('?') == -1) {
|
||||
url = options.getUrl() + "?" + buildQueryString(options.getQueryParams());
|
||||
} else {
|
||||
url = options.getUrl() + "&" + buildQueryString(options.getQueryParams());
|
||||
}
|
||||
} else {
|
||||
url = options.getUrl();
|
||||
}
|
||||
|
||||
SSLEngine sslEngine = null;
|
||||
if (options.getSslEngine() != null) {
|
||||
sslEngine = options.getSslEngine();
|
||||
} else if (options.getInsecure()) {
|
||||
sslEngine = SslContextFactory.trustAnybody();
|
||||
}
|
||||
|
||||
HttpMethod method = options.getMethod().getValue();
|
||||
if (method == null) {
|
||||
method = HttpMethod.GET;
|
||||
}
|
||||
|
||||
Map<String, Object> headers = prepareHeaders(options);
|
||||
|
||||
Object body;
|
||||
if (options.getFormParams() != null) {
|
||||
body = buildQueryString(options.getFormParams());
|
||||
} else {
|
||||
body = options.getBody();
|
||||
}
|
||||
|
||||
if (options.getMultipartEntities() != null) {
|
||||
String boundary = MultipartEntity.genBoundary(options.getMultipartEntities());
|
||||
|
||||
headers = options.getHeaders();
|
||||
headers.put("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
|
||||
body = MultipartEntity.encode(boundary, options.getMultipartEntities());
|
||||
}
|
||||
|
||||
return new CoercedRequestOptions(url, method, headers, body, sslEngine);
|
||||
}
|
||||
|
||||
public static Promise<HttpResponse> request(RequestOptions options, IResponseCallback callback)
|
||||
throws IOException {
|
||||
HttpClient client = options.getClient();
|
||||
if (client == null) {
|
||||
client = getDefaultClient();
|
||||
}
|
||||
|
||||
CoercedRequestOptions coercedOptions = coerceRequestOptions(options);
|
||||
|
||||
RequestConfig config = new RequestConfig(coercedOptions.getMethod(),
|
||||
coercedOptions.getHeaders(), coercedOptions.getBody(),
|
||||
options.getTimeout(), options.getKeepalive());
|
||||
|
||||
RespListener listener = new RespListener(
|
||||
new ResponseHandler(options, coercedOptions, callback), options.getFilter(),
|
||||
options.getWorkerPool(), options.getAs().getValue());
|
||||
|
||||
client.exec(options.getUrl(), config, coercedOptions.getSslEngine(), listener);
|
||||
|
||||
return options.getPromise();
|
||||
}
|
||||
}
|
26
src/java/com/puppetlabs/http/client/impl/Promise.java
Normal file
26
src/java/com/puppetlabs/http/client/impl/Promise.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class Promise<T> {
|
||||
private final CountDownLatch latch;
|
||||
private T value = null;
|
||||
|
||||
public Promise() {
|
||||
latch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
public synchronized void deliver(T t) {
|
||||
if (value != null) {
|
||||
throw new IllegalStateException("Attempting to deliver value to a promise that has already been realized!");
|
||||
}
|
||||
value = t;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public T deref() throws InterruptedException {
|
||||
latch.await();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import com.puppetlabs.http.client.HttpMethod;
|
||||
import com.puppetlabs.http.client.HttpResponse;
|
||||
import com.puppetlabs.http.client.RequestOptions;
|
||||
import org.httpkit.HttpUtils;
|
||||
import org.httpkit.client.IResponseHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ResponseHandler implements IResponseHandler {
|
||||
|
||||
private static final Set<Integer> REDIRECT_STATUS_CODES =
|
||||
new HashSet<Integer>(Arrays.asList(301, 302, 303, 307, 308));
|
||||
|
||||
private final RequestOptions options;
|
||||
private final CoercedRequestOptions coercedOptions;
|
||||
private final IResponseCallback callback;
|
||||
|
||||
public ResponseHandler(RequestOptions options,
|
||||
CoercedRequestOptions coercedOptions,
|
||||
IResponseCallback callback) {
|
||||
this.options = options;
|
||||
this.coercedOptions = coercedOptions;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
private HttpMethod getNewMethod(int status) {
|
||||
if (status == 301 || status == 302 || status == 303) {
|
||||
return HttpMethod.GET;
|
||||
} else {
|
||||
return options.getMethod();
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverResponse(HttpResponse response) {
|
||||
HttpResponse finalResponse = response;
|
||||
try {
|
||||
if (callback != null) {
|
||||
finalResponse = callback.handleResponse(response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// dump stacktrace to stderr
|
||||
HttpUtils.printError(coercedOptions.getMethod() + " " +
|
||||
coercedOptions.getUrl() + "'s callback", e);
|
||||
// return the error
|
||||
options.getPromise().deliver(new HttpResponse(options, e));
|
||||
}
|
||||
options.getPromise().deliver(finalResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(int status, Map<String, Object> headers, Object body) {
|
||||
if (options.getFollowRedirects() && REDIRECT_STATUS_CODES.contains(status)) {
|
||||
if (options.getMaxRedirects() >= options.getTraceRedirects().size()) {
|
||||
// follow 301 and 302 redirect
|
||||
try {
|
||||
JavaClient.request(
|
||||
options.setUrl(new URI(coercedOptions.getUrl()).resolve((String) headers.get("location")).toString())
|
||||
.setMethod(getNewMethod(status))
|
||||
.addTraceRedirect(coercedOptions.getUrl()),
|
||||
callback);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error when attempting redirect", e);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Error when attempting redirect", e);
|
||||
}
|
||||
} else {
|
||||
deliverResponse(new HttpResponse(options,
|
||||
new Exception("too many redirects: " + options.getTraceRedirects().size())));
|
||||
}
|
||||
} else {
|
||||
deliverResponse(new HttpResponse(options, body, headers, status));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onThrowable(Throwable t) {
|
||||
deliverResponse(new HttpResponse(options, t));
|
||||
}
|
||||
}
|
349
src/java/com/puppetlabs/http/client/impl/SslUtils.java
Normal file
349
src/java/com/puppetlabs/http/client/impl/SslUtils.java
Normal file
|
@ -0,0 +1,349 @@
|
|||
package com.puppetlabs.http.client.impl;
|
||||
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMException;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SslUtils {
|
||||
|
||||
|
||||
// TODO: this code is copied verbatim from the CA library; we should get rid
|
||||
// of this file and just specify a dependency on that library once we release it
|
||||
|
||||
|
||||
/**
|
||||
* Given a PEM reader, decode the contents into a collection of objects of the corresponding
|
||||
* type from the java.security package.
|
||||
*
|
||||
* @param reader Reader for a PEM-encoded stream
|
||||
* @return The list of decoded objects from the stream
|
||||
* @throws IOException
|
||||
* @see #writeToPEM
|
||||
*/
|
||||
public static List<Object> pemToObjects(Reader reader)
|
||||
throws IOException
|
||||
{
|
||||
PEMParser parser = new PEMParser(reader);
|
||||
List<Object> results = new ArrayList<Object>();
|
||||
for (Object o = parser.readObject(); o != null; o = parser.readObject())
|
||||
results.add(o);
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes the provided object (read from a PEM stream via {@link #pemToObjects}) into a private key.
|
||||
*
|
||||
* @param obj The object to decode into a PrivateKey
|
||||
* @return The PrivateKey decoded from the object
|
||||
* @throws PEMException
|
||||
* @see #pemToPrivateKey
|
||||
* @see #pemToPrivateKeys
|
||||
*/
|
||||
public static PrivateKey objectToPrivateKey(Object obj)
|
||||
throws PEMException
|
||||
{
|
||||
// Certain PEMs will hand back a keypair with a nil public key
|
||||
if (obj instanceof PrivateKeyInfo)
|
||||
return new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) obj);
|
||||
else if (obj instanceof PEMKeyPair)
|
||||
return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) obj).getPrivate();
|
||||
else
|
||||
throw new IllegalArgumentException("Expected a KeyPair or PrivateKey, got " + obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a PEM reader, decode the contents into a list of private keys.
|
||||
*
|
||||
* @param reader Reader for a PEM-encoded stream
|
||||
* @return The list of decoded private keys from the stream
|
||||
* @throws IOException
|
||||
* @throws PEMException
|
||||
* @see #pemToPrivateKey
|
||||
* @see #writeToPEM
|
||||
*/
|
||||
public static List<PrivateKey> pemToPrivateKeys(Reader reader)
|
||||
throws IOException, PEMException
|
||||
{
|
||||
List<Object> objects = pemToObjects(reader);
|
||||
List<PrivateKey> results = new ArrayList<PrivateKey>(objects.size());
|
||||
for (Object o : objects)
|
||||
results.add(objectToPrivateKey(o));
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a PEM reader, decode the contents into a private key.
|
||||
* Throws an exception if multiple keys are found.
|
||||
*
|
||||
* @param reader Reader for a PEM-encoded stream
|
||||
* @return The decoded private key from the stream
|
||||
* @throws IOException
|
||||
* @throws IllegalArgumentException
|
||||
* @see #pemToPrivateKeys
|
||||
* @see #writeToPEM
|
||||
*/
|
||||
public static PrivateKey pemToPrivateKey(Reader reader)
|
||||
throws IOException
|
||||
{
|
||||
List<PrivateKey> privateKeys = pemToPrivateKeys(reader);
|
||||
if (privateKeys.size() != 1)
|
||||
throw new IllegalArgumentException("The PEM stream must contain exactly one private key");
|
||||
return privateKeys.get(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a PEM reader, decode the contents into a list of certificates.
|
||||
*
|
||||
* @param reader Reader for a PEM-encoded stream
|
||||
* @return The list of decoded certificates from the stream
|
||||
* @throws CertificateException
|
||||
* @throws IOException
|
||||
* @see #writeToPEM
|
||||
*/
|
||||
public static List<X509Certificate> pemToCerts(Reader reader)
|
||||
throws CertificateException, IOException
|
||||
{
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
|
||||
List<Object> pemObjects = pemToObjects(reader);
|
||||
List<X509Certificate> results = new ArrayList<X509Certificate>(pemObjects.size());
|
||||
for (Object o : pemObjects)
|
||||
results.add(converter.getCertificate((X509CertificateHolder) o));
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a private key to a keystore.
|
||||
*
|
||||
* @param keystore The keystore to add the private key to
|
||||
* @param alias An alias to associate with the private key
|
||||
* @param privateKey The private key to add to the keystore
|
||||
* @param password To protect the key in the keystore
|
||||
* @param cert The certificate for the private key; a private key cannot
|
||||
* be added to a keystore without a signed certificate
|
||||
* @return The provided keystore
|
||||
* @throws KeyStoreException
|
||||
* @see #associatePrivateKeyFromReader
|
||||
*/
|
||||
public static KeyStore associatePrivateKey(KeyStore keystore, String alias, PrivateKey privateKey,
|
||||
String password, X509Certificate cert)
|
||||
throws KeyStoreException
|
||||
{
|
||||
keystore.setKeyEntry(alias, privateKey, password.toCharArray(), new Certificate[]{cert});
|
||||
return keystore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the private key from a PEM reader to the keystore.
|
||||
*
|
||||
* @param keystore The keystore to add the private key to
|
||||
* @param alias An alias to associate with the private key
|
||||
* @param pemPrivateKey Reader for a PEM-encoded stream with the private key
|
||||
* @param password To protect the key in the keystore
|
||||
* @param pemCert Reader for a PEM-encoded stream with the certificate; a private
|
||||
* key cannot be added to a keystore without a signed certificate
|
||||
* @return The provided keystore
|
||||
* @throws CertificateException
|
||||
* @throws KeyStoreException
|
||||
* @throws IOException
|
||||
* @see #associatePrivateKey
|
||||
*/
|
||||
public static KeyStore associatePrivateKeyFromReader(KeyStore keystore, String alias, Reader pemPrivateKey,
|
||||
String password, Reader pemCert)
|
||||
throws CertificateException, KeyStoreException, IOException
|
||||
{
|
||||
PrivateKey privateKey = pemToPrivateKey(pemPrivateKey);
|
||||
List<X509Certificate> certs = pemToCerts(pemCert);
|
||||
|
||||
if (certs.size() > 1)
|
||||
throw new IllegalArgumentException("The PEM stream contains more than one certificate");
|
||||
|
||||
X509Certificate firstCert = certs.get(0);
|
||||
return associatePrivateKey(keystore, alias, privateKey, password, firstCert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a certificate to a keystore.
|
||||
*
|
||||
* @param keystore The keystore to add the certificate to
|
||||
* @param alias An alias to associate with the certificate
|
||||
* @param cert The certificate to add to the keystore
|
||||
* @return The provided keystore
|
||||
* @throws KeyStoreException
|
||||
* @see #associateCertsFromReader
|
||||
*/
|
||||
public static KeyStore associateCert(KeyStore keystore, String alias, X509Certificate cert)
|
||||
throws KeyStoreException
|
||||
{
|
||||
keystore.setCertificateEntry(alias, cert);
|
||||
return keystore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all certificates from a PEM reader to the keystore.
|
||||
*
|
||||
* @param keystore The keystore to add all the certificates to
|
||||
* @param prefix An alias to associate with the certificates. Each certificate will
|
||||
* have a numeric index appended to the prefix (starting with '-0')
|
||||
* @param pem Reader for a PEM-encoded stream of certificates
|
||||
* @return The provided keystore
|
||||
* @throws CertificateException
|
||||
* @throws KeyStoreException
|
||||
* @throws IOException
|
||||
* @see #associateCert
|
||||
*/
|
||||
public static KeyStore associateCertsFromReader(KeyStore keystore, String prefix, Reader pem)
|
||||
throws CertificateException, KeyStoreException, IOException
|
||||
{
|
||||
List<X509Certificate> certs = pemToCerts(pem);
|
||||
ListIterator<X509Certificate> iter = certs.listIterator();
|
||||
for (int i = 0; iter.hasNext(); i++)
|
||||
associateCert(keystore, prefix + "-" + i, iter.next());
|
||||
return keystore;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create an empty in-memory key store.
|
||||
*
|
||||
* @return New key store
|
||||
* @throws KeyStoreException
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CertificateException
|
||||
*/
|
||||
public static KeyStore createKeyStore()
|
||||
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException
|
||||
{
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(null);
|
||||
return ks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given PEM readers for a certificate, private key, and CA certificate,
|
||||
* create an in-memory keystore and truststore.
|
||||
*
|
||||
* Returns a map containing the following:
|
||||
* <ul>
|
||||
* <li>"keystore" - a keystore initialized with the cert and private key</li>
|
||||
* <li>"keystore-pw" - a string containing a dynamically generated password for the keystore</li>
|
||||
* <li>"truststore" - a keystore containing the CA cert</li>
|
||||
* <ul>
|
||||
*
|
||||
* @param cert Reader for a PEM-encoded stream with the certificate
|
||||
* @param privateKey Reader for a PEM-encoded stream with the correspnding private key
|
||||
* @param caCert Reader for a PEM-encoded stream with the CA certificate
|
||||
* @return Map containing the keystore, keystore password, and truststore
|
||||
* @throws KeyStoreException
|
||||
* @throws CertificateException
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static Map<String, Object> pemsToKeyAndTrustStores(Reader cert, Reader privateKey, Reader caCert)
|
||||
throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException
|
||||
{
|
||||
KeyStore truststore = createKeyStore();
|
||||
associateCertsFromReader(truststore, "CA Certificate", caCert);
|
||||
|
||||
KeyStore keystore = createKeyStore();
|
||||
String keystorePassword = UUID.randomUUID().toString();
|
||||
associatePrivateKeyFromReader(keystore, "Private Key", privateKey, keystorePassword, cert);
|
||||
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
result.put("truststore", truststore);
|
||||
result.put("keystore", keystore);
|
||||
result.put("keystore-pw", keystorePassword);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a keystore and keystore password (as generated by {@link #pemsToKeyAndTrustStores}),
|
||||
* return a key manager factory that contains the keystore.
|
||||
*
|
||||
* @param keystore The keystore to get a key manager for
|
||||
* @param password The password for the keystore
|
||||
* @return A key manager factory for the provided keystore
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws KeyStoreException
|
||||
* @throws UnrecoverableKeyException
|
||||
*/
|
||||
public static KeyManagerFactory getKeyManagerFactory(KeyStore keystore, String password)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException
|
||||
{
|
||||
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
factory.init(keystore, password.toCharArray());
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a truststore (as generated by {@link #pemsToKeyAndTrustStores}),
|
||||
* return a trust manager factory that contains the truststore.
|
||||
*
|
||||
* @param truststore The truststore to get a trust manager for
|
||||
* @return A trust manager factory for the provided truststore
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws KeyStoreException
|
||||
*/
|
||||
public static TrustManagerFactory getTrustManagerFactory(KeyStore truststore)
|
||||
throws NoSuchAlgorithmException, KeyStoreException
|
||||
{
|
||||
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
factory.init(truststore);
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given PEM readers for a certificate, private key, and CA certificate, create an
|
||||
* in-memory SSL context initialized with a keystore/truststore generated from the
|
||||
* provided certificates and key.
|
||||
*
|
||||
* @param cert Reader for PEM-encoded stream with the certificate
|
||||
* @param privateKey Reader for PEM-encoded stream with the corresponding private key
|
||||
* @param caCert Reader for PEM-encoded stream with the CA certificate
|
||||
* @return The configured SSLContext
|
||||
* @throws KeyStoreException
|
||||
* @throws CertificateException
|
||||
* @throws IOException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws KeyManagementException
|
||||
* @throws UnrecoverableKeyException
|
||||
*/
|
||||
public static SSLContext pemsToSSLContext(Reader cert, Reader privateKey, Reader caCert)
|
||||
throws KeyStoreException, CertificateException, IOException,
|
||||
NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException
|
||||
{
|
||||
Map<String, Object> stores = pemsToKeyAndTrustStores(cert, privateKey, caCert);
|
||||
KeyStore keystore = (KeyStore) stores.get("keystore");
|
||||
String password = (String) stores.get("keystore-pw");
|
||||
KeyStore truststore = (KeyStore) stores.get("truststore");
|
||||
KeyManagerFactory kmf = getKeyManagerFactory(keystore, password);
|
||||
TrustManagerFactory tmf = getTrustManagerFactory(truststore);
|
||||
SSLContext context = SSLContext.getInstance("SSL");
|
||||
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
return context;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* This package is basically just a straight port of the clojure code from
|
||||
* the org.httpkit.client namespace, so that we can make requests from Java
|
||||
* using this same library.
|
||||
*/
|
||||
package com.puppetlabs.http.client.impl;
|
|
@ -8,9 +8,9 @@
|
|||
(deftest ssl-config-with-files
|
||||
(let [req {:url "http://localhost"
|
||||
:method :get
|
||||
:ssl-cert (resource "resources/cert.pem")
|
||||
:ssl-key (resource "resources/key.pem")
|
||||
:ssl-ca-cert (resource "resources/ca.pem")}
|
||||
:ssl-cert (resource "ssl/cert.pem")
|
||||
:ssl-key (resource "ssl/key.pem")
|
||||
:ssl-ca-cert (resource "ssl/ca.pem")}
|
||||
configured-req (http/configure-ssl req)]
|
||||
|
||||
(testing "configure-ssl sets up an SSLEngine when given cert, key, ca-cert"
|
||||
|
@ -33,9 +33,9 @@
|
|||
(let [req {:url "http://localhost"
|
||||
:method :get
|
||||
:ssl-context (ks-ssl/pems->ssl-context
|
||||
(resource "resources/cert.pem")
|
||||
(resource "resources/key.pem")
|
||||
(resource "resources/ca.pem"))}
|
||||
(resource "ssl/cert.pem")
|
||||
(resource "ssl/key.pem")
|
||||
(resource "ssl/ca.pem"))}
|
||||
configured-req (http/configure-ssl req)]
|
||||
|
||||
(testing "configure-ssl uses an existing ssl context"
|
||||
|
@ -44,9 +44,9 @@
|
|||
(deftest ssl-config-with-sslengine
|
||||
(let [req {:url "http://localhost"
|
||||
:method :get
|
||||
:ssl-cert (resource "resources/cert.pem")
|
||||
:ssl-key (resource "resources/key.pem")
|
||||
:ssl-ca-cert (resource "resources/ca.pem")
|
||||
:ssl-cert (resource "ssl/cert.pem")
|
||||
:ssl-key (resource "ssl/key.pem")
|
||||
:ssl-ca-cert (resource "ssl/ca.pem")
|
||||
:sslengine "thing"}
|
||||
configured-req (http/configure-ssl req)]
|
||||
(testing "configure-ssl does nothing when :sslengine is given"
|
||||
|
|
44
test/puppetlabs/http/client/sync_test.clj
Normal file
44
test/puppetlabs/http/client/sync_test.clj
Normal file
|
@ -0,0 +1,44 @@
|
|||
(ns puppetlabs.http.client.sync-test
|
||||
(:import (com.puppetlabs.http.client SyncHttpClient RequestOptions))
|
||||
(:require [clojure.test :refer :all]
|
||||
[puppetlabs.trapperkeeper.core :as tk]
|
||||
[puppetlabs.trapperkeeper.testutils.bootstrap :as testutils]
|
||||
[puppetlabs.trapperkeeper.testutils.logging :as testlogging]
|
||||
[puppetlabs.trapperkeeper.services.webserver.jetty9-service :as jetty9]
|
||||
[puppetlabs.http.client.sync :as sync]))
|
||||
|
||||
(defn app
|
||||
[req]
|
||||
{:status 200
|
||||
:body "Hello, World!"})
|
||||
|
||||
(tk/defservice test-web-service
|
||||
[[:WebserverService add-ring-handler]]
|
||||
(init [this context]
|
||||
(add-ring-handler app "/hello")
|
||||
context))
|
||||
|
||||
(deftest sync-client-test
|
||||
(testlogging/with-test-logging
|
||||
(testutils/with-app-with-config app
|
||||
[jetty9/jetty9-service test-web-service]
|
||||
{:webserver {:ssl-host "0.0.0.0"
|
||||
:ssl-port 10080
|
||||
:ssl-ca-cert "./dev-resources/ssl/ca.pem"
|
||||
:ssl-cert "./dev-resources/ssl/cert.pem"
|
||||
:ssl-key "./dev-resources/ssl/key.pem"}}
|
||||
(testing "java sync client"
|
||||
(let [options (.. (RequestOptions. "https://localhost:10080/hello/")
|
||||
(setSslCert "./dev-resources/ssl/cert.pem")
|
||||
(setSslKey "./dev-resources/ssl/key.pem")
|
||||
(setSslCaCert "./dev-resources/ssl/ca.pem"))
|
||||
response (SyncHttpClient/get options)]
|
||||
(is (= 200 (.getStatus response)))
|
||||
(is (= "Hello, World!" (slurp (.getBody response))))))
|
||||
(testing "clojure sync client"
|
||||
(let [response (sync/get "https://localhost:10080/hello/"
|
||||
{:ssl-cert "./dev-resources/ssl/cert.pem"
|
||||
:ssl-key "./dev-resources/ssl/key.pem"
|
||||
:ssl-ca-cert "./dev-resources/ssl/ca.pem"})]
|
||||
(is (= 200 (:status response)))
|
||||
(is (= "Hello, World!" (slurp (:body response)))))))))
|
Loading…
Reference in a new issue