askIT

Image upload from Android to PHP server

save.png Download source code

01 Create HttpRequest

First you need to create a Java class named HttpRequest created by kevinsawicki.

HttpRequest:

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.PrivilegedAction;
import java.security.SecureRandom;
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.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * A fluid interface for making HTTP requests using an underlying
 * {@link HttpURLConnection} (or sub-class).
 * <p>
 * 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";

  /**
   * '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";

  /**
   * '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";

  private static final String BOUNDARY = "00content0boundary00";

  private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary="
      + BOUNDARY;

  private static final String CRLF = "
";

  private static final String[] EMPTY_STRINGS = new String[0];

  private static SSLSocketFactory TRUSTED_FACTORY;

  private static HostnameVerifier TRUSTED_VERIFIER;

  private static String getValidCharset(final String charset) {
    if (charset != null && charset.length() > 0)
      return charset;
    else
      return CHARSET_UTF8;
  }

  private static SSLSocketFactory getTrustedFactory()
      throws HttpRequestException {
    if (TRUSTED_FACTORY == null) {
      final TrustManager[] trustAllCerts = 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
        }
      } };
      try {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, trustAllCerts, new SecureRandom());
        TRUSTED_FACTORY = context.getSocketFactory();
      } catch (GeneralSecurityException e) {
        IOException ioException = new IOException(
            "Security exception configuring SSL context");
        ioException.initCause(e);
        throw new HttpRequestException(ioException);
      }
    }

    return TRUSTED_FACTORY;
  }

  private static HostnameVerifier getTrustedVerifier() {
    if (TRUSTED_VERIFIER == null)
      TRUSTED_VERIFIER = new HostnameVerifier() {

        public boolean verify(String hostname, SSLSession session) {
          return true;
        }
      };

    return TRUSTED_VERIFIER;
  }

  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) {
    if (value != null && value.getClass().isArray())
      value = arrayToList(value);

    if (value instanceof Iterable<?>) {
      Iterator<?> iterator = ((Iterable<?>) value).iterator();
      while (iterator.hasNext()) {
        result.append(key);
        result.append("[]=");
        Object element = iterator.next();
        if (element != null)
          result.append(element);
        if (iterator.hasNext())
          result.append("&");
      }
    } else {
      result.append(key);
      result.append("=");
      if (value != null)
        result.append(value);
    }

    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 ConnectionFactory() {
      public HttpURLConnection create(URL url) throws IOException {
        return (HttpURLConnection) url.openConnection();
      }

      public HttpURLConnection create(URL url, Proxy proxy) throws IOException {
        return (HttpURLConnection) url.openConnection(proxy);
      }
    };
  }

  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;
  }

  /**
   * 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) {
      }
    };
  }

  /**
   * <p>
   * Encodes and decodes to and from Base64 notation.
   * </p>
   * <p>
   * 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 <a
   * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically
   * to check for updates or to contribute improvements.
   * </p>
   *
   * @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() {
    }

    /**
     * <p>
     * Encodes up to three bytes of the array <var>source</var> and writes the
     * resulting four Base64 bytes to <var>destination</var>. The source and
     * destination arrays can be manipulated anywhere along their length by
     * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
     * does not check to make sure your arrays are large enough to accomodate
     * <var>srcOffset</var> + 3 for the <var>source</var> array or
     * <var>destOffset</var> + 4 for the <var>destination</var> array. The
     * actual number of significant bytes in your array is given by
     * <var>numSigBytes</var>.
     * </p>
     * <p>
     * This is the lowest level of the encoding methods with all possible
     * parameters.
     * </p>
     *
     * @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 <var>destination</var> 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 <V>
   */
  protected static abstract class Operation<V> implements Callable<V> {

    /**
     * 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 <V>
   */
  protected static abstract class CloseOperation<V> extends Operation<V> {

    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 <V>
   */
  protected static abstract class FlushOperation<V> extends Operation<V> {

    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<Object> arrayToList(final Object array) {
    if (array instanceof Object[])
      return Arrays.asList((Object[]) array);

    List<Object> result = new ArrayList<Object>();
    // 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}
   * <p>
   * 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
   * <p>
   * 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
   * <p>
   * 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 '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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * Hosts will be separated by a '|' character.
   * <p>
   * 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.
   * <p>
   * 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<String> action;
    if (value != null)
      action = new PrivilegedAction<String>() {

        public String run() {
          return System.setProperty(name, value);
        }
      };
    else
      action = new PrivilegedAction<String>() {

        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;
  }

  /**
   * 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;
  }

  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()}
   * <p>
   * The default value of this setting is <code>true</code>
   *
   * @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
   * <p>
   * This size is also used for send and receive buffers created for both char
   * and byte arrays
   * <p>
   * 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
   * <p>
   * 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.
   * <p>
   * This will only affect requests that have the 'Content-Encoding' response
   * header set to 'gzip'.
   * <p>
   * This causes all receive methods to use a {@link GZIPInputStream} when
   * applicable so that higher level streams and readers can read the data
   * uncompressed.
   * <p>
   * 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
   * <p>
   * 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 a {@link String} and set it as the value of the
   * given reference.
   *
   * @param output
   * @return this request
   * @throws HttpRequestException
   */
  public HttpRequest body(final AtomicReference<String> output) throws HttpRequestException {
    output.set(body());
    return this;
  }

  /**
   * 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<String> 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.
   * <p>
   * 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<HttpRequest>(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<HttpRequest>(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<HttpRequest>(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<String, String> headers) {
    if (!headers.isEmpty())
      for (Entry<String, String> 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<String, String> 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<String, List<String>> 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<String, List<String>> headers = headers();
    if (headers == null || headers.isEmpty())
      return EMPTY_STRINGS;

    final List<String> 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
   * <p>
   * 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<String, String> 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<String, String> 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<String, String> params = new LinkedHashMap<String, String>();
    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);
  }

  /**
   * 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<HttpRequest>(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<HttpRequest>(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
   * <p>
   * 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
   * <p>
   * 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<HttpRequest>(writer) {

      @Override
      protected HttpRequest run() throws IOException {
        return copy(input, writer);
      }
    }.call();
  }

  /**
   * Write char sequence to request body
   * <p>
   * 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
   * <p>
   * 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
   * <p>
   * 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
   * <p>
   * 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
   * <p>
   * 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
   * <p>
   * 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;
  }

  /**
   * Configure HTTPS connection to trust all certificates
   * <p>
   * This method does nothing if the current request is not a HTTPS request
   *
   * @return this request
   * @throws HttpRequestException
   */
  public HttpRequest trustAllCerts() throws HttpRequestException {
    final HttpURLConnection connection = getConnection();
    if (connection instanceof HttpsURLConnection)
      ((HttpsURLConnection) connection)
          .setSSLSocketFactory(getTrustedFactory());
    return this;
  }

  /**
   * Configure HTTPS connection to trust all hosts using a custom
   * {@link HostnameVerifier} that always returns <code>true</code> for each
   * host verified
   * <p>
   * This method does nothing if the current request is not a HTTPS request
   *
   * @return this request
   */
  public HttpRequest trustAllHosts() {
    final HttpURLConnection connection = getConnection();
    if (connection instanceof HttpsURLConnection)
      ((HttpsURLConnection) connection)
          .setHostnameVerifier(getTrustedVerifier());
    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;
  }
}

02 Class HttpRequestLongOperation

Next you need to create a class that sends the image to HttpRequest. it should be called HttpRequestLongOperation.

HttpRequestLongOperation:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Base64;
import android.widget.TextView;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Map;


public class HttpRequestLongOperation extends AsyncTask<String, Void, String> {
    private Context context;
    private String inputUrl;
    private String stringMethod;
    private String stringSend = "";
    private String stringImg = "";
    private Map<String, String> mapSend  = new HashMap<String, String>();
    private TextView textViewJSON;
    private final TaskListener taskListener; // This is the reference to the associated listener


    public interface TaskListener {
        public void onFinished(String result);
    }

    /*- Constructor GET, SEND --------------------------------------------------------------- */
    public HttpRequestLongOperation(Context ctx, String url, String method, String send, TextView dynamicTextView, TaskListener listener) {
        context = ctx;
        inputUrl = url;
        stringMethod = method;
        stringSend = send;
        textViewJSON = dynamicTextView;
        this.taskListener = listener; // The listener reference is passed in through the constructor
    }
    public HttpRequestLongOperation(Context ctx, String url, String method, Map<String, String> data, TextView dynamicTextView, TaskListener listener) {
        context = ctx;
        inputUrl = url;
        stringMethod = method;
        mapSend = data;
        textViewJSON = dynamicTextView;
        this.taskListener = listener; // The listener reference is passed in through the constructor
    }
    public HttpRequestLongOperation(Context ctx, String url, String method, Map<String, String> data, String img, TextView dynamicTextView, TaskListener listener) {
        context = ctx;
        inputUrl = url;
        stringMethod = method;
        mapSend = data;
        stringImg = img;
        textViewJSON = dynamicTextView;
        this.taskListener = listener; // The listener reference is passed in through the constructor
    }
    public HttpRequestLongOperation(Context ctx, String url, String method, String send, TaskListener listener) {
        context = ctx;
        inputUrl = url;
        stringMethod = method;
        stringSend = send;
        this.taskListener = listener; // The listener reference is passed in through the constructor
    }


    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        textViewJSON.setText("Loading...");
    }


    @Override
    protected String doInBackground(String... params) {
        // Run methods
        String stringResponse ="";
        try {
            if(stringMethod.equals("get")) {
                stringResponse = HttpRequest.get(inputUrl).body();
            }
            else if(stringMethod.equals("post")){
                if(!(stringSend.equals(""))){
                    int intResponse = HttpRequest.post(inputUrl).send(stringSend).code();
                    stringResponse = "" + intResponse;
                }
                else {
                    try{
                        stringResponse = HttpRequest.post(inputUrl).form(mapSend).body();
                    }
                    catch (Exception e){
                        return e.toString();
                    }
                }
            }
            else if(stringMethod.equals("post_image")){


                // Method 1 - Base 64
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 3;
                Bitmap bm = BitmapFactory.decodeFile(stringImg, options);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    bm.compress(Bitmap.CompressFormat.JPEG, 40, baos);
                } catch (Exception compresse) {
                    Toast.makeText(context, "Compress error: " + compresse.toString(), Toast.LENGTH_LONG).show();
                }
                byte[] byteImage_photo = baos.toByteArray(); // bitmap object

                String encodedImage = Base64.encodeToString(byteImage_photo,Base64.DEFAULT); //generate base64 string of image


                mapSend.put("inp_image_base", encodedImage);
                stringResponse = HttpRequest.post(inputUrl).form(mapSend).body();

            } // post_image
        }
        catch(Exception e){
            return e.toString();
        }
        return stringResponse;
    }

    @Override
    protected void onPostExecute(String result) {
        // Set text view with result string
        if(textViewJSON == null){
            Toast.makeText(context, "NULL", Toast.LENGTH_SHORT).show();
        }
        else {
            textViewJSON.setText(result);
        }
        // In onPostExecute we check if the listener is valid
        if(this.taskListener != null) {

            // And if it is we call the callback function on it.
            this.taskListener.onFinished(result);
        }
    }

    @Override
    protected void onProgressUpdate(Void... values) {}

}

03 FilePath

We need a class that can determine the file path of images.

FilePath:

import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

public class FilePath {


    /**
     * Method for return file path of Gallery image/ Document / Video / Audio
     *
     * @param context
     * @param uri
     * @return path of the selected image file from gallery
     */
    public static String getPath(final Context context, final Uri uri) {

        // check here to KITKAT or new version
        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {

            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/"
                            + split[1];
                }
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"),
                        Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] { split[1] };

                return getDataColumn(context, contentUri, selection,
                        selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {

            // Return the remote address
            if (isGooglePhotosUri(uri))
                return uri.getLastPathSegment();

            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context
     *            The context.
     * @param uri
     *            The Uri to query.
     * @param selection
     *            (Optional) Filter used in the query.
     * @param selectionArgs
     *            (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri,
                                       String selection, String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = { column };

        try {
            cursor = context.getContentResolver().query(uri, projection,
                    selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri
                .getAuthority());
    }

    /**
     * @param uri
     *            The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    public static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri
                .getAuthority());
    }
}

04 Mainifest

First we need to confirm that we got permissions. Add the following lines in the Manifest.

Mainifest:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

05 Design

Go to your design and add a imageview, button and textview. Design file is located at res / layout / activity_main.xml.

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.nettport.uploadimage.uploadimage.MainActivity">


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageViewImage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@mipmap/ic_launcher" />

        <Button
            android:id="@+id/buttonGallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/load_from_gallery" />

        <TextView
            android:id="@+id/textViewDynamicText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

06 MainActivity code

Go to your main activity and add its code.

MainActivity API variables:


    /* Api variables */
    String websiteURL   = "http://nettport.com/image_upload";
    String apiURL       = "http://nettport.com/image_upload/api"; // Without ending slash
    String apiPassword  = "qw2e3erty6uiop";

    /* Current image */
    String currentImagePath = "";
    String currentImage = "";

MainActivity check permission methods:

/*- Check permission Read ---------------------------------------------------------- */
// Pops up message to user for reading
private void checkPermissionRead(){
  int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
  if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
  != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (shouldShowRequestPermissionRationale(
      android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
      // Explain to the user why we need to read the contacts
    }

    requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
    MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);

    // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE is an
    // app-defined int constant that should be quite unique

    return;
  }
} // checkPermissionRead

/*- Check permission Write ---------------------------------------------------------- */
// Pops up message to user for writing
private void checkPermissionWrite(){
        int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
        if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            // Should we show an explanation?
            if (shouldShowRequestPermissionRationale(
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // Explain to the user why we need to read the contacts
            }

            requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);

            // MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE is an
            // app-defined int constant that should be quite unique

            return;
        }
} // checkPermissionWrite

MainActivity listener:

/*- Button Listener ------------------------------------------------------------- */
public void buttonListener() {
  // Load image button listener
  Button buttonGallery = (Button) findViewById(R.id.buttonGallery);
  buttonGallery.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
      Intent pickPhoto = new Intent(Intent.ACTION_PICK,
      android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
      startActivityForResult(pickPhoto , 1);//one can be replaced with any action code
    }
  });
}

MainActivity onActivityResult:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            Uri selectedImageUri = data.getData();

            // Set image
            //ImageView imageViewImage = (ImageView)findViewById(R.id.imageViewImage);
            //imageViewImage.setImageURI(selectedImageUri);

            // Save image
            String destinationFilename = FilePath.getPath(this, selectedImageUri);

            // Dynamic text
            TextView textViewDynamicText = (TextView) findViewById(R.id.textViewDynamicText); // Dynamic text

            // URL
            String urlToApi = apiURL + "/image_upload.php";


            // Toast
            //Toast.makeText(this, "ID:"  + currentRecipeId, Toast.LENGTH_LONG).show();

            // Data
            Map mapData = new HashMap();
            mapData.put("inp_api_password", apiPasswordId);

            HttpRequestLongOperation task = new HttpRequestLongOperation(this, urlToApi, "post_image", mapData, destinationFilename, textViewDynamicText, new HttpRequestLongOperation.TaskListener() {
                @Override
                public void onFinished(String result) {
                    // Do Something after the task has finished
                    imageUploadResult();
                }
            });
            task.execute();

        }
    }

MainActivity imageUploadResult:

public void imageUploadResult() {
        // Dynamic text
        TextView textViewDynamicText = (TextView)findViewById(R.id.textViewDynamicText);
        String dynamicText = textViewDynamicText.getText().toString();

        // Split
        int index = dynamicText.lastIndexOf('/');
        try {
            currentImagePath = dynamicText.substring(0, index);
        }
        catch (Exception e){
            Toast.makeText(this, "path: " + e.toString(), Toast.LENGTH_LONG).show();
        }
        try {
            currentImage = dynamicText.substring(index,dynamicText.length());
        }
        catch (Exception e){
            Toast.makeText(this, "image: " + e.toString(), Toast.LENGTH_LONG).show();
        }

        // Load new image
        // Todo: loadImage();

    } // imageUploadResult

MainActivity LoadImage:


    /*- Load image ------------------------------------------------------------------ */
    public void loadImage(){
        /*
        // Load image
        ImageView imageViewImage = (ImageView)findViewById(R.id.imageViewImage);

        if(!(currentImagePath.equals("")) && !(currentImage.equals(""))){

            String loadImage = websiteURL + "/" + currentImagePath + "/" + currentImage;
            new HttpRequestImageLoadTask(this, loadImage, imageViewImage).execute();

        }*/
    }

07 HttpRequestImageLoadTask:

HttpRequestImageLoadTask:

package com.nettport.cicolife.cicolife;

/**
 * Created by bruker on 09.08.2017.
 */

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpRequestImageLoadTask extends AsyncTask<Void, Void, Bitmap> {

    private String url;
    private ImageView imageView;
    private Context context;

    public HttpRequestImageLoadTask(Context ctx, String url, ImageView imageView) {
        this.context = ctx;
        this.url = url;
        this.imageView = imageView;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        try {
            URL urlConnection = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) urlConnection
                    .openConnection();
            connection.setDoInput(true);
            connection.connect();
            InputStream input = connection.getInputStream();
            Bitmap myBitmap = BitmapFactory.decodeStream(input);
            return myBitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        super.onPostExecute(result);
        imageView.setImageBitmap(result);
        imageView.setVisibility(View.VISIBLE);
    }

    public void checkIfCacheExists(Uri sourceuri){

    }


    public String savefile(Uri sourceuri) {
        String sourceFilename= FilePath.getPath(context, sourceuri);
        String destinationPath = android.os.Environment.getExternalStorageDirectory().getPath()+File.separatorChar+"/Upload";
        String destinationFilename = destinationPath+"/upload_me_";

        // Make dir
        File folder = new File(destinationPath);
        boolean success = true;
        if (!folder.exists()) {
            success = folder.mkdir();
        }
        if (success) {
        }


        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            bis = new BufferedInputStream(new FileInputStream(sourceFilename));
            bos = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
            byte[] buf = new byte[1024];
            bis.read(buf);
            do {
                bos.write(buf);
            } while(bis.read(buf) != -1);
            // Toast.makeText(this, "Saved to " + destinationFilename, Toast.LENGTH_LONG).show();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if (bis != null) bis.close();
                if (bos != null) bos.close();
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
        return destinationFilename;
    }

}

08 PHP Server Part

On your web host create the following files and folders:

folder.png image_upload
folder.png api
format-justify-fill.png image_upload.php
folder.png images

image_upload.php:

<?php
/*- User set variables ----------------------------------------------------------------- */
// Variables
$api_password = "qw2e3erty6uiop";


/*- Get extention ---------------------------------------------------------------------- */
function getExtension($str) {
	$i = strrpos($str,".");
	if (!$i) { return ""; } 
	$l = strlen($str) - $i;
	$ext = substr($str,$i+1,$l);
	return $ext;
}

/*- Script Start ----------------------------------------------------------------------- */

// API Password
if(isset($_POST['inp_api_password'])){
	$inp_api_password = $_POST['inp_api_password'];
	
	if($inp_api_password != "$api_password"){
		echo"Wrong API Password";
		die;
	}
}
else{
	echo"Missing API Password";
	die;
}


// Image
$img_code = $_POST['inp_image_base'];
$img_data = base64_decode($img_code);

$datetime = date("YmdHis");

$image_path = "../images/" . $datetime . ".jpg";
$extension = getExtension($image_path);
file_put_contents($image_path,$img_data);

// Convert it to PNG
if(file_exists("$image_path")){
	list($width,$height) = getimagesize($image_path);
	
	if($width == "" OR $height == ""){
		echo"getimagesize_failed";
		unlink("$image_path");
	}
	else{	
		$image_final_path = "../images/" . $datetime . ".png";

		// Image should be 1000 x dynamic
		if($width > 1000){
			$newwidth=1000;
			$newheight=($height/$width)*$newwidth; 
		}
		else{
			$newwidth = $width;
			$newheight = $height;
		}
		$tmp=imagecreatetruecolor($newwidth,$newheight);
						
		if($extension == "jpg" || $extension == "jpeg" ){
			$src = imagecreatefromjpeg($image_path);
		}
		else{
			$src = imagecreatefrompng($image_path);
		}

		imagecopyresampled($tmp,$src,0,0,0,0,$newwidth,$newheight, $width,$height);
		imagepng($tmp, $image_final_path);
		imagedestroy($tmp);

		// Unlink image path
		unlink("$image_path");

		// Give path
		echo"$image_final_path";
	}
}

?>

Add comment

avatar_blank_60.png

Comment:

Name:

Email:

Comments

avatar_blank_65.png

Tousif Akram

29 Jul 2018

Hallo sir
Can you show me your table structure of your php my admin part for this projects...
Thanks