mirror of
https://github.com/silkimen/cordova-plugin-advanced-http.git
synced 2026-04-24 00:00:03 +08:00
752b2cdcb7
- one HTTP request class implementing Runnable for all request methods - broken SSL cert handling
3096 lines
84 KiB
Java
3096 lines
84 KiB
Java
/*
|
|
* Copyright (c) 2014 Kevin Sawicki <kevinsawicki@gmail.com>
|
|
* modified by contributors of cordova-plugin-advanced-http
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
package com.silkimen.http;
|
|
|
|
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
|
import static java.net.HttpURLConnection.HTTP_CREATED;
|
|
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
|
|
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
|
|
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
|
|
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
|
|
import static java.net.HttpURLConnection.HTTP_OK;
|
|
import static java.net.Proxy.Type.HTTP;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.BufferedReader;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.Closeable;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.Flushable;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.PrintStream;
|
|
import java.io.Reader;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.io.Writer;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.MalformedURLException;
|
|
import java.net.Proxy;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.net.URL;
|
|
import java.net.URLEncoder;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.CharBuffer;
|
|
import java.nio.charset.Charset;
|
|
import java.nio.charset.CharsetEncoder;
|
|
import java.security.AccessController;
|
|
import java.security.GeneralSecurityException;
|
|
import java.security.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 = "\r\n";
|
|
|
|
private static final String[] EMPTY_STRINGS = new String[0];
|
|
|
|
private static String getValidCharset(final String charset) {
|
|
if (charset != null && charset.length() > 0)
|
|
return charset;
|
|
else
|
|
return CHARSET_UTF8;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public HttpRequest setSSLSocketFactory(SSLSocketFactory socketFactory) throws HttpRequestException {
|
|
final HttpURLConnection connection = getConnection();
|
|
if (connection instanceof HttpsURLConnection)
|
|
((HttpsURLConnection) connection).setSSLSocketFactory(socketFactory);
|
|
return this;
|
|
}
|
|
|
|
public HttpRequest setHostnameVerifier(HostnameVerifier verifier) {
|
|
final HttpURLConnection connection = getConnection();
|
|
if (connection instanceof HttpsURLConnection)
|
|
((HttpsURLConnection) connection).setHostnameVerifier(verifier);
|
|
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;
|
|
}
|
|
}
|