package org.stagemonitor.core.util; import com.fasterxml.jackson.databind.JsonNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.stagemonitor.util.IOUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.xml.bind.DatatypeConverter; // TODO create HttpRequest POJO // method, url, headers, outputStreamHandler, responseHandler // builder methods logErrors(int... excludedStatusCodes) public class HttpClient { private static final long CONNECT_TIMEOUT_SEC = 5; private static final long READ_TIMEOUT_SEC = 15; private final Logger logger = LoggerFactory.getLogger(getClass()); public int send(final String method, final String url) { return send(method, url, null, null); } public JsonNode getJson(String url, Map<String, String> headers) { headers = new HashMap<String, String>(headers); headers.put("Accept", "application/json"); return send("GET", url, headers, null, new ResponseHandler<JsonNode>() { @Override public JsonNode handleResponse(InputStream is, Integer statusCode, IOException e) throws IOException { return JsonUtils.getMapper().readTree(is); } }); } public int sendAsJson(final String method, final String url, final Object requestBody) { return sendAsJson(method, url, requestBody, new HashMap<String, String>()); } public int sendAsJson(final String method, final String url, final Object requestBody, Map<String, String> headerFields) { headerFields = new HashMap<String, String>(headerFields); headerFields.put("Content-Type", "application/json"); return send(method, url, headerFields, new OutputStreamHandler() { @Override public void withHttpURLConnection(OutputStream os) throws IOException { writeRequestBody(requestBody, os); } }); } public int send(String method, String url, final List<String> requestBodyLines) { return send(method, url, null, new OutputStreamHandler() { @Override public void withHttpURLConnection(OutputStream os) throws IOException { for (String line : requestBodyLines) { os.write(line.getBytes("UTF-8")); os.write('\n'); } os.flush(); } }); } public int send(final String method, final String url, final Map<String, String> headerFields, OutputStreamHandler outputStreamHandler) { Integer result = send(method, url, headerFields, outputStreamHandler, new ErrorLoggingResponseHandler(url)); return result == null ? -1 : result; } public <T> T send(final String method, final String url, final Map<String, String> headerFields, OutputStreamHandler outputStreamHandler, ResponseHandler<T> responseHandler) { HttpURLConnection connection = null; InputStream inputStream = null; try { URL parsedUrl = new URL(url); connection = (HttpURLConnection) parsedUrl.openConnection(); if (parsedUrl.getUserInfo() != null) { String basicAuth = "Basic " + DatatypeConverter.printBase64Binary(parsedUrl.getUserInfo().getBytes()); connection.setRequestProperty("Authorization", basicAuth); } connection.setDoOutput(true); connection.setRequestMethod(method); connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(CONNECT_TIMEOUT_SEC)); connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(READ_TIMEOUT_SEC)); if (headerFields != null) { for (Map.Entry<String, String> header : headerFields.entrySet()) { connection.setRequestProperty(header.getKey(), header.getValue()); } } if (outputStreamHandler != null) { outputStreamHandler.withHttpURLConnection(connection.getOutputStream()); } inputStream = connection.getInputStream(); return responseHandler.handleResponse(inputStream, connection.getResponseCode(), null); } catch (IOException e) { if (connection != null) { inputStream = connection.getErrorStream(); try { return responseHandler.handleResponse(inputStream, getResponseCodeIfPossible(connection), e); } catch (IOException e1) { logger.warn("Error sending {} request to url {}: {}", method, url, e.getMessage(), e); logger.warn("Error handling error response for {} request to url {}: {}", method, url, e1.getMessage(), e1); try { logger.trace(new String(IOUtils.readToBytes(inputStream), "UTF-8")); } catch (IOException e2) { logger.trace("Could not read error stream: {}", e2.getMessage(), e2); } } } else { logger.warn("Error sending {} request to url {}: {}", method, url, e.getMessage(), e); } return null; } finally { IOUtils.closeQuietly(inputStream); } } private Integer getResponseCodeIfPossible(HttpURLConnection connection) { try { return connection.getResponseCode(); } catch (IOException e) { // don't handle exception twice return null; } } private void writeRequestBody(Object requestBody, OutputStream os) throws IOException { if (requestBody != null) { if (requestBody instanceof InputStream) { IOUtils.copy((InputStream) requestBody, os); } else if (requestBody instanceof String) { os.write(((String)requestBody).getBytes("UTF-8")); } else { JsonUtils.writeJsonToOutputStream(requestBody, os); } os.flush(); } } public interface OutputStreamHandler { void withHttpURLConnection(OutputStream os) throws IOException; } public interface ResponseHandler<T> { T handleResponse(InputStream is, Integer statusCode, IOException e) throws IOException; } private static class ErrorLoggingResponseHandler implements ResponseHandler<Integer> { private final Logger logger = LoggerFactory.getLogger(getClass()); private final String url; public ErrorLoggingResponseHandler(String url) { this.url = url; } @Override public Integer handleResponse(InputStream is, Integer statusCode, IOException e) throws IOException { if (statusCode == null) { return -1; } if (statusCode >= 400) { logger.warn(url + ": " + statusCode + " " + IOUtils.toString(is)); } else { IOUtils.consumeAndClose(is); } return statusCode; } } }