com.tectonica.util.HTTP Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tectonica-commons Show documentation
Show all versions of tectonica-commons Show documentation
Set of Java utility classes, all completely independent, to provide lightweight solutions for common situations
/*
* Copyright (C) 2014 Zach Melamed
*
* Latest version available online at https://github.com/zach-m/tectonica-commons
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tectonica.util;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.DatatypeConverter;
/**
* General-purpose HTTP utility class for invoking HTTP requests using only Java's standard network packages (in particular, avoiding Apache
* Commons HttpClient). The class can be be operated either by its all-arguments static method
* {@link #execute(String, String, String, Headers, Attachment, CookieStore)}, or, more conveniently, by its builder service, accessible via
* {@link HTTP#url(String)}.
*
* The class supports several common requirements:
*
* - Provide custom headers
*
- Receive cookies and send them in consequent requests (using a {@link CookieStore})
*
- Add "attachment" (i.e. send a multipart content)
*
- Measure latency
*
*
* @author Zach Melamed
*/
public class HTTP
{
public static class HttpResponse
{
public final int statusCode;
public final String content;
public final Map> headers;
public final long latency;
public HttpResponse(int statusCode, String content, Map> headers, long latency)
{
this.statusCode = statusCode;
this.content = content;
this.headers = headers;
this.latency = latency;
}
@Override
public String toString()
{
return "(HTTP " + statusCode + "): [" + content + "]\n" + headers.toString();
}
}
public static class Headers implements Iterable>
{
private Map headers = new LinkedHashMap<>();
public void add(String headerName, String headerValue, boolean exclusive)
{
String value = headers.get(headerName);
if (value == null || exclusive)
value = headerValue;
else
value += ", " + headerValue;
headers.put(headerName, value);
}
@Override
public Iterator> iterator()
{
return headers.entrySet().iterator();
}
}
public static class Attachment
{
public final String fieldName;
public final String fileName;
public final File file;
public final InputStream is;
public Attachment(File file, String fieldName)
{
this.fieldName = fieldName;
this.file = file;
this.fileName = file.getName();
this.is = null;
}
public Attachment(InputStream is, String fieldName, String fileName)
{
this.fieldName = fieldName;
this.file = null;
this.fileName = fileName;
this.is = is;
}
private InputStream getStream()
{
if (is != null)
return is;
try
{
return new FileInputStream(file);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
public static class CookieStore
{
private Map cookies = new HashMap();
// TODO: add manual setter and getter for custom cookies ?
private void fromHeader(List setCookies)
{
if (setCookies != null && !setCookies.isEmpty())
{
for (String cookie : setCookies)
{
String[] kv = cookie.split(";")[0].trim().split("=");
if (kv[1].isEmpty())
cookies.remove(kv[0]);
else
cookies.put(kv[0], kv[1]);
}
}
}
private String toHeader()
{
if (isEmpty())
return null;
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Iterator> iter = cookies.entrySet().iterator(); iter.hasNext();)
{
Entry kv = iter.next();
if (first)
first = false;
else
sb.append(",");
sb.append(kv.getKey()).append("=").append(kv.getValue());
}
return sb.toString();
}
public boolean isEmpty()
{
return cookies.isEmpty();
}
}
private static final String CRLF = "\r\n";
private static final String HYPHENS = "--";
private static final String BOUNDARY = "*************";
public static HttpResponse execute(String method, String url, String body, Headers headers, Attachment attachment, CookieStore cs)
{
HttpURLConnection conn = null;
try
{
boolean sendText = (body != null && !body.isEmpty());
boolean sendMultipart = !sendText && (attachment != null);
long timeBefore = System.currentTimeMillis();
conn = (HttpURLConnection) (new URL(url)).openConnection();
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(sendText || sendMultipart);
conn.setRequestMethod(method);
for (Entry header : headers)
conn.setRequestProperty(header.getKey(), header.getValue());
if (cs != null && !cs.isEmpty())
conn.setRequestProperty("Cookie", cs.toHeader());
// // TODO: support extra options:
// conn.setRequestProperty("Connection", "Keep-Alive");
// conn.setRequestProperty("Cache-Control", "no-cache");
// conn.setReadTimeout(30000);
// conn.setConnectTimeout(30000);
// send the request's body
if (sendText)
{
byte[] requestContent = body.getBytes();
conn.setRequestProperty("Content-Length", "" + requestContent.length);
try (OutputStream out = conn.getOutputStream())
{
out.write(requestContent);
out.flush();
}
// // TODO: support more efficient alternative, but without sending "Content-Length"
// try (Writer writer = new OutputStreamWriter(conn.getOutputStream()))
// {
// writer.write(body);
// writer.flush();
// }
}
else if (sendMultipart)
{
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
try (DataOutputStream out = new DataOutputStream(conn.getOutputStream()))
{
writeFile(attachment.getStream(), attachment.fieldName, attachment.fileName, out); // TODO: re-iterate with other files
out.writeBytes(HYPHENS + BOUNDARY + HYPHENS + CRLF);
out.flush();
}
}
Map> responseHeaders = conn.getHeaderFields();
if (cs != null)
cs.fromHeader(responseHeaders.get("Set-Cookie"));
// get the response
int statusCode = conn.getResponseCode();
InputStream is = (statusCode / 100 == 2) ? conn.getInputStream() : conn.getErrorStream();
String content = (is == null) ? "" : streamToContent(is);
long latency = System.currentTimeMillis() - timeBefore;
return new HttpResponse(statusCode, content, responseHeaders, latency);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
public static String urlEncoded(String url)
{
try
{
return URLEncoder.encode(url, "UTF-8").replace("+", "%20");
}
catch (UnsupportedEncodingException e)
{
return null; // never happens as "UTF-8" is always a valid encoding
}
}
private static String streamToContent(InputStream is) throws IOException
{
StringBuffer sb = new StringBuffer();
try (BufferedReader in = new BufferedReader(new InputStreamReader(is)))
{
String line;
while ((line = in.readLine()) != null)
sb.append(line).append("\n");
}
// remove the trailing NL
return (sb.length() == 0) ? "" : sb.substring(0, sb.length() - "\n".length());
}
private static void writeFile(InputStream is, String fieldName, String fileName, DataOutputStream out) throws IOException,
FileNotFoundException
{
out.writeBytes(HYPHENS + BOUNDARY + CRLF);
out.writeBytes("Content-Disposition: form-data; name=\"" + fieldName + "\";filename=\"" + fileName + "\"" + CRLF);
out.writeBytes(CRLF);
byte[] buffer = new byte[8192];
int bytesRead = -1;
while ((bytesRead = is.read(buffer)) != -1)
out.write(buffer, 0, bytesRead);
out.flush();
is.close();
out.writeBytes(CRLF);
}
// /////////////////////////////////////////////////////////////////////////////////
//
// BUILDER API
//
// /////////////////////////////////////////////////////////////////////////////////
private final String url;
private String body = null;
private Attachment attachment = null;
private CookieStore cookieStore = null;
private Headers headers = new Headers();
public static HTTP url(String url)
{
return new HTTP(url);
}
public static HTTP url(String url, boolean encodeUrl)
{
return new HTTP(urlEncoded(url));
}
private HTTP(String url)
{
this.url = url;
}
public HTTP body(String body)
{
this.body = body;
return this;
}
public HTTP contentType(String contentTypeHeader)
{
return header("Content-Type", contentTypeHeader, true);
}
public HTTP accept(String acceptHeader)
{
return header("Accept", acceptHeader, false);
}
public HTTP basicAuthorization(String username, String password)
{
String login = username + ":" + password;
String encodedLogin = DatatypeConverter.printBase64Binary(login.getBytes());
return header("Authorization", "Basic " + encodedLogin, true);
}
public HTTP header(String headerName, String headerValue)
{
return header(headerName, headerValue, false);
}
public HTTP header(String headerName, String headerValue, boolean exclusive)
{
headers.add(headerName, headerValue, exclusive);
return this;
}
public HTTP cookieStore(CookieStore cookieStore)
{
this.cookieStore = cookieStore;
return this;
}
public HTTP attach(Attachment attachment)
{
this.attachment = attachment;
return this;
}
// //////////////////////////////////////////////////////////////////////////////////
private HttpResponse execute(String method)
{
return execute(method, url, body, headers, attachment, cookieStore);
}
public HttpResponse GET()
{
return execute("GET");
}
public HttpResponse POST()
{
return execute("POST");
}
public HttpResponse PUT()
{
return execute("PUT");
}
public HttpResponse DELETE()
{
return execute("DELETE");
}
}