org.daisy.pipeline.client.http.Pipeline2HttpClient Maven / Gradle / Ivy
package org.daisy.pipeline.client.http;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.w3c.dom.Document;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;
import org.daisy.pipeline.client.Pipeline2Exception;
import org.daisy.pipeline.client.Pipeline2Logger;
import org.daisy.pipeline.client.utils.XML;
/** Implementation of DP2HttpClient that uses Apache HTTP Client as the underlying HTTP client. */
public class Pipeline2HttpClient {
// TODO: implement PUT support put(...) ?
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public static DateFormat iso8601;
static {
iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
iso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
}
/**
* Send a GET request.
* @param endpoint WS endpoint, for instance "http://localhost:8182/ws".
* @param path Path to resource, for instance "/scripts".
* @param username Robot username. Can be null. If null, then the URL will not be signed.
* @param secret Robot secret. Can be null.
* @param parameters URL query string parameters
* @return The return body.
* @throws Pipeline2Exception
*/
public static WSResponse get(String endpoint, String path, String username, String secret, Map parameters) throws Pipeline2Exception {
return getDelete("GET", endpoint, path, username, secret, parameters);
}
/**
* Send a DELETE request.
* @param endpoint WS endpoint, for instance "http://localhost:8182/ws".
* @param path Path to resource, for instance "/scripts".
* @param username Robot username. Can be null. If null, then the URL will not be signed.
* @param secret Robot secret. Can be null.
* @param parameters URL query string parameters
* @return The return body.
* @throws Pipeline2Exception
*/
public static WSResponse delete(String endpoint, String path, String username, String secret, Map parameters) throws Pipeline2Exception {
return getDelete("DELETE", endpoint, path, username, secret, parameters);
}
private static WSResponse getDelete(String method, String endpoint, String path, String username, String secret, Map parameters) throws Pipeline2Exception {
String url = url(endpoint, path, username, secret, parameters);
if (endpoint == null) {
return new WSResponse(url, 503, "Endpoint is not set", "Please provide a Pipeline 2 endpoint.", null, null, null);
}
HttpClient httpclient = new DefaultHttpClient();
HttpRequestBase http;
if ("DELETE".equals(method)) {
http = new HttpDelete(url);
} else { // "GET"
http = new HttpGet(url);
}
HttpResponse response = null;
try {
response = httpclient.execute(http);
} catch (ClientProtocolException e) {
throw new Pipeline2Exception("Error while "+method+"ing.", e);
} catch (IOException e) {
throw new Pipeline2Exception("Error while "+method+"ing.", e);
}
HttpEntity resEntity = response.getEntity();
InputStream bodyStream = null;
if (resEntity != null) {
try {
bodyStream = resEntity.getContent();
} catch (IOException e) {
throw new Pipeline2Exception("Error while reading response body", e);
}
}
int status = response.getStatusLine() != null ? response.getStatusLine().getStatusCode() : 204; // Not sure if it's ok to default to 204, but let's try!
String statusName = response.getStatusLine() != null ? response.getStatusLine().getReasonPhrase() : "";
String statusDescription = null;
String contentType = response.getFirstHeader("Content-Type") != null ? response.getFirstHeader("Content-Type").getValue() : "application/octet-stream";
Long size = (resEntity != null && resEntity.getContentLength() >= 0) ? resEntity.getContentLength() : null;
return new WSResponse(url, status, statusName, statusDescription, contentType, size, bodyStream);
}
/**
* POST an XML document.
* @param endpoint WS endpoint, for instance "http://localhost:8182/ws".
* @param path Path to resource, for instance "/scripts".
* @param username Robot username. Can be null. If null, then the URL will not be signed.
* @param secret Robot secret. Can be null.
* @param xml The XML document to post.
* @return The return body.
* @throws Pipeline2Exception
*/
public static WSResponse postXml(String endpoint, String path, String username, String secret, Document xml) throws Pipeline2Exception {
String url = url(endpoint, path, username, secret, null);
if (Pipeline2Logger.logger().logsLevel(Pipeline2Logger.LEVEL.DEBUG)) {
Pipeline2Logger.logger().debug("URL: ["+url+"]");
Pipeline2Logger.logger().debug(XML.toString(xml));
}
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
StringEntity entity;
try {
entity = new StringEntity(XML.toString(xml), "application/xml", HTTP.UTF_8);
} catch (UnsupportedEncodingException e) {
throw new Pipeline2Exception("Error while serializing XML for POSTing.", e);
}
httppost.setEntity(entity);
HttpResponse response = null;
try {
response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {
throw new Pipeline2Exception("Error while POSTing.", e);
} catch (IOException e) {
throw new Pipeline2Exception("Error while POSTing.", e);
}
HttpEntity resEntity = response.getEntity();
InputStream bodyStream = null;
try {
bodyStream = resEntity.getContent();
} catch (IOException e) {
throw new Pipeline2Exception("Error while reading response body", e);
}
return new WSResponse(url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), null,
response.getFirstHeader("Content-Type").getValue(),
resEntity.getContentLength()>=0?resEntity.getContentLength():null,
bodyStream);
}
/**
* POST a multipart request.
* @param endpoint WS endpoint, for instance "http://localhost:8182/ws".
* @param path Path to resource, for instance "/scripts".
* @param username Robot username. Can be null. If null, then the URL will not be signed.
* @param secret Robot secret. Can be null.
* @param parts A map of all the parts.
* @return The return body.
* @throws Pipeline2Exception
*/
public static WSResponse postMultipart(String endpoint, String path, String username, String secret, Map parts) throws Pipeline2Exception {
String url = url(endpoint, path, username, secret, null);
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
MultipartEntity reqEntity = new MultipartEntity();
for (String partName : parts.keySet()) {
reqEntity.addPart(partName, new FileBody(parts.get(partName)));
}
httppost.setEntity(reqEntity);
HttpResponse response = null;
try {
response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {
throw new Pipeline2Exception("Error while POSTing.", e);
} catch (IOException e) {
throw new Pipeline2Exception("Error while POSTing.", e);
}
HttpEntity resEntity = response.getEntity();
InputStream bodyStream = null;
try {
bodyStream = resEntity.getContent();
} catch (IOException e) {
throw new Pipeline2Exception("Error while reading response body", e);
}
return new WSResponse(url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), null,
response.getFirstHeader("Content-Type").getValue(),
resEntity.getContentLength()>=0?resEntity.getContentLength():null,
bodyStream);
}
/**
* Sign a URL for communication with a Pipeline 2 Web Service running in authenticated mode.
*
* @param endpoint
* @param path
* @param username
* @param secret
* @param parameters
* @return
* @throws Pipeline2Exception
*/
public static String url(String endpoint, String path, String username, String secret, Map parameters) throws Pipeline2Exception {
boolean hasAuth = !(username == null || "".equals(username) || secret == null || "".equals(secret));
String url = endpoint + path;
if (parameters != null && parameters.size() > 0 || hasAuth)
url += "?";
if (parameters != null) {
for (String name : parameters.keySet()) {
try { url += URLEncoder.encode(name, "UTF-8") + "=" + URLEncoder.encode(parameters.get(name), "UTF-8") + "&"; }
catch (UnsupportedEncodingException e) { throw new Pipeline2Exception("Unsupported encoding: UTF-8", e); }
}
}
if (hasAuth) {
String time = iso8601.format(new Date());
String nonce = "";
while (nonce.length() < 30)
nonce += (Math.random()+"").substring(2);
nonce = nonce.substring(0, 30);
url += "authid="+username + "&time="+time + "&nonce="+nonce;
String hash = "";
try {
hash = calculateRFC2104HMAC(url, secret);
String hashEscaped = "";
char c;
for (int i = 0; i < hash.length(); i++) {
// Base64 encoding uses + which we have to encode in URL parameters.
// Hoping this for loop is more efficient than the equivalent replace("\\+","%2B") regex.
c = hash.charAt(i);
if (c == '+') hashEscaped += "%2B";
else hashEscaped += c;
}
url += "&sign="+hashEscaped;
} catch (SignatureException e) {
throw new Pipeline2Exception("Could not sign request.");
}
}
return url;
}
// adapted slightly from
// http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/index.html?AuthJavaSampleHMACSignature.html
// (copied from Pipeline 2 fwk code)
/**
* Computes RFC 2104-compliant HMAC signature.
* * @param data
* The data to be signed.
* @param secret
* The signing secret.
* @return
* The Base64-encoded RFC 2104-compliant HMAC signature.
* @throws
* java.security.SignatureException when signature generation fails
*/
private static String calculateRFC2104HMAC(String data, String secret) throws java.security.SignatureException {
byte[] result;
try {
// get an hmac_sha1 key from the raw key bytes
SecretKeySpec signingSecret = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM);
// get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingSecret);
// compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(data.getBytes());
// base64-encode the hmac
result = Base64.getEncoder().encode(rawHmac);
} catch (Exception e) {
throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
}
return new String(result);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy