diff --git a/plugin.xml b/plugin.xml index b5beef8..21b964e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -56,17 +56,12 @@ - + + - - diff --git a/src/android/com/github/kevinsawicki/http/HttpRequest.java b/src/android/com/github/kevinsawicki/http/HttpRequest.java deleted file mode 100644 index 935c972..0000000 --- a/src/android/com/github/kevinsawicki/http/HttpRequest.java +++ /dev/null @@ -1,3449 +0,0 @@ -/* - * Copyright (c) 2014 Kevin Sawicki - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -package com.github.kevinsawicki.http; - -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; -import static java.net.HttpURLConnection.HTTP_CREATED; -import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; -import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.Proxy.Type.HTTP; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.Flushable; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintStream; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.security.AccessController; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.PrivilegedAction; -import java.security.SecureRandom; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.zip.GZIPInputStream; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.KeyManager; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import android.text.TextUtils; - -/** - * A fluid interface for making HTTP requests using an underlying - * {@link HttpURLConnection} (or sub-class). - *

- * Each instance supports making a single request and cannot be reused for - * further requests. - */ -public class HttpRequest { - - /** - * 'UTF-8' charset name - */ - public static final String CHARSET_UTF8 = "UTF-8"; - - /** - * 'ISO-8859-1' charset name - */ - public static final String CHARSET_LATIN1 = "ISO-8859-1"; - - /** - * 'application/x-www-form-urlencoded' content type header value - */ - public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"; - - /** - * 'application/json' content type header value - */ - public static final String CONTENT_TYPE_JSON = "application/json"; - - /** - * 'gzip' encoding header value - */ - public static final String ENCODING_GZIP = "gzip"; - - /** - * 'Accept' header name - */ - public static final String HEADER_ACCEPT = "Accept"; - - /** - * 'Accept-Charset' header name - */ - public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset"; - - /** - * 'Accept-Encoding' header name - */ - public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; - - /** - * 'Authorization' header name - */ - public static final String HEADER_AUTHORIZATION = "Authorization"; - - /** - * 'Cache-Control' header name - */ - public static final String HEADER_CACHE_CONTROL = "Cache-Control"; - - /** - * 'Content-Encoding' header name - */ - public static final String HEADER_CONTENT_ENCODING = "Content-Encoding"; - - /** - * 'Content-Length' header name - */ - public static final String HEADER_CONTENT_LENGTH = "Content-Length"; - - /** - * 'Content-Type' header name - */ - public static final String HEADER_CONTENT_TYPE = "Content-Type"; - - /** - * 'Date' header name - */ - public static final String HEADER_DATE = "Date"; - - /** - * 'ETag' header name - */ - public static final String HEADER_ETAG = "ETag"; - - /** - * 'Expires' header name - */ - public static final String HEADER_EXPIRES = "Expires"; - - /** - * 'If-None-Match' header name - */ - public static final String HEADER_IF_NONE_MATCH = "If-None-Match"; - - /** - * 'Last-Modified' header name - */ - public static final String HEADER_LAST_MODIFIED = "Last-Modified"; - - /** - * 'Location' header name - */ - public static final String HEADER_LOCATION = "Location"; - - /** - * 'Proxy-Authorization' header name - */ - public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization"; - - /** - * 'Referer' header name - */ - public static final String HEADER_REFERER = "Referer"; - - /** - * 'Server' header name - */ - public static final String HEADER_SERVER = "Server"; - - /** - * 'User-Agent' header name - */ - public static final String HEADER_USER_AGENT = "User-Agent"; - - /** - * 'DELETE' request method - */ - public static final String METHOD_DELETE = "DELETE"; - - /** - * 'GET' request method - */ - public static final String METHOD_GET = "GET"; - - /** - * 'HEAD' request method - */ - public static final String METHOD_HEAD = "HEAD"; - - /** - * 'OPTIONS' options method - */ - public static final String METHOD_OPTIONS = "OPTIONS"; - - /** - * 'POST' request method - */ - public static final String METHOD_POST = "POST"; - - /** - * 'PATCH' request method - */ - public static final String METHOD_PATCH = "PATCH"; - - /** - * 'PUT' request method - */ - public static final String METHOD_PUT = "PUT"; - - /** - * 'TRACE' request method - */ - public static final String METHOD_TRACE = "TRACE"; - - /** - * 'charset' header value parameter - */ - public static final String PARAM_CHARSET = "charset"; - - public static final String CERT_MODE_DEFAULT = "default"; - - public static final String CERT_MODE_PINNED = "pinned"; - - public static final String CERT_MODE_TRUSTALL = "trustall"; - - private static final String BOUNDARY = "00content0boundary00"; - - private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary=" - + BOUNDARY; - - private static final String CRLF = "\r\n"; - - private static final String[] EMPTY_STRINGS = new String[0]; - - private static SSLSocketFactory SOCKET_FACTORY; - - private static String CURRENT_CERT_MODE = CERT_MODE_DEFAULT; - - private static ArrayList PINNED_CERTS; - - private static HostnameVerifier HOSTNAME_VERIFIER; - - private static String getValidCharset(final String charset) { - if (charset != null && charset.length() > 0) - return charset; - else - return CHARSET_UTF8; - } - - /** - * Configure SSL cert handling for all future HTTPS connections - * - * @param mode - */ - public static void setSSLCertMode(String mode) { - try { - if (mode == CERT_MODE_TRUSTALL) { - SOCKET_FACTORY = createSocketFactory(getNoopTrustManagers()); - HOSTNAME_VERIFIER = getTrustedVerifier(); - } else if (mode == CERT_MODE_PINNED) { - SOCKET_FACTORY = createSocketFactory(getPinnedTrustManagers()); - HOSTNAME_VERIFIER = null; - } else { - SOCKET_FACTORY = null; - HOSTNAME_VERIFIER = null; - } - - CURRENT_CERT_MODE = mode; - } catch(IOException e) { - throw new HttpRequestException(e); - } - } - - private static TrustManager[] getPinnedTrustManagers() throws IOException { - if (PINNED_CERTS == null) { - throw new IOException("You must add at least 1 certificate in order to pin to certificates"); - } - - try { - String keyStoreType = KeyStore.getDefaultType(); - KeyStore keyStore = KeyStore.getInstance(keyStoreType); - keyStore.load(null, null); - - for (int i = 0; i < PINNED_CERTS.size(); i++) { - keyStore.setCertificateEntry("CA" + i, PINNED_CERTS.get(i)); - } - - // Create a TrustManager that trusts the CAs in our KeyStore - String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); - tmf.init(keyStore); - - return tmf.getTrustManagers(); - } catch (GeneralSecurityException e) { - IOException ioException = new IOException("Security exception configuring SSL trust managers"); - ioException.initCause(e); - throw new HttpRequestException(ioException); - } - } - - private static TrustManager[] getNoopTrustManagers() { - return new TrustManager[] { new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - public void checkClientTrusted(X509Certificate[] chain, String authType) { - // Intentionally left blank - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) { - // Intentionally left blank - } - }}; - } - - private static SSLSocketFactory createSocketFactory(TrustManager[] trustManagers) - throws HttpRequestException { - try { - SSLContext context = SSLContext.getInstance("TLS"); - context.init(keyManagers, trustManagers, new SecureRandom()); - - if (android.os.Build.VERSION.SDK_INT < 20) { - return new TLSSocketFactory(context); - } else { - return context.getSocketFactory(); - } - } catch (GeneralSecurityException e) { - IOException ioException = new IOException("Security exception configuring SSL context"); - ioException.initCause(e); - throw new HttpRequestException(ioException); - } - } - - private static HostnameVerifier getTrustedVerifier() { - return new HostnameVerifier() { - public boolean verify(String hostname, SSLSession session) { - return true; - } - }; - } - - private static StringBuilder addPathSeparator(final String baseUrl, - final StringBuilder result) { - // Add trailing slash if the base URL doesn't have any path segments. - // - // The following test is checking for the last slash not being part of - // the protocol to host separator: '://'. - if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/')) - result.append('/'); - return result; - } - - private static StringBuilder addParamPrefix(final String baseUrl, - final StringBuilder result) { - // Add '?' if missing and add '&' if params already exist in base url - final int queryStart = baseUrl.indexOf('?'); - final int lastChar = result.length() - 1; - if (queryStart == -1) - result.append('?'); - else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&') - result.append('&'); - return result; - } - - private static StringBuilder addParam(final Object key, Object value, - final StringBuilder result) throws HttpRequestException { - return addParam(key, value, result, CHARSET_UTF8); - } - - private static StringBuilder addParam(final Object key, Object value, - final StringBuilder result, String charset) throws HttpRequestException { - if (value != null && value.getClass().isArray()) - value = arrayToList(value); - - try { - if (value instanceof Iterable) { - Iterator iterator = ((Iterable) value).iterator(); - while (iterator.hasNext()) { - result.append(URLEncoder.encode(key.toString(), charset)); - result.append("[]="); - Object element = iterator.next(); - if (element != null) - result.append(URLEncoder.encode(element.toString(), charset)); - if (iterator.hasNext()) - result.append("&"); - } - } else { - result.append(URLEncoder.encode(key.toString(), charset)); - result.append("="); - if (value != null) - result.append(URLEncoder.encode(value.toString(), charset)); - } - } catch (UnsupportedEncodingException e) { - throw new HttpRequestException(e); - } - - return result; - } - - /** - * Creates {@link HttpURLConnection HTTP connections} for - * {@link URL urls}. - */ - public interface ConnectionFactory { - /** - * Open an {@link HttpURLConnection} for the specified {@link URL}. - * - * @throws IOException - */ - HttpURLConnection create(URL url) throws IOException; - - /** - * Open an {@link HttpURLConnection} for the specified {@link URL} - * and {@link Proxy}. - * - * @throws IOException - */ - HttpURLConnection create(URL url, Proxy proxy) throws IOException; - - /** - * A {@link ConnectionFactory} which uses the built-in - * {@link URL#openConnection()} - */ - ConnectionFactory DEFAULT = new OkConnectionFactory(); - } - - private static ConnectionFactory CONNECTION_FACTORY = ConnectionFactory.DEFAULT; - - /** - * Specify the {@link ConnectionFactory} used to create new requests. - */ - public static void setConnectionFactory(final ConnectionFactory connectionFactory) { - if (connectionFactory == null) - CONNECTION_FACTORY = ConnectionFactory.DEFAULT; - else - CONNECTION_FACTORY = connectionFactory; - } - - - /** - * Add a certificate to test against when using ssl pinning. - * - * @param ca - * The Certificate to add - * @throws GeneralSecurityException - * @throws IOException - */ - public static void addCert(Certificate ca) throws GeneralSecurityException, IOException { - if (PINNED_CERTS == null) { - PINNED_CERTS = new ArrayList(); - } - - PINNED_CERTS.add(ca); - - if (CURRENT_CERT_MODE == CERT_MODE_PINNED) { - SOCKET_FACTORY = createSocketFactory(getPinnedTrustManagers()); - } - } - - /** - * Add a certificate to test against when using ssl pinning. - * - * @param in - * An InputStream to read a certificate from - * @throws GeneralSecurityException - * @throws IOException - */ - public static void addCert(InputStream in) throws GeneralSecurityException, IOException { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Certificate ca; - try { - ca = cf.generateCertificate(in); - addCert(ca); - } finally { - in.close(); - } - } - - /** - * Clear certs which were added to test against when using ssl pinning. - */ - public static void clearCerts() { - PINNED_CERTS = null; - } - - /** - * Callback interface for reporting upload progress for a request. - */ - public interface UploadProgress { - /** - * Callback invoked as data is uploaded by the request. - * - * @param uploaded The number of bytes already uploaded - * @param total The total number of bytes that will be uploaded or -1 if - * the length is unknown. - */ - void onUpload(long uploaded, long total); - - UploadProgress DEFAULT = new UploadProgress() { - public void onUpload(long uploaded, long total) { - } - }; - } - - /** - *

- * Encodes and decodes to and from Base64 notation. - *

- *

- * I am placing this code in the Public Domain. Do with it as you will. This - * software comes with no guarantees or warranties but with plenty of - * well-wishing instead! Please visit http://iharder.net/base64 periodically - * to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.7 - */ - public static class Base64 { - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** Preferred encoding. */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - /** The 64 valid Base64 values. */ - private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', - (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', - (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', - (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', - (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', - (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', - (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', - (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', - (byte) '+', (byte) '/' }; - - /** Defeats instantiation. */ - private Base64() { - } - - /** - *

- * Encodes up to three bytes of the array source and writes the - * resulting four Base64 bytes to destination. The source and - * destination arrays can be manipulated anywhere along their length by - * specifying srcOffset and destOffset. This method - * does not check to make sure your arrays are large enough to accomodate - * srcOffset + 3 for the source array or - * destOffset + 4 for the destination array. The - * actual number of significant bytes in your array is given by - * numSigBytes. - *

- *

- * This is the lowest level of the encoding methods with all possible - * parameters. - *

- * - * @param source - * the array to convert - * @param srcOffset - * the index where conversion begins - * @param numSigBytes - * the number of significant bytes in your array - * @param destination - * the array to hold the conversion - * @param destOffset - * the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, - int numSigBytes, byte[] destination, int destOffset) { - - byte[] ALPHABET = _STANDARD_ALPHABET; - - int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; - return destination; - - case 2: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - case 1: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - default: - return destination; - } - } - - /** - * Encode string as a byte array in Base64 annotation. - * - * @param string - * @return The Base64-encoded data as a string - */ - public static String encode(String string) { - byte[] bytes; - try { - bytes = string.getBytes(PREFERRED_ENCODING); - } catch (UnsupportedEncodingException e) { - bytes = string.getBytes(); - } - return encodeBytes(bytes); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source - * The data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException - * if source array is null - * @throws IllegalArgumentException - * if source array, offset, or length are invalid - * @since 2.0 - */ - public static String encodeBytes(byte[] source) { - return encodeBytes(source, 0, source.length); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source - * The data to convert - * @param off - * Offset in array where conversion should begin - * @param len - * Length of data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException - * if source array is null - * @throws IllegalArgumentException - * if source array, offset, or length are invalid - * @since 2.0 - */ - public static String encodeBytes(byte[] source, int off, int len) { - byte[] encoded = encodeBytesToBytes(source, off, len); - try { - return new String(encoded, PREFERRED_ENCODING); - } catch (UnsupportedEncodingException uue) { - return new String(encoded); - } - } - - /** - * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte - * array instead of instantiating a String. This is more efficient if you're - * working with I/O streams and have large data sets to encode. - * - * - * @param source - * The data to convert - * @param off - * Offset in array where conversion should begin - * @param len - * Length of data to convert - * @return The Base64-encoded data as a String if there is an error - * @throws NullPointerException - * if source array is null - * @throws IllegalArgumentException - * if source array, offset, or length are invalid - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes(byte[] source, int off, int len) { - - if (source == null) - throw new NullPointerException("Cannot serialize a null array."); - - if (off < 0) - throw new IllegalArgumentException("Cannot have negative offset: " - + off); - - if (len < 0) - throw new IllegalArgumentException("Cannot have length offset: " + len); - - if (off + len > source.length) - throw new IllegalArgumentException( - String - .format( - "Cannot have offset of %d and length of %d with array of length %d", - off, len, source.length)); - - // Bytes needed for actual encoding - int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); - - byte[] outBuff = new byte[encLen]; - - int d = 0; - int e = 0; - int len2 = len - 2; - for (; d < len2; d += 3, e += 4) - encode3to4(source, d + off, 3, outBuff, e); - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e); - e += 4; - } - - if (e <= outBuff.length - 1) { - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff, 0, finalOut, 0, e); - return finalOut; - } else - return outBuff; - } - } - - /** - * HTTP request exception whose cause is always an {@link IOException} - */ - public static class HttpRequestException extends RuntimeException { - - private static final long serialVersionUID = -1170466989781746231L; - - /** - * Create a new HttpRequestException with the given cause - * - * @param cause - */ - public HttpRequestException(final IOException cause) { - super(cause); - } - - /** - * Get {@link IOException} that triggered this request exception - * - * @return {@link IOException} cause - */ - @Override - public IOException getCause() { - return (IOException) super.getCause(); - } - } - - /** - * Operation that handles executing a callback once complete and handling - * nested exceptions - * - * @param - */ - protected static abstract class Operation implements Callable { - - /** - * Run operation - * - * @return result - * @throws HttpRequestException - * @throws IOException - */ - protected abstract V run() throws HttpRequestException, IOException; - - /** - * Operation complete callback - * - * @throws IOException - */ - protected abstract void done() throws IOException; - - public V call() throws HttpRequestException { - boolean thrown = false; - try { - return run(); - } catch (HttpRequestException e) { - thrown = true; - throw e; - } catch (IOException e) { - thrown = true; - throw new HttpRequestException(e); - } finally { - try { - done(); - } catch (IOException e) { - if (!thrown) - throw new HttpRequestException(e); - } - } - } - } - - /** - * Class that ensures a {@link Closeable} gets closed with proper exception - * handling. - * - * @param - */ - protected static abstract class CloseOperation extends Operation { - - private final Closeable closeable; - - private final boolean ignoreCloseExceptions; - - /** - * Create closer for operation - * - * @param closeable - * @param ignoreCloseExceptions - */ - protected CloseOperation(final Closeable closeable, - final boolean ignoreCloseExceptions) { - this.closeable = closeable; - this.ignoreCloseExceptions = ignoreCloseExceptions; - } - - @Override - protected void done() throws IOException { - if (closeable instanceof Flushable) - ((Flushable) closeable).flush(); - if (ignoreCloseExceptions) - try { - closeable.close(); - } catch (IOException e) { - // Ignored - } - else - closeable.close(); - } - } - - /** - * Class that and ensures a {@link Flushable} gets flushed with proper - * exception handling. - * - * @param - */ - protected static abstract class FlushOperation extends Operation { - - private final Flushable flushable; - - /** - * Create flush operation - * - * @param flushable - */ - protected FlushOperation(final Flushable flushable) { - this.flushable = flushable; - } - - @Override - protected void done() throws IOException { - flushable.flush(); - } - } - - /** - * Request output stream - */ - public static class RequestOutputStream extends BufferedOutputStream { - - private final CharsetEncoder encoder; - - /** - * Create request output stream - * - * @param stream - * @param charset - * @param bufferSize - */ - public RequestOutputStream(final OutputStream stream, final String charset, - final int bufferSize) { - super(stream, bufferSize); - - encoder = Charset.forName(getValidCharset(charset)).newEncoder(); - } - - /** - * Write string to stream - * - * @param value - * @return this stream - * @throws IOException - */ - public RequestOutputStream write(final String value) throws IOException { - final ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value)); - - super.write(bytes.array(), 0, bytes.limit()); - - return this; - } - } - - /** - * Represents array of any type as list of objects so we can easily iterate over it - * @param array of elements - * @return list with the same elements - */ - private static List arrayToList(final Object array) { - if (array instanceof Object[]) - return Arrays.asList((Object[]) array); - - List result = new ArrayList(); - // Arrays of the primitive types can't be cast to array of Object, so this: - if (array instanceof int[]) - for (int value : (int[]) array) result.add(value); - else if (array instanceof boolean[]) - for (boolean value : (boolean[]) array) result.add(value); - else if (array instanceof long[]) - for (long value : (long[]) array) result.add(value); - else if (array instanceof float[]) - for (float value : (float[]) array) result.add(value); - else if (array instanceof double[]) - for (double value : (double[]) array) result.add(value); - else if (array instanceof short[]) - for (short value : (short[]) array) result.add(value); - else if (array instanceof byte[]) - for (byte value : (byte[]) array) result.add(value); - else if (array instanceof char[]) - for (char value : (char[]) array) result.add(value); - return result; - } - - /** - * Encode the given URL as an ASCII {@link String} - *

- * This method ensures the path and query segments of the URL are properly - * encoded such as ' ' characters being encoded to '%20' or any UTF-8 - * characters that are non-ASCII. No encoding of URLs is done by default by - * the {@link HttpRequest} constructors and so if URL encoding is needed this - * method should be called before calling the {@link HttpRequest} constructor. - * - * @param url - * @return encoded URL - * @throws HttpRequestException - */ - public static String encode(final CharSequence url) - throws HttpRequestException { - URL parsed; - try { - parsed = new URL(url.toString()); - } catch (IOException e) { - throw new HttpRequestException(e); - } - - String host = parsed.getHost(); - int port = parsed.getPort(); - if (port != -1) - host = host + ':' + Integer.toString(port); - - try { - String encoded = new URI(parsed.getProtocol(), host, parsed.getPath(), - parsed.getQuery(), null).toASCIIString(); - int paramsStart = encoded.indexOf('?'); - if (paramsStart > 0 && paramsStart + 1 < encoded.length()) - encoded = encoded.substring(0, paramsStart + 1) - + encoded.substring(paramsStart + 1).replace("+", "%2B"); - return encoded; - } catch (URISyntaxException e) { - IOException io = new IOException("Parsing URI failed"); - io.initCause(e); - throw new HttpRequestException(io); - } - } - - /** - * Append given map as query parameters to the base URL - *

- * Each map entry's key will be a parameter name and the value's - * {@link Object#toString()} will be the parameter value. - * - * @param url - * @param params - * @return URL with appended query params - */ - public static String append(final CharSequence url, final Map params) { - final String baseUrl = url.toString(); - if (params == null || params.isEmpty()) - return baseUrl; - - final StringBuilder result = new StringBuilder(baseUrl); - - addPathSeparator(baseUrl, result); - addParamPrefix(baseUrl, result); - - Entry entry; - Iterator iterator = params.entrySet().iterator(); - entry = (Entry) iterator.next(); - addParam(entry.getKey().toString(), entry.getValue(), result); - - while (iterator.hasNext()) { - result.append('&'); - entry = (Entry) iterator.next(); - addParam(entry.getKey().toString(), entry.getValue(), result); - } - - return result.toString(); - } - - /** - * Append given name/value pairs as query parameters to the base URL - *

- * The params argument is interpreted as a sequence of name/value pairs so the - * given number of params must be divisible by 2. - * - * @param url - * @param params - * name/value pairs - * @return URL with appended query params - */ - public static String append(final CharSequence url, final Object... params) { - final String baseUrl = url.toString(); - if (params == null || params.length == 0) - return baseUrl; - - if (params.length % 2 != 0) - throw new IllegalArgumentException( - "Must specify an even number of parameter names/values"); - - final StringBuilder result = new StringBuilder(baseUrl); - - addPathSeparator(baseUrl, result); - addParamPrefix(baseUrl, result); - - addParam(params[0], params[1], result); - - for (int i = 2; i < params.length; i += 2) { - result.append('&'); - addParam(params[i], params[i + 1], result); - } - - return result.toString(); - } - - /** - * Start a 'GET' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest get(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_GET); - } - - /** - * Start a 'GET' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest get(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_GET); - } - - /** - * Start a 'GET' request to the given URL along with the query params - * - * @param baseUrl - * @param params - * The query parameters to include as part of the baseUrl - * @param encode - * true to encode the full URL - * - * @see #append(CharSequence, Map) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest get(final CharSequence baseUrl, - final Map params, final boolean encode) { - String url = append(baseUrl, params); - return get(encode ? encode(url) : url); - } - - /** - * Start a 'GET' request to the given URL along with the query params - * - * @param baseUrl - * @param encode - * true to encode the full URL - * @param params - * the name/value query parameter pairs to include as part of the - * baseUrl - * - * @see #append(CharSequence, Object...) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest get(final CharSequence baseUrl, - final boolean encode, final Object... params) { - String url = append(baseUrl, params); - return get(encode ? encode(url) : url); - } - - /** - * Start a 'POST' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest post(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_POST); - } - - /** - * Start a 'POST' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest post(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_POST); - } - - /** - * Start a 'POST' request to the given URL along with the query params - * - * @param baseUrl - * @param params - * the query parameters to include as part of the baseUrl - * @param encode - * true to encode the full URL - * - * @see #append(CharSequence, Map) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest post(final CharSequence baseUrl, - final Map params, final boolean encode) { - String url = append(baseUrl, params); - return post(encode ? encode(url) : url); - } - - /** - * Start a 'POST' request to the given URL along with the query params - * - * @param baseUrl - * @param encode - * true to encode the full URL - * @param params - * the name/value query parameter pairs to include as part of the - * baseUrl - * - * @see #append(CharSequence, Object...) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest post(final CharSequence baseUrl, - final boolean encode, final Object... params) { - String url = append(baseUrl, params); - return post(encode ? encode(url) : url); - } - -/** - * Start a 'PATCH' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest patch(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_PATCH); - } - - /** - * Start a 'PATCH' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest patch(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_PATCH); - } - - /** - * Start a 'PATCH' request to the given URL along with the query params - * - * @param baseUrl - * @param params - * the query parameters to include as part of the baseUrl - * @param encode - * true to encode the full URL - * - * @see #append(CharSequence, Map) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest patch(final CharSequence baseUrl, - final Map params, final boolean encode) { - String url = append(baseUrl, params); - return patch(encode ? encode(url) : url); - } - - /** - * Start a 'PATCH' request to the given URL along with the query params - * - * @param baseUrl - * @param encode - * true to encode the full URL - * @param params - * the name/value query parameter pairs to include as part of the - * baseUrl - * - * @see #append(CharSequence, Object...) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest patch(final CharSequence baseUrl, - final boolean encode, final Object... params) { - String url = append(baseUrl, params); - return patch(encode ? encode(url) : url); - } - - /** - * Start a 'PUT' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest put(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_PUT); - } - - /** - * Start a 'PUT' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest put(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_PUT); - } - - /** - * Start a 'PUT' request to the given URL along with the query params - * - * @param baseUrl - * @param params - * the query parameters to include as part of the baseUrl - * @param encode - * true to encode the full URL - * - * @see #append(CharSequence, Map) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest put(final CharSequence baseUrl, - final Map params, final boolean encode) { - String url = append(baseUrl, params); - return put(encode ? encode(url) : url); - } - - /** - * Start a 'PUT' request to the given URL along with the query params - * - * @param baseUrl - * @param encode - * true to encode the full URL - * @param params - * the name/value query parameter pairs to include as part of the - * baseUrl - * - * @see #append(CharSequence, Object...) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest put(final CharSequence baseUrl, - final boolean encode, final Object... params) { - String url = append(baseUrl, params); - return put(encode ? encode(url) : url); - } - - /** - * Start a 'DELETE' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest delete(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_DELETE); - } - - /** - * Start a 'DELETE' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest delete(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_DELETE); - } - - /** - * Start a 'DELETE' request to the given URL along with the query params - * - * @param baseUrl - * @param params - * The query parameters to include as part of the baseUrl - * @param encode - * true to encode the full URL - * - * @see #append(CharSequence, Map) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest delete(final CharSequence baseUrl, - final Map params, final boolean encode) { - String url = append(baseUrl, params); - return delete(encode ? encode(url) : url); - } - - /** - * Start a 'DELETE' request to the given URL along with the query params - * - * @param baseUrl - * @param encode - * true to encode the full URL - * @param params - * the name/value query parameter pairs to include as part of the - * baseUrl - * - * @see #append(CharSequence, Object...) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest delete(final CharSequence baseUrl, - final boolean encode, final Object... params) { - String url = append(baseUrl, params); - return delete(encode ? encode(url) : url); - } - - /** - * Start a 'HEAD' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest head(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_HEAD); - } - - /** - * Start a 'HEAD' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest head(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_HEAD); - } - - /** - * Start a 'HEAD' request to the given URL along with the query params - * - * @param baseUrl - * @param params - * The query parameters to include as part of the baseUrl - * @param encode - * true to encode the full URL - * - * @see #append(CharSequence, Map) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest head(final CharSequence baseUrl, - final Map params, final boolean encode) { - String url = append(baseUrl, params); - return head(encode ? encode(url) : url); - } - - /** - * Start a 'GET' request to the given URL along with the query params - * - * @param baseUrl - * @param encode - * true to encode the full URL - * @param params - * the name/value query parameter pairs to include as part of the - * baseUrl - * - * @see #append(CharSequence, Object...) - * @see #encode(CharSequence) - * - * @return request - */ - public static HttpRequest head(final CharSequence baseUrl, - final boolean encode, final Object... params) { - String url = append(baseUrl, params); - return head(encode ? encode(url) : url); - } - - /** - * Start an 'OPTIONS' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest options(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_OPTIONS); - } - - /** - * Start an 'OPTIONS' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest options(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_OPTIONS); - } - - /** - * Start a 'TRACE' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest trace(final CharSequence url) - throws HttpRequestException { - return new HttpRequest(url, METHOD_TRACE); - } - - /** - * Start a 'TRACE' request to the given URL - * - * @param url - * @return request - * @throws HttpRequestException - */ - public static HttpRequest trace(final URL url) throws HttpRequestException { - return new HttpRequest(url, METHOD_TRACE); - } - - /** - * Set the 'http.keepAlive' property to the given value. - *

- * This setting will apply to all requests. - * - * @param keepAlive - */ - public static void keepAlive(final boolean keepAlive) { - setProperty("http.keepAlive", Boolean.toString(keepAlive)); - } - - /** - * Set the 'http.maxConnections' property to the given value. - *

- * This setting will apply to all requests. - * - * @param maxConnections - */ - public static void maxConnections(final int maxConnections) { - setProperty("http.maxConnections", Integer.toString(maxConnections)); - } - - /** - * Set the 'http.proxyHost' and 'https.proxyHost' properties to the given host - * value. - *

- * This setting will apply to all requests. - * - * @param host - */ - public static void proxyHost(final String host) { - setProperty("http.proxyHost", host); - setProperty("https.proxyHost", host); - } - - /** - * Set the 'http.proxyPort' and 'https.proxyPort' properties to the given port - * number. - *

- * This setting will apply to all requests. - * - * @param port - */ - public static void proxyPort(final int port) { - final String portValue = Integer.toString(port); - setProperty("http.proxyPort", portValue); - setProperty("https.proxyPort", portValue); - } - - /** - * Set the 'http.nonProxyHosts' property to the given host values. - *

- * Hosts will be separated by a '|' character. - *

- * This setting will apply to all requests. - * - * @param hosts - */ - public static void nonProxyHosts(final String... hosts) { - if (hosts != null && hosts.length > 0) { - StringBuilder separated = new StringBuilder(); - int last = hosts.length - 1; - for (int i = 0; i < last; i++) - separated.append(hosts[i]).append('|'); - separated.append(hosts[last]); - setProperty("http.nonProxyHosts", separated.toString()); - } else - setProperty("http.nonProxyHosts", null); - } - - /** - * Set property to given value. - *

- * Specifying a null value will cause the property to be cleared - * - * @param name - * @param value - * @return previous value - */ - private static String setProperty(final String name, final String value) { - final PrivilegedAction action; - if (value != null) - action = new PrivilegedAction() { - - public String run() { - return System.setProperty(name, value); - } - }; - else - action = new PrivilegedAction() { - - public String run() { - return System.clearProperty(name); - } - }; - return AccessController.doPrivileged(action); - } - - private HttpURLConnection connection = null; - - private final URL url; - - private final String requestMethod; - - private RequestOutputStream output; - - private boolean multipart; - - private boolean form; - - private boolean ignoreCloseExceptions = true; - - private boolean uncompress = false; - - private int bufferSize = 8192; - - private long totalSize = -1; - - private long totalWritten = 0; - - private String httpProxyHost; - - private int httpProxyPort; - - private UploadProgress progress = UploadProgress.DEFAULT; - - /** - * Create HTTP connection wrapper - * - * @param url Remote resource URL. - * @param method HTTP request method (e.g., "GET", "POST"). - * @throws HttpRequestException - */ - public HttpRequest(final CharSequence url, final String method) - throws HttpRequestException { - try { - this.url = new URL(url.toString()); - } catch (MalformedURLException e) { - throw new HttpRequestException(e); - } - this.requestMethod = method; - this.setupSecurity(); - } - - /** - * Create HTTP connection wrapper - * - * @param url Remote resource URL. - * @param method HTTP request method (e.g., "GET", "POST"). - * @throws HttpRequestException - */ - public HttpRequest(final URL url, final String method) - throws HttpRequestException { - this.url = url; - this.requestMethod = method; - this.setupSecurity(); - } - - private void setupSecurity() { - final HttpURLConnection connection = getConnection(); - - if (!(connection instanceof HttpsURLConnection)) { - return; - } - - if (SOCKET_FACTORY != null) { - ((HttpsURLConnection) connection).setSSLSocketFactory(SOCKET_FACTORY); - } - - if (HOSTNAME_VERIFIER != null) { - ((HttpsURLConnection) connection).setHostnameVerifier(HOSTNAME_VERIFIER); - } - } - - private Proxy createProxy() { - return new Proxy(HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort)); - } - - private HttpURLConnection createConnection() { - try { - final HttpURLConnection connection; - if (httpProxyHost != null) - connection = CONNECTION_FACTORY.create(url, createProxy()); - else - connection = CONNECTION_FACTORY.create(url); - connection.setRequestMethod(requestMethod); - return connection; - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - @Override - public String toString() { - return method() + ' ' + url(); - } - - /** - * Get underlying connection - * - * @return connection - */ - public HttpURLConnection getConnection() { - if (connection == null) - connection = createConnection(); - return connection; - } - - /** - * Set whether or not to ignore exceptions that occur from calling - * {@link Closeable#close()} - *

- * The default value of this setting is true - * - * @param ignore - * @return this request - */ - public HttpRequest ignoreCloseExceptions(final boolean ignore) { - ignoreCloseExceptions = ignore; - return this; - } - - /** - * Get whether or not exceptions thrown by {@link Closeable#close()} are - * ignored - * - * @return true if ignoring, false if throwing - */ - public boolean ignoreCloseExceptions() { - return ignoreCloseExceptions; - } - - /** - * Get the status code of the response - * - * @return the response code - * @throws HttpRequestException - */ - public int code() throws HttpRequestException { - try { - closeOutput(); - return getConnection().getResponseCode(); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Set the value of the given {@link AtomicInteger} to the status code of the - * response - * - * @param output - * @return this request - * @throws HttpRequestException - */ - public HttpRequest code(final AtomicInteger output) - throws HttpRequestException { - output.set(code()); - return this; - } - - /** - * Is the response code a 200 OK? - * - * @return true if 200, false otherwise - * @throws HttpRequestException - */ - public boolean ok() throws HttpRequestException { - return HTTP_OK == code(); - } - - /** - * Is the response code a 201 Created? - * - * @return true if 201, false otherwise - * @throws HttpRequestException - */ - public boolean created() throws HttpRequestException { - return HTTP_CREATED == code(); - } - - /** - * Is the response code a 204 No Content? - * - * @return true if 204, false otherwise - * @throws HttpRequestException - */ - public boolean noContent() throws HttpRequestException { - return HTTP_NO_CONTENT == code(); - } - - /** - * Is the response code a 500 Internal Server Error? - * - * @return true if 500, false otherwise - * @throws HttpRequestException - */ - public boolean serverError() throws HttpRequestException { - return HTTP_INTERNAL_ERROR == code(); - } - - /** - * Is the response code a 400 Bad Request? - * - * @return true if 400, false otherwise - * @throws HttpRequestException - */ - public boolean badRequest() throws HttpRequestException { - return HTTP_BAD_REQUEST == code(); - } - - /** - * Is the response code a 404 Not Found? - * - * @return true if 404, false otherwise - * @throws HttpRequestException - */ - public boolean notFound() throws HttpRequestException { - return HTTP_NOT_FOUND == code(); - } - - /** - * Is the response code a 304 Not Modified? - * - * @return true if 304, false otherwise - * @throws HttpRequestException - */ - public boolean notModified() throws HttpRequestException { - return HTTP_NOT_MODIFIED == code(); - } - - /** - * Get status message of the response - * - * @return message - * @throws HttpRequestException - */ - public String message() throws HttpRequestException { - try { - closeOutput(); - return getConnection().getResponseMessage(); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Disconnect the connection - * - * @return this request - */ - public HttpRequest disconnect() { - getConnection().disconnect(); - return this; - } - - /** - * Set chunked streaming mode to the given size - * - * @param size - * @return this request - */ - public HttpRequest chunk(final int size) { - getConnection().setChunkedStreamingMode(size); - return this; - } - - /** - * Set the size used when buffering and copying between streams - *

- * This size is also used for send and receive buffers created for both char - * and byte arrays - *

- * The default buffer size is 8,192 bytes - * - * @param size - * @return this request - */ - public HttpRequest bufferSize(final int size) { - if (size < 1) - throw new IllegalArgumentException("Size must be greater than zero"); - bufferSize = size; - return this; - } - - /** - * Get the configured buffer size - *

- * The default buffer size is 8,192 bytes - * - * @return buffer size - */ - public int bufferSize() { - return bufferSize; - } - - /** - * Set whether or not the response body should be automatically uncompressed - * when read from. - *

- * This will only affect requests that have the 'Content-Encoding' response - * header set to 'gzip'. - *

- * This causes all receive methods to use a {@link GZIPInputStream} when - * applicable so that higher level streams and readers can read the data - * uncompressed. - *

- * Setting this option does not cause any request headers to be set - * automatically so {@link #acceptGzipEncoding()} should be used in - * conjunction with this setting to tell the server to gzip the response. - * - * @param uncompress - * @return this request - */ - public HttpRequest uncompress(final boolean uncompress) { - this.uncompress = uncompress; - return this; - } - - /** - * Create byte array output stream - * - * @return stream - */ - protected ByteArrayOutputStream byteStream() { - final int size = contentLength(); - if (size > 0) - return new ByteArrayOutputStream(size); - else - return new ByteArrayOutputStream(); - } - - /** - * Get response as {@link String} in given character set - *

- * This will fall back to using the UTF-8 character set if the given charset - * is null - * - * @param charset - * @return string - * @throws HttpRequestException - */ - public String body(final String charset) throws HttpRequestException { - final ByteArrayOutputStream output = byteStream(); - try { - copy(buffer(), output); - return output.toString(getValidCharset(charset)); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Get response as {@link String} using character set returned from - * {@link #charset()} - * - * @return string - * @throws HttpRequestException - */ - public String body() throws HttpRequestException { - return body(charset()); - } - - /** - * Get the response body as ByteBuffer and set it as the value of the - * given reference. - * - * @param output - * @return this request - * @throws HttpRequestException - */ - public HttpRequest body(final AtomicReference output) throws HttpRequestException { - final ByteArrayOutputStream outputStream = byteStream(); - - try { - copy(buffer(), outputStream); - output.set(ByteBuffer.wrap(outputStream.toByteArray())); - - return this; - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Get the response body as a {@link String} and set it as the value of the - * given reference. - * - * @param output - * @param charset - * @return this request - * @throws HttpRequestException - */ - public HttpRequest body(final AtomicReference output, final String charset) throws HttpRequestException { - output.set(body(charset)); - return this; - } - - /** - * Is the response body empty? - * - * @return true if the Content-Length response header is 0, false otherwise - * @throws HttpRequestException - */ - public boolean isBodyEmpty() throws HttpRequestException { - return contentLength() == 0; - } - - /** - * Get response as byte array - * - * @return byte array - * @throws HttpRequestException - */ - public byte[] bytes() throws HttpRequestException { - final ByteArrayOutputStream output = byteStream(); - try { - copy(buffer(), output); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return output.toByteArray(); - } - - /** - * Get response in a buffered stream - * - * @see #bufferSize(int) - * @return stream - * @throws HttpRequestException - */ - public BufferedInputStream buffer() throws HttpRequestException { - return new BufferedInputStream(stream(), bufferSize); - } - - /** - * Get stream to response body - * - * @return stream - * @throws HttpRequestException - */ - public InputStream stream() throws HttpRequestException { - InputStream stream; - if (code() < HTTP_BAD_REQUEST) - try { - stream = getConnection().getInputStream(); - } catch (IOException e) { - throw new HttpRequestException(e); - } - else { - stream = getConnection().getErrorStream(); - if (stream == null) - try { - stream = getConnection().getInputStream(); - } catch (IOException e) { - if (contentLength() > 0) - throw new HttpRequestException(e); - else - stream = new ByteArrayInputStream(new byte[0]); - } - } - - if (!uncompress || !ENCODING_GZIP.equals(contentEncoding())) - return stream; - else - try { - return new GZIPInputStream(stream); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Get reader to response body using given character set. - *

- * This will fall back to using the UTF-8 character set if the given charset - * is null - * - * @param charset - * @return reader - * @throws HttpRequestException - */ - public InputStreamReader reader(final String charset) - throws HttpRequestException { - try { - return new InputStreamReader(stream(), getValidCharset(charset)); - } catch (UnsupportedEncodingException e) { - throw new HttpRequestException(e); - } - } - - /** - * Get reader to response body using the character set returned from - * {@link #charset()} - * - * @return reader - * @throws HttpRequestException - */ - public InputStreamReader reader() throws HttpRequestException { - return reader(charset()); - } - - /** - * Get buffered reader to response body using the given character set r and - * the configured buffer size - * - * - * @see #bufferSize(int) - * @param charset - * @return reader - * @throws HttpRequestException - */ - public BufferedReader bufferedReader(final String charset) - throws HttpRequestException { - return new BufferedReader(reader(charset), bufferSize); - } - - /** - * Get buffered reader to response body using the character set returned from - * {@link #charset()} and the configured buffer size - * - * @see #bufferSize(int) - * @return reader - * @throws HttpRequestException - */ - public BufferedReader bufferedReader() throws HttpRequestException { - return bufferedReader(charset()); - } - - /** - * Stream response body to file - * - * @param file - * @return this request - * @throws HttpRequestException - */ - public HttpRequest receive(final File file) throws HttpRequestException { - final OutputStream output; - try { - output = new BufferedOutputStream(new FileOutputStream(file), bufferSize); - } catch (FileNotFoundException e) { - throw new HttpRequestException(e); - } - return new CloseOperation(output, ignoreCloseExceptions) { - - @Override - protected HttpRequest run() throws HttpRequestException, IOException { - return receive(output); - } - }.call(); - } - - /** - * Stream response to given output stream - * - * @param output - * @return this request - * @throws HttpRequestException - */ - public HttpRequest receive(final OutputStream output) - throws HttpRequestException { - try { - return copy(buffer(), output); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Stream response to given print stream - * - * @param output - * @return this request - * @throws HttpRequestException - */ - public HttpRequest receive(final PrintStream output) - throws HttpRequestException { - return receive((OutputStream) output); - } - - /** - * Receive response into the given appendable - * - * @param appendable - * @return this request - * @throws HttpRequestException - */ - public HttpRequest receive(final Appendable appendable) - throws HttpRequestException { - final BufferedReader reader = bufferedReader(); - return new CloseOperation(reader, ignoreCloseExceptions) { - - @Override - public HttpRequest run() throws IOException { - final CharBuffer buffer = CharBuffer.allocate(bufferSize); - int read; - while ((read = reader.read(buffer)) != -1) { - buffer.rewind(); - appendable.append(buffer, 0, read); - buffer.rewind(); - } - return HttpRequest.this; - } - }.call(); - } - - /** - * Receive response into the given writer - * - * @param writer - * @return this request - * @throws HttpRequestException - */ - public HttpRequest receive(final Writer writer) throws HttpRequestException { - final BufferedReader reader = bufferedReader(); - return new CloseOperation(reader, ignoreCloseExceptions) { - - @Override - public HttpRequest run() throws IOException { - return copy(reader, writer); - } - }.call(); - } - - /** - * Set read timeout on connection to given value - * - * @param timeout - * @return this request - */ - public HttpRequest readTimeout(final int timeout) { - getConnection().setReadTimeout(timeout); - return this; - } - - /** - * Set connect timeout on connection to given value - * - * @param timeout - * @return this request - */ - public HttpRequest connectTimeout(final int timeout) { - getConnection().setConnectTimeout(timeout); - return this; - } - - /** - * Set header name to given value - * - * @param name - * @param value - * @return this request - */ - public HttpRequest header(final String name, final String value) { - getConnection().setRequestProperty(name, value); - return this; - } - - /** - * Set header name to given value - * - * @param name - * @param value - * @return this request - */ - public HttpRequest header(final String name, final Number value) { - return header(name, value != null ? value.toString() : null); - } - - /** - * Set all headers found in given map where the keys are the header names and - * the values are the header values - * - * @param headers - * @return this request - */ - public HttpRequest headers(final Map headers) { - if (!headers.isEmpty()) - for (Entry header : headers.entrySet()) - header(header); - return this; - } - - /** - * Set header to have given entry's key as the name and value as the value - * - * @param header - * @return this request - */ - public HttpRequest header(final Entry header) { - return header(header.getKey(), header.getValue()); - } - - /** - * Get a response header - * - * @param name - * @return response header - * @throws HttpRequestException - */ - public String header(final String name) throws HttpRequestException { - closeOutputQuietly(); - return getConnection().getHeaderField(name); - } - - /** - * Get all the response headers - * - * @return map of response header names to their value(s) - * @throws HttpRequestException - */ - public Map> headers() throws HttpRequestException { - closeOutputQuietly(); - return getConnection().getHeaderFields(); - } - - /** - * Get a date header from the response falling back to returning -1 if the - * header is missing or parsing fails - * - * @param name - * @return date, -1 on failures - * @throws HttpRequestException - */ - public long dateHeader(final String name) throws HttpRequestException { - return dateHeader(name, -1L); - } - - /** - * Get a date header from the response falling back to returning the given - * default value if the header is missing or parsing fails - * - * @param name - * @param defaultValue - * @return date, default value on failures - * @throws HttpRequestException - */ - public long dateHeader(final String name, final long defaultValue) - throws HttpRequestException { - closeOutputQuietly(); - return getConnection().getHeaderFieldDate(name, defaultValue); - } - - /** - * Get an integer header from the response falling back to returning -1 if the - * header is missing or parsing fails - * - * @param name - * @return header value as an integer, -1 when missing or parsing fails - * @throws HttpRequestException - */ - public int intHeader(final String name) throws HttpRequestException { - return intHeader(name, -1); - } - - /** - * Get an integer header value from the response falling back to the given - * default value if the header is missing or if parsing fails - * - * @param name - * @param defaultValue - * @return header value as an integer, default value when missing or parsing - * fails - * @throws HttpRequestException - */ - public int intHeader(final String name, final int defaultValue) - throws HttpRequestException { - closeOutputQuietly(); - return getConnection().getHeaderFieldInt(name, defaultValue); - } - - /** - * Get all values of the given header from the response - * - * @param name - * @return non-null but possibly empty array of {@link String} header values - */ - public String[] headers(final String name) { - final Map> headers = headers(); - if (headers == null || headers.isEmpty()) - return EMPTY_STRINGS; - - final List values = headers.get(name); - if (values != null && !values.isEmpty()) - return values.toArray(new String[values.size()]); - else - return EMPTY_STRINGS; - } - - /** - * Get parameter with given name from header value in response - * - * @param headerName - * @param paramName - * @return parameter value or null if missing - */ - public String parameter(final String headerName, final String paramName) { - return getParam(header(headerName), paramName); - } - - /** - * Get all parameters from header value in response - *

- * This will be all key=value pairs after the first ';' that are separated by - * a ';' - * - * @param headerName - * @return non-null but possibly empty map of parameter headers - */ - public Map parameters(final String headerName) { - return getParams(header(headerName)); - } - - /** - * Get parameter values from header value - * - * @param header - * @return parameter value or null if none - */ - protected Map getParams(final String header) { - if (header == null || header.length() == 0) - return Collections.emptyMap(); - - final int headerLength = header.length(); - int start = header.indexOf(';') + 1; - if (start == 0 || start == headerLength) - return Collections.emptyMap(); - - int end = header.indexOf(';', start); - if (end == -1) - end = headerLength; - - Map params = new LinkedHashMap(); - while (start < end) { - int nameEnd = header.indexOf('=', start); - if (nameEnd != -1 && nameEnd < end) { - String name = header.substring(start, nameEnd).trim(); - if (name.length() > 0) { - String value = header.substring(nameEnd + 1, end).trim(); - int length = value.length(); - if (length != 0) - if (length > 2 && '"' == value.charAt(0) - && '"' == value.charAt(length - 1)) - params.put(name, value.substring(1, length - 1)); - else - params.put(name, value); - } - } - - start = end + 1; - end = header.indexOf(';', start); - if (end == -1) - end = headerLength; - } - - return params; - } - - /** - * Get parameter value from header value - * - * @param value - * @param paramName - * @return parameter value or null if none - */ - protected String getParam(final String value, final String paramName) { - if (value == null || value.length() == 0) - return null; - - final int length = value.length(); - int start = value.indexOf(';') + 1; - if (start == 0 || start == length) - return null; - - int end = value.indexOf(';', start); - if (end == -1) - end = length; - - while (start < end) { - int nameEnd = value.indexOf('=', start); - if (nameEnd != -1 && nameEnd < end - && paramName.equals(value.substring(start, nameEnd).trim())) { - String paramValue = value.substring(nameEnd + 1, end).trim(); - int valueLength = paramValue.length(); - if (valueLength != 0) - if (valueLength > 2 && '"' == paramValue.charAt(0) - && '"' == paramValue.charAt(valueLength - 1)) - return paramValue.substring(1, valueLength - 1); - else - return paramValue; - } - - start = end + 1; - end = value.indexOf(';', start); - if (end == -1) - end = length; - } - - return null; - } - - /** - * Get 'charset' parameter from 'Content-Type' response header - * - * @return charset or null if none - */ - public String charset() { - return parameter(HEADER_CONTENT_TYPE, PARAM_CHARSET); - } - - /** - * Set the 'User-Agent' header to given value - * - * @param userAgent - * @return this request - */ - public HttpRequest userAgent(final String userAgent) { - return header(HEADER_USER_AGENT, userAgent); - } - - /** - * Set the 'Referer' header to given value - * - * @param referer - * @return this request - */ - public HttpRequest referer(final String referer) { - return header(HEADER_REFERER, referer); - } - - /** - * Set value of {@link HttpURLConnection#setUseCaches(boolean)} - * - * @param useCaches - * @return this request - */ - public HttpRequest useCaches(final boolean useCaches) { - getConnection().setUseCaches(useCaches); - return this; - } - - /** - * Set the 'Accept-Encoding' header to given value - * - * @param acceptEncoding - * @return this request - */ - public HttpRequest acceptEncoding(final String acceptEncoding) { - return header(HEADER_ACCEPT_ENCODING, acceptEncoding); - } - - /** - * Set the 'Accept-Encoding' header to 'gzip' - * - * @see #uncompress(boolean) - * @return this request - */ - public HttpRequest acceptGzipEncoding() { - return acceptEncoding(ENCODING_GZIP); - } - - /** - * Set the 'Accept-Charset' header to given value - * - * @param acceptCharset - * @return this request - */ - public HttpRequest acceptCharset(final String acceptCharset) { - return header(HEADER_ACCEPT_CHARSET, acceptCharset); - } - - /** - * Set the 'Accept-Charset' header to given values - * - * @param acceptCharsets - * @return this request - */ - public HttpRequest acceptCharset(final String[] acceptCharsets) { - return header(HEADER_ACCEPT_CHARSET, TextUtils.join(", ", acceptCharsets)); - } - - /** - * Get the 'Content-Encoding' header from the response - * - * @return this request - */ - public String contentEncoding() { - return header(HEADER_CONTENT_ENCODING); - } - - /** - * Get the 'Server' header from the response - * - * @return server - */ - public String server() { - return header(HEADER_SERVER); - } - - /** - * Get the 'Date' header from the response - * - * @return date value, -1 on failures - */ - public long date() { - return dateHeader(HEADER_DATE); - } - - /** - * Get the 'Cache-Control' header from the response - * - * @return cache control - */ - public String cacheControl() { - return header(HEADER_CACHE_CONTROL); - } - - /** - * Get the 'ETag' header from the response - * - * @return entity tag - */ - public String eTag() { - return header(HEADER_ETAG); - } - - /** - * Get the 'Expires' header from the response - * - * @return expires value, -1 on failures - */ - public long expires() { - return dateHeader(HEADER_EXPIRES); - } - - /** - * Get the 'Last-Modified' header from the response - * - * @return last modified value, -1 on failures - */ - public long lastModified() { - return dateHeader(HEADER_LAST_MODIFIED); - } - - /** - * Get the 'Location' header from the response - * - * @return location - */ - public String location() { - return header(HEADER_LOCATION); - } - - /** - * Set the 'Authorization' header to given value - * - * @param authorization - * @return this request - */ - public HttpRequest authorization(final String authorization) { - return header(HEADER_AUTHORIZATION, authorization); - } - - /** - * Set the 'Proxy-Authorization' header to given value - * - * @param proxyAuthorization - * @return this request - */ - public HttpRequest proxyAuthorization(final String proxyAuthorization) { - return header(HEADER_PROXY_AUTHORIZATION, proxyAuthorization); - } - - /** - * Set the 'Authorization' header to given values in Basic authentication - * format - * - * @param name - * @param password - * @return this request - */ - public HttpRequest basic(final String name, final String password) { - return authorization("Basic " + Base64.encode(name + ':' + password)); - } - - /** - * Set the 'Proxy-Authorization' header to given values in Basic authentication - * format - * - * @param name - * @param password - * @return this request - */ - public HttpRequest proxyBasic(final String name, final String password) { - return proxyAuthorization("Basic " + Base64.encode(name + ':' + password)); - } - - /** - * Set the 'If-Modified-Since' request header to the given value - * - * @param ifModifiedSince - * @return this request - */ - public HttpRequest ifModifiedSince(final long ifModifiedSince) { - getConnection().setIfModifiedSince(ifModifiedSince); - return this; - } - - /** - * Set the 'If-None-Match' request header to the given value - * - * @param ifNoneMatch - * @return this request - */ - public HttpRequest ifNoneMatch(final String ifNoneMatch) { - return header(HEADER_IF_NONE_MATCH, ifNoneMatch); - } - - /** - * Set the 'Content-Type' request header to the given value - * - * @param contentType - * @return this request - */ - public HttpRequest contentType(final String contentType) { - return contentType(contentType, null); - } - - /** - * Set the 'Content-Type' request header to the given value and charset - * - * @param contentType - * @param charset - * @return this request - */ - public HttpRequest contentType(final String contentType, final String charset) { - if (charset != null && charset.length() > 0) { - final String separator = "; " + PARAM_CHARSET + '='; - return header(HEADER_CONTENT_TYPE, contentType + separator + charset); - } else - return header(HEADER_CONTENT_TYPE, contentType); - } - - /** - * Get the 'Content-Type' header from the response - * - * @return response header value - */ - public String contentType() { - return header(HEADER_CONTENT_TYPE); - } - - /** - * Get the 'Content-Length' header from the response - * - * @return response header value - */ - public int contentLength() { - return intHeader(HEADER_CONTENT_LENGTH); - } - - /** - * Set the 'Content-Length' request header to the given value - * - * @param contentLength - * @return this request - */ - public HttpRequest contentLength(final String contentLength) { - return contentLength(Integer.parseInt(contentLength)); - } - - /** - * Set the 'Content-Length' request header to the given value - * - * @param contentLength - * @return this request - */ - public HttpRequest contentLength(final int contentLength) { - getConnection().setFixedLengthStreamingMode(contentLength); - return this; - } - - /** - * Set the 'Accept' header to given value - * - * @param accept - * @return this request - */ - public HttpRequest accept(final String accept) { - return header(HEADER_ACCEPT, accept); - } - - /** - * Set the 'Accept' header to 'application/json' - * - * @return this request - */ - public HttpRequest acceptJson() { - return accept(CONTENT_TYPE_JSON); - } - - /** - * Copy from input stream to output stream - * - * @param input - * @param output - * @return this request - * @throws IOException - */ - protected HttpRequest copy(final InputStream input, final OutputStream output) - throws IOException { - return new CloseOperation(input, ignoreCloseExceptions) { - - @Override - public HttpRequest run() throws IOException { - final byte[] buffer = new byte[bufferSize]; - int read; - while ((read = input.read(buffer)) != -1) { - output.write(buffer, 0, read); - totalWritten += read; - progress.onUpload(totalWritten, totalSize); - } - return HttpRequest.this; - } - }.call(); - } - - /** - * Copy from reader to writer - * - * @param input - * @param output - * @return this request - * @throws IOException - */ - protected HttpRequest copy(final Reader input, final Writer output) - throws IOException { - return new CloseOperation(input, ignoreCloseExceptions) { - - @Override - public HttpRequest run() throws IOException { - final char[] buffer = new char[bufferSize]; - int read; - while ((read = input.read(buffer)) != -1) { - output.write(buffer, 0, read); - totalWritten += read; - progress.onUpload(totalWritten, -1); - } - return HttpRequest.this; - } - }.call(); - } - - /** - * Set the UploadProgress callback for this request - * - * @param callback - * @return this request - */ - public HttpRequest progress(final UploadProgress callback) { - if (callback == null) - progress = UploadProgress.DEFAULT; - else - progress = callback; - return this; - } - - private HttpRequest incrementTotalSize(final long size) { - if (totalSize == -1) - totalSize = 0; - totalSize += size; - return this; - } - - /** - * Close output stream - * - * @return this request - * @throws HttpRequestException - * @throws IOException - */ - protected HttpRequest closeOutput() throws IOException { - progress(null); - if (output == null) - return this; - if (multipart) - output.write(CRLF + "--" + BOUNDARY + "--" + CRLF); - if (ignoreCloseExceptions) - try { - output.close(); - } catch (IOException ignored) { - // Ignored - } - else - output.close(); - output = null; - return this; - } - - /** - * Call {@link #closeOutput()} and re-throw a caught {@link IOException}s as - * an {@link HttpRequestException} - * - * @return this request - * @throws HttpRequestException - */ - protected HttpRequest closeOutputQuietly() throws HttpRequestException { - try { - return closeOutput(); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Open output stream - * - * @return this request - * @throws IOException - */ - protected HttpRequest openOutput() throws IOException { - if (output != null) - return this; - getConnection().setDoOutput(true); - final String charset = getParam( - getConnection().getRequestProperty(HEADER_CONTENT_TYPE), PARAM_CHARSET); - output = new RequestOutputStream(getConnection().getOutputStream(), charset, - bufferSize); - return this; - } - - /** - * Start part of a multipart - * - * @return this request - * @throws IOException - */ - protected HttpRequest startPart() throws IOException { - if (!multipart) { - multipart = true; - contentType(CONTENT_TYPE_MULTIPART).openOutput(); - output.write("--" + BOUNDARY + CRLF); - } else - output.write(CRLF + "--" + BOUNDARY + CRLF); - return this; - } - - /** - * Write part header - * - * @param name - * @param filename - * @return this request - * @throws IOException - */ - protected HttpRequest writePartHeader(final String name, final String filename) - throws IOException { - return writePartHeader(name, filename, null); - } - - /** - * Write part header - * - * @param name - * @param filename - * @param contentType - * @return this request - * @throws IOException - */ - protected HttpRequest writePartHeader(final String name, - final String filename, final String contentType) throws IOException { - final StringBuilder partBuffer = new StringBuilder(); - partBuffer.append("form-data; name=\"").append(name); - if (filename != null) - partBuffer.append("\"; filename=\"").append(filename); - partBuffer.append('"'); - partHeader("Content-Disposition", partBuffer.toString()); - if (contentType != null) - partHeader(HEADER_CONTENT_TYPE, contentType); - return send(CRLF); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param part - * @return this request - */ - public HttpRequest part(final String name, final String part) { - return part(name, null, part); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param filename - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final String filename, - final String part) throws HttpRequestException { - return part(name, filename, null, part); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param filename - * @param contentType - * value of the Content-Type part header - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final String filename, - final String contentType, final String part) throws HttpRequestException { - try { - startPart(); - writePartHeader(name, filename, contentType); - output.write(part); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return this; - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final Number part) - throws HttpRequestException { - return part(name, null, part); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param filename - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final String filename, - final Number part) throws HttpRequestException { - return part(name, filename, part != null ? part.toString() : null); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final File part) - throws HttpRequestException { - return part(name, null, part); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param filename - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final String filename, - final File part) throws HttpRequestException { - return part(name, filename, null, part); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param filename - * @param contentType - * value of the Content-Type part header - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final String filename, - final String contentType, final File part) throws HttpRequestException { - final InputStream stream; - try { - stream = new BufferedInputStream(new FileInputStream(part)); - incrementTotalSize(part.length()); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return part(name, filename, contentType, stream); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final InputStream part) - throws HttpRequestException { - return part(name, null, null, part); - } - - /** - * Write part of a multipart request to the request body - * - * @param name - * @param filename - * @param contentType - * value of the Content-Type part header - * @param part - * @return this request - * @throws HttpRequestException - */ - public HttpRequest part(final String name, final String filename, - final String contentType, final InputStream part) - throws HttpRequestException { - try { - startPart(); - writePartHeader(name, filename, contentType); - copy(part, output); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return this; - } - - /** - * Write a multipart header to the response body - * - * @param name - * @param value - * @return this request - * @throws HttpRequestException - */ - public HttpRequest partHeader(final String name, final String value) - throws HttpRequestException { - return send(name).send(": ").send(value).send(CRLF); - } - - /** - * Write contents of file to request body - * - * @param input - * @return this request - * @throws HttpRequestException - */ - public HttpRequest send(final File input) throws HttpRequestException { - final InputStream stream; - try { - stream = new BufferedInputStream(new FileInputStream(input)); - incrementTotalSize(input.length()); - } catch (FileNotFoundException e) { - throw new HttpRequestException(e); - } - return send(stream); - } - - /** - * Write byte array to request body - * - * @param input - * @return this request - * @throws HttpRequestException - */ - public HttpRequest send(final byte[] input) throws HttpRequestException { - if (input != null) - incrementTotalSize(input.length); - return send(new ByteArrayInputStream(input)); - } - - /** - * Write stream to request body - *

- * The given stream will be closed once sending completes - * - * @param input - * @return this request - * @throws HttpRequestException - */ - public HttpRequest send(final InputStream input) throws HttpRequestException { - try { - openOutput(); - copy(input, output); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return this; - } - - /** - * Write reader to request body - *

- * The given reader will be closed once sending completes - * - * @param input - * @return this request - * @throws HttpRequestException - */ - public HttpRequest send(final Reader input) throws HttpRequestException { - try { - openOutput(); - } catch (IOException e) { - throw new HttpRequestException(e); - } - final Writer writer = new OutputStreamWriter(output, - output.encoder.charset()); - return new FlushOperation(writer) { - - @Override - protected HttpRequest run() throws IOException { - return copy(input, writer); - } - }.call(); - } - - /** - * Write char sequence to request body - *

- * The charset configured via {@link #contentType(String)} will be used and - * UTF-8 will be used if it is unset. - * - * @param value - * @return this request - * @throws HttpRequestException - */ - public HttpRequest send(final CharSequence value) throws HttpRequestException { - try { - openOutput(); - output.write(value.toString()); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return this; - } - - /** - * Create writer to request output stream - * - * @return writer - * @throws HttpRequestException - */ - public OutputStreamWriter writer() throws HttpRequestException { - try { - openOutput(); - return new OutputStreamWriter(output, output.encoder.charset()); - } catch (IOException e) { - throw new HttpRequestException(e); - } - } - - /** - * Write the values in the map as form data to the request body - *

- * The pairs specified will be URL-encoded in UTF-8 and sent with the - * 'application/x-www-form-urlencoded' content-type - * - * @param values - * @return this request - * @throws HttpRequestException - */ - public HttpRequest form(final Map values) throws HttpRequestException { - return form(values, CHARSET_UTF8); - } - - /** - * Write the key and value in the entry as form data to the request body - *

- * The pair specified will be URL-encoded in UTF-8 and sent with the - * 'application/x-www-form-urlencoded' content-type - * - * @param entry - * @return this request - * @throws HttpRequestException - */ - public HttpRequest form(final Entry entry) throws HttpRequestException { - return form(entry, CHARSET_UTF8); - } - - /** - * Write the key and value in the entry as form data to the request body - *

- * The pair specified will be URL-encoded and sent with the - * 'application/x-www-form-urlencoded' content-type - * - * @param entry - * @param charset - * @return this request - * @throws HttpRequestException - */ - public HttpRequest form(final Entry entry, final String charset) - throws HttpRequestException { - return form(entry.getKey(), entry.getValue(), charset); - } - - /** - * Write the name/value pair as form data to the request body - *

- * The pair specified will be URL-encoded in UTF-8 and sent with the - * 'application/x-www-form-urlencoded' content-type - * - * @param name - * @param value - * @return this request - * @throws HttpRequestException - */ - public HttpRequest form(final Object name, final Object value) - throws HttpRequestException { - return form(name, value, CHARSET_UTF8); - } - - /** - * Write the name/value pair as form data to the request body - *

- * The values specified will be URL-encoded and sent with the - * 'application/x-www-form-urlencoded' content-type - * - * @param name - * @param value - * @param charset - * @return this request - * @throws HttpRequestException - */ - public HttpRequest form(final Object name, final Object value, String charset) - throws HttpRequestException { - final boolean first = !form; - if (first) { - contentType(CONTENT_TYPE_FORM, charset); - form = true; - } - charset = getValidCharset(charset); - try { - openOutput(); - if (!first) - output.write('&'); - output.write(URLEncoder.encode(name.toString(), charset)); - output.write('='); - if (value != null) - output.write(URLEncoder.encode(value.toString(), charset)); - } catch (IOException e) { - throw new HttpRequestException(e); - } - return this; - } - - /** - * Write the values in the map as encoded form data to the request body - * - * @param values - * @param charset - * @return this request - * @throws HttpRequestException - */ - public HttpRequest form(final Map values, final String charset) - throws HttpRequestException { - if (!values.isEmpty()) - for (Entry entry : values.entrySet()) - form(entry, charset); - return this; - } - - /** - * Get the {@link URL} of this request's connection - * - * @return request URL - */ - public URL url() { - return getConnection().getURL(); - } - - /** - * Get the HTTP method of this request - * - * @return method - */ - public String method() { - return getConnection().getRequestMethod(); - } - - /** - * Configure an HTTP proxy on this connection. Use {{@link #proxyBasic(String, String)} if - * this proxy requires basic authentication. - * - * @param proxyHost - * @param proxyPort - * @return this request - */ - public HttpRequest useProxy(final String proxyHost, final int proxyPort) { - if (connection != null) - throw new IllegalStateException("The connection has already been created. This method must be called before reading or writing to the request."); - - this.httpProxyHost = proxyHost; - this.httpProxyPort = proxyPort; - return this; - } - - /** - * Set whether or not the underlying connection should follow redirects in - * the response. - * - * @param followRedirects - true fo follow redirects, false to not. - * @return this request - */ - public HttpRequest followRedirects(final boolean followRedirects) { - getConnection().setInstanceFollowRedirects(followRedirects); - return this; - } -} diff --git a/src/android/com/github/kevinsawicki/http/TLSSocketFactory.java b/src/android/com/github/kevinsawicki/http/TLSSocketFactory.java deleted file mode 100644 index 37cedb0..0000000 --- a/src/android/com/github/kevinsawicki/http/TLSSocketFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.github.kevinsawicki.http; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - -public class TLSSocketFactory extends SSLSocketFactory { - - private SSLSocketFactory delegate; - - public TLSSocketFactory(SSLContext context) { - delegate = context.getSocketFactory(); - } - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return enableTLSOnSocket(delegate.createSocket(s, host, port, autoClose)); - } - - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - return enableTLSOnSocket(delegate.createSocket(host, port)); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { - return enableTLSOnSocket(delegate.createSocket(host, port, localHost, localPort)); - } - - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - return enableTLSOnSocket(delegate.createSocket(host, port)); - } - - @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return enableTLSOnSocket(delegate.createSocket(address, port, localAddress, localPort)); - } - - private Socket enableTLSOnSocket(Socket socket) { - if(socket != null && (socket instanceof SSLSocket)) { - ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}); - } - return socket; - } -} diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java new file mode 100644 index 0000000..b005e57 --- /dev/null +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpBase.java @@ -0,0 +1,171 @@ +package com.silkimen.cordovahttp; + +import java.io.ByteArrayOutputStream; + +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import java.nio.ByteBuffer; + +import javax.net.ssl.SSLHandshakeException; + +import com.silkimen.http.HttpBodyDecoder; +import com.silkimen.http.HttpRequest; +import com.silkimen.http.HttpRequest.HttpRequestException; +import com.silkimen.http.JsonUtils; + +import org.apache.cordova.CallbackContext; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.util.Log; + +abstract class CordovaHttpBase implements Runnable { + protected static final String TAG = "Cordova-Plugin-HTTP"; + + protected String method; + protected String url; + protected String serializer = "none"; + protected Object data; + protected JSONObject params; + protected JSONObject headers; + protected int timeout; + protected CallbackContext callbackContext; + + public CordovaHttpBase(String method, String url, String serializer, Object data, JSONObject headers, int timeout, + CallbackContext callbackContext) { + + this.method = method; + this.url = url; + this.serializer = serializer; + this.data = data; + this.headers = headers; + this.timeout = timeout; + this.callbackContext = callbackContext; + } + + public CordovaHttpBase(String method, String url, JSONObject params, JSONObject headers, int timeout, + CallbackContext callbackContext) { + + this.method = method; + this.url = url; + this.params = params; + this.headers = headers; + this.timeout = timeout; + this.callbackContext = callbackContext; + } + + @Override + public void run() { + CordovaHttpResponse response = new CordovaHttpResponse(); + + try { + HttpRequest request = this.createRequest(); + this.prepareRequest(request); + this.sendBody(request); + this.processResponse(request, response); + } catch (HttpRequestException e) { + if (e.getCause() instanceof SSLHandshakeException) { + response.setStatus(-2); + response.setErrorMessage("SSL handshake failed: " + e.getMessage()); + Log.w(TAG, "SSL handshake failed", e); + } else if (e.getCause() instanceof UnknownHostException) { + response.setStatus(-3); + response.setErrorMessage("Host could not be resolved: " + e.getMessage()); + Log.w(TAG, "Host could not be resolved", e); + } else if (e.getCause() instanceof SocketTimeoutException) { + response.setStatus(-4); + response.setErrorMessage("Request timed out: " + e.getMessage()); + Log.w(TAG, "Request timed out", e); + } else { + response.setStatus(-1); + response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage()); + Log.w(TAG, "Generic request error", e); + } + } catch (Exception e) { + response.setStatus(-1); + response.setErrorMessage(e.getMessage()); + Log.e(TAG, "An unexpected error occured", e); + } + + try { + if (response.hasFailed()) { + this.callbackContext.error(response.toJSON()); + } else { + this.callbackContext.success(response.toJSON()); + } + } catch (JSONException e) { + Log.e(TAG, "An unexpected error occured while creating HTTP response object", e); + } + } + + protected HttpRequest createRequest() throws JSONException { + String processedUrl = HttpRequest.encode(HttpRequest.append(this.url, JsonUtils.getObjectMap(this.params))); + HttpRequest request = new HttpRequest(processedUrl, this.method); + + return request; + } + + protected void prepareRequest(HttpRequest request) throws JSONException { + request.followRedirects(true /* @TODO */); + request.readTimeout(this.timeout); + request.acceptCharset("UTF-8"); + request.uncompress(true); + + // setup content type before applying headers, so user can override it + this.setContentType(request); + + request.headers(JsonUtils.getStringMap(this.headers)); + } + + protected void setContentType(HttpRequest request) { + switch (this.serializer) { + case "json": + request.contentType("application/json", "UTF-8"); + break; + case "utf8": + request.contentType("text/plain", "UTF-8"); + break; + case "urlencoded": + // intentionally left blank, because content type is set in HttpRequest.form() + break; + } + } + + protected void sendBody(HttpRequest request) throws Exception { + if (this.data == null) { + return; + } + + switch (this.serializer) { + case "json": + request.send(this.data.toString()); + break; + case "utf8": + request.send(((JSONObject) this.data).getString("text")); + break; + case "urlencoded": + request.form(JsonUtils.getObjectMap((JSONObject) this.data)); + break; + } + } + + protected void processResponse(HttpRequest request, CordovaHttpResponse response) throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + request.receive(outputStream); + + ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray()); + String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset()); + + response.setStatus(request.code()); + response.setUrl(request.url().toString()); + response.setHeaders(request.headers()); + + if (request.code() >= 200 && request.code() < 300) { + response.setBody(decodedBody); + } else { + response.setErrorMessage(decodedBody); + } + } +} diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java b/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java index 05f73fe..9c6bc5e 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpDownload.java @@ -1,105 +1,38 @@ package com.silkimen.cordovahttp; import java.io.File; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; import java.net.URI; -import java.net.URISyntaxException; -import javax.net.ssl.SSLHandshakeException; - -import com.silkimen.http.HttpBodyDecoder; import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; -import com.silkimen.http.JsonUtils; import org.apache.cordova.CallbackContext; import org.apache.cordova.file.FileUtils; - -import org.json.JSONException; import org.json.JSONObject; -import android.util.Log; - -class CordovaHttpDownload implements Runnable { - private static final String TAG = "Cordova-Plugin-HTTP"; - - private String url; - private JSONObject params; - private JSONObject headers; +class CordovaHttpDownload extends CordovaHttpBase { private String filePath; - private int timeout; - private CallbackContext callbackContext; public CordovaHttpDownload(String url, JSONObject params, JSONObject headers, String filePath, int timeout, CallbackContext callbackContext) { - this.url = url; - this.params = params; - this.headers = headers; + super("GET", url, params, headers, timeout, callbackContext); this.filePath = filePath; - this.timeout = timeout; - this.callbackContext = callbackContext; } @Override - public void run() { - CordovaHttpResponse response = new CordovaHttpResponse(); + protected void processResponse(HttpRequest request, CordovaHttpResponse response) throws Exception { + response.setStatus(request.code()); + response.setUrl(request.url().toString()); + response.setHeaders(request.headers()); - try { - String processedUrl = HttpRequest.encode(HttpRequest.append(this.url, JsonUtils.getObjectMap(this.params))); + if (request.code() >= 200 && request.code() < 300) { + File file = new File(new URI(this.filePath)); + JSONObject fileEntry = FileUtils.getFilePlugin().getEntryForFile(file); - HttpRequest request = new HttpRequest(processedUrl, "GET") - .followRedirects(true /* @TODO */) - .readTimeout(this.timeout) - .acceptCharset("UTF-8") - .uncompress(true) - .headers(JsonUtils.getStringMap(this.headers)); - - response.setStatus(request.code()); - response.setUrl(request.url().toString()); - response.setHeaders(request.headers()); - - if (request.code() >= 200 && request.code() < 300) { - File file = new File(new URI(filePath)); - - request.receive(file); - response.setFileEntry(FileUtils.getFilePlugin().getEntryForFile(file)); - } else { - response.setErrorMessage("There was an error downloading the file"); - } - } catch (HttpRequestException e) { - if (e.getCause() instanceof SSLHandshakeException) { - response.setStatus(-2); - response.setErrorMessage("SSL handshake failed: " + e.getMessage()); - Log.w(TAG, "SSL handshake failed", e); - } else if (e.getCause() instanceof UnknownHostException) { - response.setStatus(-3); - response.setErrorMessage("Host could not be resolved: " + e.getMessage()); - Log.w(TAG, "Host could not be resolved", e); - } else if (e.getCause() instanceof SocketTimeoutException) { - response.setStatus(-4); - response.setErrorMessage("Request timed out: " + e.getMessage()); - Log.w(TAG, "Request timed out", e); - } else { - response.setStatus(-1); - response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage()); - Log.w(TAG, "Generic request error", e); - } - } catch (Exception e) { - response.setStatus(-1); - response.setErrorMessage(e.getMessage()); - Log.e(TAG, "An unexpected error occured", e); - } - - try { - if (response.hasFailed()) { - this.callbackContext.error(response.toJSON()); - } else { - this.callbackContext.success(response.toJSON()); - } - } catch (JSONException e) { - Log.e(TAG, "An unexpected error occured while processing HTTP response", e); + request.receive(file); + response.setFileEntry(fileEntry); + } else { + response.setErrorMessage("There was an error downloading the file"); } } } diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java b/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java new file mode 100644 index 0000000..97fe824 --- /dev/null +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpOperation.java @@ -0,0 +1,18 @@ +package com.silkimen.cordovahttp; + +import org.apache.cordova.CallbackContext; +import org.json.JSONObject; + +class CordovaHttpOperation extends CordovaHttpBase { + public CordovaHttpOperation(String method, String url, String serializer, Object data, JSONObject headers, + int timeout, CallbackContext callbackContext) { + + super(method, url, serializer, data, headers, timeout, callbackContext); + } + + public CordovaHttpOperation(String method, String url, JSONObject params, JSONObject headers, int timeout, + CallbackContext callbackContext) { + + super(method, url, params, headers, timeout, callbackContext); + } +} diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java index 8f73e00..9392ba3 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpPlugin.java @@ -83,10 +83,10 @@ public class CordovaHttpPlugin extends CordovaPlugin { JSONObject headers = args.getJSONObject(2); int timeout = args.getInt(3) * 1000; - CordovaHttpRequest get = new CordovaHttpRequest(method.toUpperCase(), url, params, headers, timeout, + CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, params, headers, timeout, callbackContext); - cordova.getThreadPool().execute(get); + cordova.getThreadPool().execute(request); return true; } @@ -100,7 +100,7 @@ public class CordovaHttpPlugin extends CordovaPlugin { JSONObject headers = args.getJSONObject(3); int timeout = args.getInt(4) * 1000; - CordovaHttpRequest request = new CordovaHttpRequest(method.toUpperCase(), url, serializer, data, headers, timeout, + CordovaHttpOperation request = new CordovaHttpOperation(method.toUpperCase(), url, serializer, data, headers, timeout, callbackContext); cordova.getThreadPool().execute(request); diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpRequest.java b/src/android/com/silkimen/cordovahttp/CordovaHttpRequest.java deleted file mode 100644 index 65b0a49..0000000 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpRequest.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.silkimen.cordovahttp; - -import java.io.ByteArrayOutputStream; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import java.nio.ByteBuffer; - -import javax.net.ssl.SSLHandshakeException; - -import com.silkimen.http.HttpBodyDecoder; -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; -import com.silkimen.http.JsonUtils; - -import org.apache.cordova.CallbackContext; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.util.Log; - -public class CordovaHttpRequest implements Runnable { - private static final String TAG = "Cordova-Plugin-HTTP"; - - private String method; - private String url; - private String serializer = "none"; - private Object data; - private JSONObject params; - private JSONObject headers; - private int timeout; - private CallbackContext callbackContext; - - public CordovaHttpRequest(String method, String url, String serializer, Object data, JSONObject headers, - int timeout, CallbackContext callbackContext) { - - this.method = method; - this.url = url; - this.serializer = serializer; - this.data = data; - this.headers = headers; - this.timeout = timeout; - this.callbackContext = callbackContext; - } - - public CordovaHttpRequest(String method, String url, JSONObject params, JSONObject headers, int timeout, - CallbackContext callbackContext) { - - this.method = method; - this.url = url; - this.params = params; - this.headers = headers; - this.timeout = timeout; - this.callbackContext = callbackContext; - } - - @Override - public void run() { - CordovaHttpResponse response = new CordovaHttpResponse(); - - try { - String processedUrl = HttpRequest.encode(HttpRequest.append(this.url, JsonUtils.getObjectMap(this.params))); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - HttpRequest request = new HttpRequest(processedUrl, this.method) - .followRedirects(true /* @TODO */) - .readTimeout(this.timeout) - .acceptCharset("UTF-8") - .uncompress(true); - - // setup content type before applying headers, so user can override it - this.setContentType(request) - .headers(JsonUtils.getStringMap(this.headers)); - - this.sendBody(request) - .receive(outputStream); - - ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray()); - String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset()); - - response.setStatus(request.code()); - response.setUrl(request.url().toString()); - response.setHeaders(request.headers()); - - if (request.code() >= 200 && request.code() < 300) { - response.setBody(decodedBody); - } else { - response.setErrorMessage(decodedBody); - } - } catch (HttpRequestException e) { - if (e.getCause() instanceof SSLHandshakeException) { - response.setStatus(-2); - response.setErrorMessage("SSL handshake failed: " + e.getMessage()); - Log.w(TAG, "SSL handshake failed", e); - } else if (e.getCause() instanceof UnknownHostException) { - response.setStatus(-3); - response.setErrorMessage("Host could not be resolved: " + e.getMessage()); - Log.w(TAG, "Host could not be resolved", e); - } else if (e.getCause() instanceof SocketTimeoutException) { - response.setStatus(-4); - response.setErrorMessage("Request timed out: " + e.getMessage()); - Log.w(TAG, "Request timed out", e); - } else { - response.setStatus(-1); - response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage()); - Log.w(TAG, "Generic request error", e); - } - } catch (Exception e) { - response.setStatus(-1); - response.setErrorMessage(e.getMessage()); - Log.e(TAG, "An unexpected error occured", e); - } - - try { - if (response.hasFailed()) { - this.callbackContext.error(response.toJSON()); - } else { - this.callbackContext.success(response.toJSON()); - } - } catch (JSONException e) { - Log.e(TAG, "An unexpected error occured while processing HTTP response", e); - } - } - - private HttpRequest setContentType(HttpRequest request) { - switch(this.serializer) { - case "json": - return request.contentType("application/json", "UTF-8"); - case "utf8": - return request.contentType("text/plain", "UTF-8"); - case "urlencoded": - // intentionally left blank, because content type is set in HttpRequest.form() - } - - return request; - } - - private HttpRequest sendBody(HttpRequest request) throws JSONException { - if (this.data == null) { - return request; - } - - switch (this.serializer) { - case "json": - return request.send(this.data.toString()); - case "utf8": - return request.send(((JSONObject) this.data).getString("text")); - case "urlencoded": - return request.form(JsonUtils.getObjectMap((JSONObject) this.data)); - } - - return request; - } -} diff --git a/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java b/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java index 8d96b50..31e0e8a 100644 --- a/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java +++ b/src/android/com/silkimen/cordovahttp/CordovaHttpUpload.java @@ -1,128 +1,39 @@ package com.silkimen.cordovahttp; -import java.io.ByteArrayOutputStream; -import java.io.File; +import android.webkit.MimeTypeMap; -import java.net.SocketTimeoutException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; - -import java.nio.ByteBuffer; - -import javax.net.ssl.SSLHandshakeException; - -import com.silkimen.http.HttpBodyDecoder; import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; -import com.silkimen.http.JsonUtils; + +import java.io.File; +import java.net.URI; import org.apache.cordova.CallbackContext; - -import org.json.JSONException; import org.json.JSONObject; -import android.webkit.MimeTypeMap; -import android.util.Log; -class CordovaHttpUpload implements Runnable { - private static final String TAG = "Cordova-Plugin-HTTP"; - - private String url; - private JSONObject params; - private JSONObject headers; +class CordovaHttpUpload extends CordovaHttpBase { private String filePath; private String uploadName; - private int timeout; - private CallbackContext callbackContext; public CordovaHttpUpload(String url, JSONObject params, JSONObject headers, String filePath, String uploadName, int timeout, CallbackContext callbackContext) { - this.url = url; - this.params = params; - this.headers = headers; + super("POST", url, params, headers, timeout, callbackContext); this.filePath = filePath; this.uploadName = uploadName; - this.timeout = timeout; - this.callbackContext = callbackContext; } @Override - public void run() { - CordovaHttpResponse response = new CordovaHttpResponse(); + protected void sendBody(HttpRequest request) throws Exception { + int filenameIndex = this.filePath.lastIndexOf('/'); + String filename = this.filePath.substring(filenameIndex + 1); - try { - String processedUrl = HttpRequest.encode(HttpRequest.append(this.url, JsonUtils.getObjectMap(this.params))); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + int extIndex = this.filePath.lastIndexOf('.'); + String ext = this.filePath.substring(extIndex + 1); - HttpRequest request = new HttpRequest(processedUrl, "POST") - .followRedirects(true /* @TODO */) - .readTimeout(this.timeout) - .acceptCharset("UTF-8") - .uncompress(true) - .headers(JsonUtils.getStringMap(this.headers)); + MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); + String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext); - int filenameIndex = filePath.lastIndexOf('/'); - String filename = filePath.substring(filenameIndex + 1); - - int extIndex = filePath.lastIndexOf('.'); - String ext = filePath.substring(extIndex + 1); - - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String mimeType = mimeTypeMap.getMimeTypeFromExtension(ext); - - request.part(this.uploadName, filename, mimeType, new File(new URI(this.filePath))) - .receive(outputStream); - - ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray()); - String decodedBody = HttpBodyDecoder.decodeBody(rawOutput, request.charset()); - - response.setStatus(request.code()); - response.setUrl(request.url().toString()); - response.setHeaders(request.headers()); - - if (request.code() >= 200 && request.code() < 300) { - response.setBody(decodedBody); - } else { - response.setErrorMessage(decodedBody); - } - } catch (HttpRequestException e) { - if (e.getCause() instanceof SSLHandshakeException) { - response.setStatus(-2); - response.setErrorMessage("SSL handshake failed: " + e.getMessage()); - Log.w(TAG, "SSL handshake failed", e); - } else if (e.getCause() instanceof UnknownHostException) { - response.setStatus(-3); - response.setErrorMessage("Host could not be resolved: " + e.getMessage()); - Log.w(TAG, "Host could not be resolved", e); - } else if (e.getCause() instanceof SocketTimeoutException) { - response.setStatus(-4); - response.setErrorMessage("Request timed out: " + e.getMessage()); - Log.w(TAG, "Request timed out", e); - } else { - response.setStatus(-1); - response.setErrorMessage("There was an error with the request: " + e.getCause().getMessage()); - Log.w(TAG, "Generic request error", e); - } - } catch (URISyntaxException e) { - response.setStatus(-1); - response.setErrorMessage("An error occured while loading file"); - Log.e(TAG, "An error occured while loading file", e); - } catch (Exception e) { - response.setStatus(-1); - response.setErrorMessage(e.getMessage()); - Log.e(TAG, "An unexpected error occured", e); - } - - try { - if (response.hasFailed()) { - this.callbackContext.error(response.toJSON()); - } else { - this.callbackContext.success(response.toJSON()); - } - } catch (JSONException e) { - Log.e(TAG, "An unexpected error occured while processing HTTP response", e); - } + request.part(this.uploadName, filename, mimeType, new File(new URI(this.filePath))); } } diff --git a/src/android/com/synconset/cordovahttp/CordovaHttp.java b/src/android/com/synconset/cordovahttp/CordovaHttp.java deleted file mode 100644 index ee14091..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttp.java +++ /dev/null @@ -1,249 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import org.apache.cordova.CallbackContext; - -import org.json.JSONException; -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; - -import java.nio.charset.CharacterCodingException; -import java.nio.charset.MalformedInputException; - -import javax.net.ssl.SSLHandshakeException; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.Iterator; - -import android.text.TextUtils; - -import com.silkimen.http.HttpBodyDecoder; -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; - -abstract class CordovaHttp { - protected static final String TAG = "CordovaHTTP"; - private static AtomicBoolean disableRedirect = new AtomicBoolean(false); - - private String urlString; - private Object params; - private String serializerName; - private JSONObject headers; - private int timeoutInMilliseconds; - private CallbackContext callbackContext; - - public CordovaHttp(String urlString, Object params, JSONObject headers, int timeout, - CallbackContext callbackContext) { - this(urlString, params, "default", headers, timeout, callbackContext); - } - - public CordovaHttp(String urlString, Object params, String serializerName, JSONObject headers, int timeout, - CallbackContext callbackContext) { - this.urlString = urlString; - this.params = params; - this.serializerName = serializerName; - this.headers = headers; - this.timeoutInMilliseconds = timeout; - this.callbackContext = callbackContext; - - - } - - public static void disableRedirect(boolean disable) { - disableRedirect.set(disable); - } - - protected String getUrlString() { - return this.urlString; - } - - protected Object getParamsObject() { - return this.params; - } - - protected String getSerializerName() { - return this.serializerName; - } - - protected HashMap getParamsMap() throws JSONException, Exception { - if (this.params instanceof JSONObject) { - return this.getMapFromJSONObject((JSONObject) this.params); - } else { - throw new Exception("unsupported params type, needs to be a JSON object"); - } - } - - protected JSONObject getHeadersObject() { - return this.headers; - } - - protected HashMap getHeadersMap() throws JSONException { - return this.getStringMapFromJSONObject(this.headers); - } - - protected int getRequestTimeout() { - return this.timeoutInMilliseconds; - } - - protected CallbackContext getCallbackContext() { - return this.callbackContext; - } - - protected HttpRequest setupRedirect(HttpRequest request) { - if (disableRedirect.get()) { - request.followRedirects(false); - } - - return request; - } - - protected void setupDataSerializer(HttpRequest request) throws JSONException, Exception { - if ("json".equals(this.getSerializerName())) { - request.contentType(request.CONTENT_TYPE_JSON, request.CHARSET_UTF8); - } else if ("utf8".equals(this.getSerializerName())) { - request.contentType("text/plain", request.CHARSET_UTF8); - } - } - - protected void respondWithError(int status, String msg) { - try { - JSONObject response = new JSONObject(); - response.put("status", status); - response.put("error", msg); - this.callbackContext.error(response); - } catch (JSONException e) { - this.callbackContext.error(msg); - } - } - - protected void respondWithError(String msg) { - this.respondWithError(-1, msg); - } - - protected void addResponseHeaders(HttpRequest request, JSONObject response) throws JSONException { - Map> headers = request.headers(); - Map filteredHeaders = new HashMap(); - - for (Map.Entry> entry : headers.entrySet()) { - String key = entry.getKey(); - List value = entry.getValue(); - - if ((key != null) && (!value.isEmpty())) { - filteredHeaders.put(key.toLowerCase(), TextUtils.join(", ", value)); - } - } - - response.put("headers", new JSONObject(filteredHeaders)); - } - - protected HashMap getStringMapFromJSONObject(JSONObject object) throws JSONException { - HashMap map = new HashMap(); - Iterator i = object.keys(); - - while (i.hasNext()) { - String key = (String) i.next(); - map.put(key, object.getString(key)); - } - return map; - } - - protected ArrayList getListFromJSONArray(JSONArray array) throws JSONException { - ArrayList list = new ArrayList(); - - for (int i = 0; i < array.length(); i++) { - list.add(array.get(i)); - } - return list; - } - - protected HashMap getMapFromJSONObject(JSONObject object) throws JSONException { - HashMap map = new HashMap(); - Iterator i = object.keys(); - - while (i.hasNext()) { - String key = (String) i.next(); - Object value = object.get(key); - - if (value instanceof JSONArray) { - map.put(key, getListFromJSONArray((JSONArray) value)); - } else { - map.put(key, object.get(key)); - } - } - return map; - } - - protected void prepareRequest(HttpRequest request) throws HttpRequestException, JSONException { - this.setupRedirect(request); - - request.readTimeout(this.getRequestTimeout()); - // request.acceptCharset(ACCEPTED_CHARSETS); - request.headers(this.getHeadersMap()); - request.uncompress(true); - } - - protected void prepareRequestBody(HttpRequest request) throws JSONException, Exception { - if ("json".equals(this.getSerializerName())) { - request.send(this.getParamsObject().toString()); - } else if ("utf8".equals(this.getSerializerName())) { - request.send(this.getParamsMap().get("text").toString()); - } else { - request.form(this.getParamsMap()); - } - } - - protected void returnResponseObject(HttpRequest request) throws HttpRequestException { - try { - JSONObject response = new JSONObject(); - int code = request.code(); - - final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - request.receive(outputStream); - ByteBuffer rawOutput = ByteBuffer.wrap(outputStream.toByteArray()); - - //request.body(rawOutput); - response.put("status", code); - response.put("url", request.url().toString()); - this.addResponseHeaders(request, response); - - if (code >= 200 && code < 300) { - response.put("data", HttpBodyDecoder.decodeBody(rawOutput, request.charset())); - this.getCallbackContext().success(response); - } else { - response.put("error", HttpBodyDecoder.decodeBody(rawOutput, request.charset())); - this.getCallbackContext().error(response); - } - } catch (JSONException e) { - this.respondWithError("There was an error generating the response"); - } catch (MalformedInputException e) { - this.respondWithError("Could not decode response data due to malformed data"); - } catch (CharacterCodingException e) { - this.respondWithError("Could not decode response data due to invalid or unknown charset encoding"); - } - } - - protected void handleHttpRequestException(HttpRequestException e) { - if (e.getCause() instanceof UnknownHostException) { - this.respondWithError(0, "The host could not be resolved: " + e.getMessage()); - } else if (e.getCause() instanceof SocketTimeoutException) { - this.respondWithError(1, "The request timed out: " + e.getMessage()); - } else if (e.getCause() instanceof SSLHandshakeException) { - this.respondWithError(-2, "SSL handshake failed: " + e.getMessage()); - } else { - this.respondWithError("There was an error with the request: " + e.getMessage()); - } - } -} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java b/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java deleted file mode 100644 index 4857828..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttpDelete.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLHandshakeException; - -import org.apache.cordova.CallbackContext; - -import org.json.JSONException; -import org.json.JSONObject; - -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; - -class CordovaHttpDelete extends CordovaHttp implements Runnable { - public CordovaHttpDelete(String urlString, Object data, JSONObject headers, int timeout, CallbackContext callbackContext) { - super(urlString, data, headers, timeout, callbackContext); - } - - @Override - public void run() { - try { - HttpRequest request = HttpRequest.delete(this.getUrlString(), this.getParamsMap(), false); - - this.prepareRequest(request); - this.returnResponseObject(request); - } catch (HttpRequestException e) { - this.handleHttpRequestException(e); - } catch (Exception e) { - this.respondWithError(e.getMessage()); - } - } -} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpHead.java b/src/android/com/synconset/cordovahttp/CordovaHttpHead.java deleted file mode 100644 index 224788a..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttpHead.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLHandshakeException; - -import org.apache.cordova.CallbackContext; -import org.json.JSONException; -import org.json.JSONObject; - -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; - -class CordovaHttpHead extends CordovaHttp implements Runnable { - public CordovaHttpHead(String urlString, Object params, JSONObject headers, int timeout, CallbackContext callbackContext) { - super(urlString, params, headers, timeout, callbackContext); - } - - @Override - public void run() { - try { - HttpRequest request = HttpRequest.head(this.getUrlString(), this.getParamsMap(), false); - - this.prepareRequest(request); - this.returnResponseObject(request); - } catch (HttpRequestException e) { - this.handleHttpRequestException(e); - } catch (Exception e) { - this.respondWithError(e.getMessage()); - } - } -} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java b/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java deleted file mode 100644 index e6015cf..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPatch.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import org.apache.cordova.CallbackContext; -import org.json.JSONException; -import org.json.JSONObject; - -import javax.net.ssl.SSLHandshakeException; - -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; - -class CordovaHttpPatch extends CordovaHttp implements Runnable { - public CordovaHttpPatch(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { - super(urlString, params, serializerName, headers, timeout, callbackContext); - } - - @Override - public void run() { - try { - /* todo */ - HttpRequest request = HttpRequest.get(this.getUrlString()); - - this.setupDataSerializer(request); - this.prepareRequest(request); - this.prepareRequestBody(request); - this.returnResponseObject(request); - } catch (HttpRequestException e) { - this.handleHttpRequestException(e); - } catch (Exception e) { - this.respondWithError(e.getMessage()); - } - } -} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPost.java b/src/android/com/synconset/cordovahttp/CordovaHttpPost.java deleted file mode 100644 index 4696086..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPost.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import org.apache.cordova.CallbackContext; -import org.json.JSONException; -import org.json.JSONObject; - -import javax.net.ssl.SSLHandshakeException; - -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; - -class CordovaHttpPost extends CordovaHttp implements Runnable { - public CordovaHttpPost(String urlString, Object params, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { - super(urlString, params, serializerName, headers, timeout, callbackContext); - } - - @Override - public void run() { - try { - HttpRequest request = HttpRequest.post(this.getUrlString()); - - this.setupDataSerializer(request); - this.prepareRequest(request); - this.prepareRequestBody(request); - this.returnResponseObject(request); - } catch (HttpRequestException e) { - this.handleHttpRequestException(e); - } catch (Exception e) { - this.respondWithError(e.getMessage()); - } - } -} diff --git a/src/android/com/synconset/cordovahttp/CordovaHttpPut.java b/src/android/com/synconset/cordovahttp/CordovaHttpPut.java deleted file mode 100644 index 0386b4a..0000000 --- a/src/android/com/synconset/cordovahttp/CordovaHttpPut.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * A HTTP plugin for Cordova / Phonegap - */ -package com.synconset.cordovahttp; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import org.apache.cordova.CallbackContext; -import org.json.JSONException; -import org.json.JSONObject; - -import javax.net.ssl.SSLHandshakeException; - -import com.silkimen.http.HttpRequest; -import com.silkimen.http.HttpRequest.HttpRequestException; - -class CordovaHttpPut extends CordovaHttp implements Runnable { - public CordovaHttpPut(String urlString, Object data, String serializerName, JSONObject headers, int timeout, CallbackContext callbackContext) { - super(urlString, data, serializerName, headers, timeout, callbackContext); - } - - @Override - public void run() { - try { - HttpRequest request = HttpRequest.put(this.getUrlString()); - - this.setupDataSerializer(request); - this.prepareRequest(request); - this.prepareRequestBody(request); - this.returnResponseObject(request); - } catch (HttpRequestException e) { - this.handleHttpRequestException(e); - } catch (Exception e) { - this.respondWithError(e.getMessage()); - } - } -}