source: gs3-extensions/atea-nlp-tools/trunk/src/koreromaori-proxy/src/main/java/org/atea/nlptools/koreromaoriinterface/services/HttpRequestService.java@ 35239

Last change on this file since 35239 was 35239, checked in by davidb, 3 years ago

Adding proxy servlet for the Korero Maori Reo Tuhituhi API (initail commit)

File size: 87.4 KB
Line 
1/*
2 * Copyright (c) 2014 Kevin Sawicki <[email protected]>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to
6 * deal in the Software without restriction, including without limitation the
7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 * sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 * IN THE SOFTWARE.
21 *
22 * Obtained from https://github.com/kevinsawicki/http-request
23 */
24package org.atea.nlptools.koreromaoriinterface.services;
25
26import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
27import static java.net.HttpURLConnection.HTTP_CREATED;
28import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
29import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
30import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
31import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
32import static java.net.HttpURLConnection.HTTP_OK;
33import static java.net.Proxy.Type.HTTP;
34
35import java.io.BufferedInputStream;
36import java.io.BufferedOutputStream;
37import java.io.BufferedReader;
38import java.io.ByteArrayInputStream;
39import java.io.ByteArrayOutputStream;
40import java.io.Closeable;
41import java.io.File;
42import java.io.FileInputStream;
43import java.io.FileNotFoundException;
44import java.io.FileOutputStream;
45import java.io.Flushable;
46import java.io.IOException;
47import java.io.InputStream;
48import java.io.InputStreamReader;
49import java.io.OutputStream;
50import java.io.OutputStreamWriter;
51import java.io.PrintStream;
52import java.io.Reader;
53import java.io.UnsupportedEncodingException;
54import java.io.Writer;
55import java.net.HttpURLConnection;
56import java.net.InetSocketAddress;
57import java.net.MalformedURLException;
58import java.net.Proxy;
59import java.net.URI;
60import java.net.URISyntaxException;
61import java.net.URL;
62import java.net.URLEncoder;
63import java.nio.ByteBuffer;
64import java.nio.CharBuffer;
65import java.nio.charset.Charset;
66import java.nio.charset.CharsetEncoder;
67import java.security.AccessController;
68import java.security.GeneralSecurityException;
69import java.security.PrivilegedAction;
70import java.security.SecureRandom;
71import java.security.cert.X509Certificate;
72import java.util.ArrayList;
73import java.util.Arrays;
74import java.util.Collections;
75import java.util.Iterator;
76import java.util.LinkedHashMap;
77import java.util.List;
78import java.util.Map;
79import java.util.Map.Entry;
80import java.util.concurrent.Callable;
81import java.util.concurrent.atomic.AtomicInteger;
82import java.util.concurrent.atomic.AtomicReference;
83import java.util.zip.GZIPInputStream;
84
85import javax.net.ssl.HostnameVerifier;
86import javax.net.ssl.HttpsURLConnection;
87import javax.net.ssl.SSLContext;
88import javax.net.ssl.SSLSession;
89import javax.net.ssl.SSLSocketFactory;
90import javax.net.ssl.TrustManager;
91import javax.net.ssl.X509TrustManager;
92
93/**
94 * A fluid interface for making HTTP requests using an underlying
95 * {@link HttpURLConnection} (or sub-class).
96 * <p>
97 * Each instance supports making a single request and cannot be reused for
98 * further requests.
99 */
100public class HttpRequestService {
101
102 /**
103 * 'UTF-8' charset name
104 */
105 public static final String CHARSET_UTF8 = "UTF-8";
106
107 /**
108 * 'application/x-www-form-urlencoded' content type header value
109 */
110 public static final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
111
112 /**
113 * 'application/json' content type header value
114 */
115 public static final String CONTENT_TYPE_JSON = "application/json";
116
117 /**
118 * 'gzip' encoding header value
119 */
120 public static final String ENCODING_GZIP = "gzip";
121
122 /**
123 * 'Accept' header name
124 */
125 public static final String HEADER_ACCEPT = "Accept";
126
127 /**
128 * 'Accept-Charset' header name
129 */
130 public static final String HEADER_ACCEPT_CHARSET = "Accept-Charset";
131
132 /**
133 * 'Accept-Encoding' header name
134 */
135 public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
136
137 /**
138 * 'Authorization' header name
139 */
140 public static final String HEADER_AUTHORIZATION = "Authorization";
141
142 /**
143 * 'Cache-Control' header name
144 */
145 public static final String HEADER_CACHE_CONTROL = "Cache-Control";
146
147 /**
148 * 'Content-Encoding' header name
149 */
150 public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
151
152 /**
153 * 'Content-Length' header name
154 */
155 public static final String HEADER_CONTENT_LENGTH = "Content-Length";
156
157 /**
158 * 'Content-Type' header name
159 */
160 public static final String HEADER_CONTENT_TYPE = "Content-Type";
161
162 /**
163 * 'Date' header name
164 */
165 public static final String HEADER_DATE = "Date";
166
167 /**
168 * 'ETag' header name
169 */
170 public static final String HEADER_ETAG = "ETag";
171
172 /**
173 * 'Expires' header name
174 */
175 public static final String HEADER_EXPIRES = "Expires";
176
177 /**
178 * 'If-None-Match' header name
179 */
180 public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
181
182 /**
183 * 'Last-Modified' header name
184 */
185 public static final String HEADER_LAST_MODIFIED = "Last-Modified";
186
187 /**
188 * 'Location' header name
189 */
190 public static final String HEADER_LOCATION = "Location";
191
192 /**
193 * 'Proxy-Authorization' header name
194 */
195 public static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization";
196
197 /**
198 * 'Referer' header name
199 */
200 public static final String HEADER_REFERER = "Referer";
201
202 /**
203 * 'Server' header name
204 */
205 public static final String HEADER_SERVER = "Server";
206
207 /**
208 * 'User-Agent' header name
209 */
210 public static final String HEADER_USER_AGENT = "User-Agent";
211
212 /**
213 * 'DELETE' request method
214 */
215 public static final String METHOD_DELETE = "DELETE";
216
217 /**
218 * 'GET' request method
219 */
220 public static final String METHOD_GET = "GET";
221
222 /**
223 * 'HEAD' request method
224 */
225 public static final String METHOD_HEAD = "HEAD";
226
227 /**
228 * 'OPTIONS' options method
229 */
230 public static final String METHOD_OPTIONS = "OPTIONS";
231
232 /**
233 * 'POST' request method
234 */
235 public static final String METHOD_POST = "POST";
236
237 /**
238 * 'PUT' request method
239 */
240 public static final String METHOD_PUT = "PUT";
241
242 /**
243 * 'TRACE' request method
244 */
245 public static final String METHOD_TRACE = "TRACE";
246
247 /**
248 * 'charset' header value parameter
249 */
250 public static final String PARAM_CHARSET = "charset";
251
252 private static final String BOUNDARY = "00content0boundary00";
253
254 private static final String CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary="
255 + BOUNDARY;
256
257 private static final String CRLF = "\r\n";
258
259 private static final String[] EMPTY_STRINGS = new String[0];
260
261 private static SSLSocketFactory TRUSTED_FACTORY;
262
263 private static HostnameVerifier TRUSTED_VERIFIER;
264
265 private static String getValidCharset(final String charset) {
266 if (charset != null && charset.length() > 0)
267 return charset;
268 else
269 return CHARSET_UTF8;
270 }
271
272 private static SSLSocketFactory getTrustedFactory()
273 throws HttpRequestException {
274 if (TRUSTED_FACTORY == null) {
275 final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
276
277 public X509Certificate[] getAcceptedIssuers() {
278 return new X509Certificate[0];
279 }
280
281 public void checkClientTrusted(X509Certificate[] chain, String authType) {
282 // Intentionally left blank
283 }
284
285 public void checkServerTrusted(X509Certificate[] chain, String authType) {
286 // Intentionally left blank
287 }
288 } };
289 try {
290 SSLContext context = SSLContext.getInstance("TLS");
291 context.init(null, trustAllCerts, new SecureRandom());
292 TRUSTED_FACTORY = context.getSocketFactory();
293 } catch (GeneralSecurityException e) {
294 IOException ioException = new IOException(
295 "Security exception configuring SSL context");
296 ioException.initCause(e);
297 throw new HttpRequestException(ioException);
298 }
299 }
300
301 return TRUSTED_FACTORY;
302 }
303
304 private static HostnameVerifier getTrustedVerifier() {
305 if (TRUSTED_VERIFIER == null)
306 TRUSTED_VERIFIER = new HostnameVerifier() {
307
308 public boolean verify(String hostname, SSLSession session) {
309 return true;
310 }
311 };
312
313 return TRUSTED_VERIFIER;
314 }
315
316 private static StringBuilder addPathSeparator(final String baseUrl,
317 final StringBuilder result) {
318 // Add trailing slash if the base URL doesn't have any path segments.
319 //
320 // The following test is checking for the last slash not being part of
321 // the protocol to host separator: '://'.
322 if (baseUrl.indexOf(':') + 2 == baseUrl.lastIndexOf('/'))
323 result.append('/');
324 return result;
325 }
326
327 private static StringBuilder addParamPrefix(final String baseUrl,
328 final StringBuilder result) {
329 // Add '?' if missing and add '&' if params already exist in base url
330 final int queryStart = baseUrl.indexOf('?');
331 final int lastChar = result.length() - 1;
332 if (queryStart == -1)
333 result.append('?');
334 else if (queryStart < lastChar && baseUrl.charAt(lastChar) != '&')
335 result.append('&');
336 return result;
337 }
338
339 private static StringBuilder addParam(final Object key, Object value,
340 final StringBuilder result) {
341 if (value != null && value.getClass().isArray())
342 value = arrayToList(value);
343
344 if (value instanceof Iterable<?>) {
345 Iterator<?> iterator = ((Iterable<?>) value).iterator();
346 while (iterator.hasNext()) {
347 result.append(key);
348 result.append("[]=");
349 Object element = iterator.next();
350 if (element != null)
351 result.append(element);
352 if (iterator.hasNext())
353 result.append("&");
354 }
355 } else {
356 result.append(key);
357 result.append("=");
358 if (value != null)
359 result.append(value);
360 }
361
362 return result;
363 }
364
365 /**
366 * Creates {@link HttpURLConnection HTTP connections} for
367 * {@link URL urls}.
368 */
369 public interface ConnectionFactory {
370 /**
371 * Open an {@link HttpURLConnection} for the specified {@link URL}.
372 *
373 * @throws IOException
374 */
375 HttpURLConnection create(URL url) throws IOException;
376
377 /**
378 * Open an {@link HttpURLConnection} for the specified {@link URL}
379 * and {@link Proxy}.
380 *
381 * @throws IOException
382 */
383 HttpURLConnection create(URL url, Proxy proxy) throws IOException;
384
385 /**
386 * A {@link ConnectionFactory} which uses the built-in
387 * {@link URL#openConnection()}
388 */
389 ConnectionFactory DEFAULT = new ConnectionFactory() {
390 public HttpURLConnection create(URL url) throws IOException {
391 return (HttpURLConnection) url.openConnection();
392 }
393
394 public HttpURLConnection create(URL url, Proxy proxy) throws IOException {
395 return (HttpURLConnection) url.openConnection(proxy);
396 }
397 };
398 }
399
400 private static ConnectionFactory CONNECTION_FACTORY = ConnectionFactory.DEFAULT;
401
402 /**
403 * Specify the {@link ConnectionFactory} used to create new requests.
404 */
405 public static void setConnectionFactory(final ConnectionFactory connectionFactory) {
406 if (connectionFactory == null)
407 CONNECTION_FACTORY = ConnectionFactory.DEFAULT;
408 else
409 CONNECTION_FACTORY = connectionFactory;
410 }
411
412 /**
413 * Callback interface for reporting upload progress for a request.
414 */
415 public interface UploadProgress {
416 /**
417 * Callback invoked as data is uploaded by the request.
418 *
419 * @param uploaded The number of bytes already uploaded
420 * @param total The total number of bytes that will be uploaded or -1 if
421 * the length is unknown.
422 */
423 void onUpload(long uploaded, long total);
424
425 UploadProgress DEFAULT = new UploadProgress() {
426 public void onUpload(long uploaded, long total) {
427 }
428 };
429 }
430
431 /**
432 * <p>
433 * Encodes and decodes to and from Base64 notation.
434 * </p>
435 * <p>
436 * I am placing this code in the Public Domain. Do with it as you will. This
437 * software comes with no guarantees or warranties but with plenty of
438 * well-wishing instead! Please visit <a
439 * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically
440 * to check for updates or to contribute improvements.
441 * </p>
442 *
443 * @author Robert Harder
444 * @author [email protected]
445 * @version 2.3.7
446 */
447 public static class Base64 {
448
449 /** The equals sign (=) as a byte. */
450 private final static byte EQUALS_SIGN = (byte) '=';
451
452 /** Preferred encoding. */
453 private final static String PREFERRED_ENCODING = "US-ASCII";
454
455 /** The 64 valid Base64 values. */
456 private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B',
457 (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H',
458 (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N',
459 (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T',
460 (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
461 (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
462 (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l',
463 (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r',
464 (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x',
465 (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
466 (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9',
467 (byte) '+', (byte) '/' };
468
469 /** Defeats instantiation. */
470 private Base64() {
471 }
472
473 /**
474 * <p>
475 * Encodes up to three bytes of the array <var>source</var> and writes the
476 * resulting four Base64 bytes to <var>destination</var>. The source and
477 * destination arrays can be manipulated anywhere along their length by
478 * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
479 * does not check to make sure your arrays are large enough to accomodate
480 * <var>srcOffset</var> + 3 for the <var>source</var> array or
481 * <var>destOffset</var> + 4 for the <var>destination</var> array. The
482 * actual number of significant bytes in your array is given by
483 * <var>numSigBytes</var>.
484 * </p>
485 * <p>
486 * This is the lowest level of the encoding methods with all possible
487 * parameters.
488 * </p>
489 *
490 * @param source
491 * the array to convert
492 * @param srcOffset
493 * the index where conversion begins
494 * @param numSigBytes
495 * the number of significant bytes in your array
496 * @param destination
497 * the array to hold the conversion
498 * @param destOffset
499 * the index where output will be put
500 * @return the <var>destination</var> array
501 * @since 1.3
502 */
503 private static byte[] encode3to4(byte[] source, int srcOffset,
504 int numSigBytes, byte[] destination, int destOffset) {
505
506 byte[] ALPHABET = _STANDARD_ALPHABET;
507
508 int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
509 | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
510 | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
511
512 switch (numSigBytes) {
513 case 3:
514 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
515 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
516 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
517 destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
518 return destination;
519
520 case 2:
521 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
522 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
523 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
524 destination[destOffset + 3] = EQUALS_SIGN;
525 return destination;
526
527 case 1:
528 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
529 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
530 destination[destOffset + 2] = EQUALS_SIGN;
531 destination[destOffset + 3] = EQUALS_SIGN;
532 return destination;
533
534 default:
535 return destination;
536 }
537 }
538
539 /**
540 * Encode string as a byte array in Base64 annotation.
541 *
542 * @param string
543 * @return The Base64-encoded data as a string
544 */
545 public static String encode(String string) {
546 byte[] bytes;
547 try {
548 bytes = string.getBytes(PREFERRED_ENCODING);
549 } catch (UnsupportedEncodingException e) {
550 bytes = string.getBytes();
551 }
552 return encodeBytes(bytes);
553 }
554
555 /**
556 * Encodes a byte array into Base64 notation.
557 *
558 * @param source
559 * The data to convert
560 * @return The Base64-encoded data as a String
561 * @throws NullPointerException
562 * if source array is null
563 * @throws IllegalArgumentException
564 * if source array, offset, or length are invalid
565 * @since 2.0
566 */
567 public static String encodeBytes(byte[] source) {
568 return encodeBytes(source, 0, source.length);
569 }
570
571 /**
572 * Encodes a byte array into Base64 notation.
573 *
574 * @param source
575 * The data to convert
576 * @param off
577 * Offset in array where conversion should begin
578 * @param len
579 * Length of data to convert
580 * @return The Base64-encoded data as a String
581 * @throws NullPointerException
582 * if source array is null
583 * @throws IllegalArgumentException
584 * if source array, offset, or length are invalid
585 * @since 2.0
586 */
587 public static String encodeBytes(byte[] source, int off, int len) {
588 byte[] encoded = encodeBytesToBytes(source, off, len);
589 try {
590 return new String(encoded, PREFERRED_ENCODING);
591 } catch (UnsupportedEncodingException uue) {
592 return new String(encoded);
593 }
594 }
595
596 /**
597 * Similar to {@link #encodeBytes(byte[], int, int)} but returns a byte
598 * array instead of instantiating a String. This is more efficient if you're
599 * working with I/O streams and have large data sets to encode.
600 *
601 *
602 * @param source
603 * The data to convert
604 * @param off
605 * Offset in array where conversion should begin
606 * @param len
607 * Length of data to convert
608 * @return The Base64-encoded data as a String if there is an error
609 * @throws NullPointerException
610 * if source array is null
611 * @throws IllegalArgumentException
612 * if source array, offset, or length are invalid
613 * @since 2.3.1
614 */
615 public static byte[] encodeBytesToBytes(byte[] source, int off, int len) {
616
617 if (source == null)
618 throw new NullPointerException("Cannot serialize a null array.");
619
620 if (off < 0)
621 throw new IllegalArgumentException("Cannot have negative offset: "
622 + off);
623
624 if (len < 0)
625 throw new IllegalArgumentException("Cannot have length offset: " + len);
626
627 if (off + len > source.length)
628 throw new IllegalArgumentException(
629 String
630 .format(
631 "Cannot have offset of %d and length of %d with array of length %d",
632 off, len, source.length));
633
634 // Bytes needed for actual encoding
635 int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0);
636
637 byte[] outBuff = new byte[encLen];
638
639 int d = 0;
640 int e = 0;
641 int len2 = len - 2;
642 for (; d < len2; d += 3, e += 4)
643 encode3to4(source, d + off, 3, outBuff, e);
644
645 if (d < len) {
646 encode3to4(source, d + off, len - d, outBuff, e);
647 e += 4;
648 }
649
650 if (e <= outBuff.length - 1) {
651 byte[] finalOut = new byte[e];
652 System.arraycopy(outBuff, 0, finalOut, 0, e);
653 return finalOut;
654 } else
655 return outBuff;
656 }
657 }
658
659 /**
660 * HTTP request exception whose cause is always an {@link IOException}
661 */
662 public static class HttpRequestException extends RuntimeException {
663
664 private static final long serialVersionUID = -1170466989781746231L;
665
666 /**
667 * Create a new HttpRequestException with the given cause
668 *
669 * @param cause
670 */
671 public HttpRequestException(final IOException cause) {
672 super(cause);
673 }
674
675 /**
676 * Get {@link IOException} that triggered this request exception
677 *
678 * @return {@link IOException} cause
679 */
680 @Override
681 public IOException getCause() {
682 return (IOException) super.getCause();
683 }
684 }
685
686 /**
687 * Operation that handles executing a callback once complete and handling
688 * nested exceptions
689 *
690 * @param <V>
691 */
692 protected static abstract class Operation<V> implements Callable<V> {
693
694 /**
695 * Run operation
696 *
697 * @return result
698 * @throws HttpRequestException
699 * @throws IOException
700 */
701 protected abstract V run() throws HttpRequestException, IOException;
702
703 /**
704 * Operation complete callback
705 *
706 * @throws IOException
707 */
708 protected abstract void done() throws IOException;
709
710 public V call() throws HttpRequestException {
711 boolean thrown = false;
712 try {
713 return run();
714 } catch (HttpRequestException e) {
715 thrown = true;
716 throw e;
717 } catch (IOException e) {
718 thrown = true;
719 throw new HttpRequestException(e);
720 } finally {
721 try {
722 done();
723 } catch (IOException e) {
724 if (!thrown)
725 throw new HttpRequestException(e);
726 }
727 }
728 }
729 }
730
731 /**
732 * Class that ensures a {@link Closeable} gets closed with proper exception
733 * handling.
734 *
735 * @param <V>
736 */
737 protected static abstract class CloseOperation<V> extends Operation<V> {
738
739 private final Closeable closeable;
740
741 private final boolean ignoreCloseExceptions;
742
743 /**
744 * Create closer for operation
745 *
746 * @param closeable
747 * @param ignoreCloseExceptions
748 */
749 protected CloseOperation(final Closeable closeable,
750 final boolean ignoreCloseExceptions) {
751 this.closeable = closeable;
752 this.ignoreCloseExceptions = ignoreCloseExceptions;
753 }
754
755 @Override
756 protected void done() throws IOException {
757 if (closeable instanceof Flushable)
758 ((Flushable) closeable).flush();
759 if (ignoreCloseExceptions)
760 try {
761 closeable.close();
762 } catch (IOException e) {
763 // Ignored
764 }
765 else
766 closeable.close();
767 }
768 }
769
770 /**
771 * Class that and ensures a {@link Flushable} gets flushed with proper
772 * exception handling.
773 *
774 * @param <V>
775 */
776 protected static abstract class FlushOperation<V> extends Operation<V> {
777
778 private final Flushable flushable;
779
780 /**
781 * Create flush operation
782 *
783 * @param flushable
784 */
785 protected FlushOperation(final Flushable flushable) {
786 this.flushable = flushable;
787 }
788
789 @Override
790 protected void done() throws IOException {
791 flushable.flush();
792 }
793 }
794
795 /**
796 * Request output stream
797 */
798 public static class RequestOutputStream extends BufferedOutputStream {
799
800 private final CharsetEncoder encoder;
801
802 /**
803 * Create request output stream
804 *
805 * @param stream
806 * @param charset
807 * @param bufferSize
808 */
809 public RequestOutputStream(final OutputStream stream, final String charset,
810 final int bufferSize) {
811 super(stream, bufferSize);
812
813 encoder = Charset.forName(getValidCharset(charset)).newEncoder();
814 }
815
816 /**
817 * Write string to stream
818 *
819 * @param value
820 * @return this stream
821 * @throws IOException
822 */
823 public RequestOutputStream write(final String value) throws IOException {
824 final ByteBuffer bytes = encoder.encode(CharBuffer.wrap(value));
825
826 super.write(bytes.array(), 0, bytes.limit());
827
828 return this;
829 }
830 }
831
832 /**
833 * Represents array of any type as list of objects so we can easily iterate over it
834 * @param array of elements
835 * @return list with the same elements
836 */
837 private static List<Object> arrayToList(final Object array) {
838 if (array instanceof Object[])
839 return Arrays.asList((Object[]) array);
840
841 List<Object> result = new ArrayList<Object>();
842 // Arrays of the primitive types can't be cast to array of Object, so this:
843 if (array instanceof int[])
844 for (int value : (int[]) array) result.add(value);
845 else if (array instanceof boolean[])
846 for (boolean value : (boolean[]) array) result.add(value);
847 else if (array instanceof long[])
848 for (long value : (long[]) array) result.add(value);
849 else if (array instanceof float[])
850 for (float value : (float[]) array) result.add(value);
851 else if (array instanceof double[])
852 for (double value : (double[]) array) result.add(value);
853 else if (array instanceof short[])
854 for (short value : (short[]) array) result.add(value);
855 else if (array instanceof byte[])
856 for (byte value : (byte[]) array) result.add(value);
857 else if (array instanceof char[])
858 for (char value : (char[]) array) result.add(value);
859 return result;
860 }
861
862 /**
863 * Encode the given URL as an ASCII {@link String}
864 * <p>
865 * This method ensures the path and query segments of the URL are properly
866 * encoded such as ' ' characters being encoded to '%20' or any UTF-8
867 * characters that are non-ASCII. No encoding of URLs is done by default by
868 * the {@link HttpRequestService} constructors and so if URL encoding is needed this
869 * method should be called before calling the {@link HttpRequestService} constructor.
870 *
871 * @param url
872 * @return encoded URL
873 * @throws HttpRequestException
874 */
875 public static String encode(final CharSequence url)
876 throws HttpRequestException {
877 URL parsed;
878 try {
879 parsed = new URL(url.toString());
880 } catch (IOException e) {
881 throw new HttpRequestException(e);
882 }
883
884 String host = parsed.getHost();
885 int port = parsed.getPort();
886 if (port != -1)
887 host = host + ':' + Integer.toString(port);
888
889 try {
890 String encoded = new URI(parsed.getProtocol(), host, parsed.getPath(),
891 parsed.getQuery(), null).toASCIIString();
892 int paramsStart = encoded.indexOf('?');
893 if (paramsStart > 0 && paramsStart + 1 < encoded.length())
894 encoded = encoded.substring(0, paramsStart + 1)
895 + encoded.substring(paramsStart + 1).replace("+", "%2B");
896 return encoded;
897 } catch (URISyntaxException e) {
898 IOException io = new IOException("Parsing URI failed");
899 io.initCause(e);
900 throw new HttpRequestException(io);
901 }
902 }
903
904 /**
905 * Append given map as query parameters to the base URL
906 * <p>
907 * Each map entry's key will be a parameter name and the value's
908 * {@link Object#toString()} will be the parameter value.
909 *
910 * @param url
911 * @param params
912 * @return URL with appended query params
913 */
914 public static String append(final CharSequence url, final Map<?, ?> params) {
915 final String baseUrl = url.toString();
916 if (params == null || params.isEmpty())
917 return baseUrl;
918
919 final StringBuilder result = new StringBuilder(baseUrl);
920
921 addPathSeparator(baseUrl, result);
922 addParamPrefix(baseUrl, result);
923
924 Entry<?, ?> entry;
925 Iterator<?> iterator = params.entrySet().iterator();
926 entry = (Entry<?, ?>) iterator.next();
927 addParam(entry.getKey().toString(), entry.getValue(), result);
928
929 while (iterator.hasNext()) {
930 result.append('&');
931 entry = (Entry<?, ?>) iterator.next();
932 addParam(entry.getKey().toString(), entry.getValue(), result);
933 }
934
935 return result.toString();
936 }
937
938 /**
939 * Append given name/value pairs as query parameters to the base URL
940 * <p>
941 * The params argument is interpreted as a sequence of name/value pairs so the
942 * given number of params must be divisible by 2.
943 *
944 * @param url
945 * @param params
946 * name/value pairs
947 * @return URL with appended query params
948 */
949 public static String append(final CharSequence url, final Object... params) {
950 final String baseUrl = url.toString();
951 if (params == null || params.length == 0)
952 return baseUrl;
953
954 if (params.length % 2 != 0)
955 throw new IllegalArgumentException(
956 "Must specify an even number of parameter names/values");
957
958 final StringBuilder result = new StringBuilder(baseUrl);
959
960 addPathSeparator(baseUrl, result);
961 addParamPrefix(baseUrl, result);
962
963 addParam(params[0], params[1], result);
964
965 for (int i = 2; i < params.length; i += 2) {
966 result.append('&');
967 addParam(params[i], params[i + 1], result);
968 }
969
970 return result.toString();
971 }
972
973 /**
974 * Start a 'GET' request to the given URL
975 *
976 * @param url
977 * @return request
978 * @throws HttpRequestException
979 */
980 public static HttpRequestService get(final CharSequence url)
981 throws HttpRequestException {
982 return new HttpRequestService(url, METHOD_GET);
983 }
984
985 /**
986 * Start a 'GET' request to the given URL
987 *
988 * @param url
989 * @return request
990 * @throws HttpRequestException
991 */
992 public static HttpRequestService get(final URL url) throws HttpRequestException {
993 return new HttpRequestService(url, METHOD_GET);
994 }
995
996 /**
997 * Start a 'GET' request to the given URL along with the query params
998 *
999 * @param baseUrl
1000 * @param params
1001 * The query parameters to include as part of the baseUrl
1002 * @param encode
1003 * true to encode the full URL
1004 *
1005 * @see #append(CharSequence, Map)
1006 * @see #encode(CharSequence)
1007 *
1008 * @return request
1009 */
1010 public static HttpRequestService get(final CharSequence baseUrl,
1011 final Map<?, ?> params, final boolean encode) {
1012 String url = append(baseUrl, params);
1013 return get(encode ? encode(url) : url);
1014 }
1015
1016 /**
1017 * Start a 'GET' request to the given URL along with the query params
1018 *
1019 * @param baseUrl
1020 * @param encode
1021 * true to encode the full URL
1022 * @param params
1023 * the name/value query parameter pairs to include as part of the
1024 * baseUrl
1025 *
1026 * @see #append(CharSequence, Object...)
1027 * @see #encode(CharSequence)
1028 *
1029 * @return request
1030 */
1031 public static HttpRequestService get(final CharSequence baseUrl,
1032 final boolean encode, final Object... params) {
1033 String url = append(baseUrl, params);
1034 return get(encode ? encode(url) : url);
1035 }
1036
1037 /**
1038 * Start a 'POST' request to the given URL
1039 *
1040 * @param url
1041 * @return request
1042 * @throws HttpRequestException
1043 */
1044 public static HttpRequestService post(final CharSequence url)
1045 throws HttpRequestException {
1046 return new HttpRequestService(url, METHOD_POST);
1047 }
1048
1049 /**
1050 * Start a 'POST' request to the given URL
1051 *
1052 * @param url
1053 * @return request
1054 * @throws HttpRequestException
1055 */
1056 public static HttpRequestService post(final URL url) throws HttpRequestException {
1057 return new HttpRequestService(url, METHOD_POST);
1058 }
1059
1060 /**
1061 * Start a 'POST' request to the given URL along with the query params
1062 *
1063 * @param baseUrl
1064 * @param params
1065 * the query parameters to include as part of the baseUrl
1066 * @param encode
1067 * true to encode the full URL
1068 *
1069 * @see #append(CharSequence, Map)
1070 * @see #encode(CharSequence)
1071 *
1072 * @return request
1073 */
1074 public static HttpRequestService post(final CharSequence baseUrl,
1075 final Map<?, ?> params, final boolean encode) {
1076 String url = append(baseUrl, params);
1077 return post(encode ? encode(url) : url);
1078 }
1079
1080 /**
1081 * Start a 'POST' request to the given URL along with the query params
1082 *
1083 * @param baseUrl
1084 * @param encode
1085 * true to encode the full URL
1086 * @param params
1087 * the name/value query parameter pairs to include as part of the
1088 * baseUrl
1089 *
1090 * @see #append(CharSequence, Object...)
1091 * @see #encode(CharSequence)
1092 *
1093 * @return request
1094 */
1095 public static HttpRequestService post(final CharSequence baseUrl,
1096 final boolean encode, final Object... params) {
1097 String url = append(baseUrl, params);
1098 return post(encode ? encode(url) : url);
1099 }
1100
1101 /**
1102 * Start a 'PUT' request to the given URL
1103 *
1104 * @param url
1105 * @return request
1106 * @throws HttpRequestException
1107 */
1108 public static HttpRequestService put(final CharSequence url)
1109 throws HttpRequestException {
1110 return new HttpRequestService(url, METHOD_PUT);
1111 }
1112
1113 /**
1114 * Start a 'PUT' request to the given URL
1115 *
1116 * @param url
1117 * @return request
1118 * @throws HttpRequestException
1119 */
1120 public static HttpRequestService put(final URL url) throws HttpRequestException {
1121 return new HttpRequestService(url, METHOD_PUT);
1122 }
1123
1124 /**
1125 * Start a 'PUT' request to the given URL along with the query params
1126 *
1127 * @param baseUrl
1128 * @param params
1129 * the query parameters to include as part of the baseUrl
1130 * @param encode
1131 * true to encode the full URL
1132 *
1133 * @see #append(CharSequence, Map)
1134 * @see #encode(CharSequence)
1135 *
1136 * @return request
1137 */
1138 public static HttpRequestService put(final CharSequence baseUrl,
1139 final Map<?, ?> params, final boolean encode) {
1140 String url = append(baseUrl, params);
1141 return put(encode ? encode(url) : url);
1142 }
1143
1144 /**
1145 * Start a 'PUT' request to the given URL along with the query params
1146 *
1147 * @param baseUrl
1148 * @param encode
1149 * true to encode the full URL
1150 * @param params
1151 * the name/value query parameter pairs to include as part of the
1152 * baseUrl
1153 *
1154 * @see #append(CharSequence, Object...)
1155 * @see #encode(CharSequence)
1156 *
1157 * @return request
1158 */
1159 public static HttpRequestService put(final CharSequence baseUrl,
1160 final boolean encode, final Object... params) {
1161 String url = append(baseUrl, params);
1162 return put(encode ? encode(url) : url);
1163 }
1164
1165 /**
1166 * Start a 'DELETE' request to the given URL
1167 *
1168 * @param url
1169 * @return request
1170 * @throws HttpRequestException
1171 */
1172 public static HttpRequestService delete(final CharSequence url)
1173 throws HttpRequestException {
1174 return new HttpRequestService(url, METHOD_DELETE);
1175 }
1176
1177 /**
1178 * Start a 'DELETE' request to the given URL
1179 *
1180 * @param url
1181 * @return request
1182 * @throws HttpRequestException
1183 */
1184 public static HttpRequestService delete(final URL url) throws HttpRequestException {
1185 return new HttpRequestService(url, METHOD_DELETE);
1186 }
1187
1188 /**
1189 * Start a 'DELETE' request to the given URL along with the query params
1190 *
1191 * @param baseUrl
1192 * @param params
1193 * The query parameters to include as part of the baseUrl
1194 * @param encode
1195 * true to encode the full URL
1196 *
1197 * @see #append(CharSequence, Map)
1198 * @see #encode(CharSequence)
1199 *
1200 * @return request
1201 */
1202 public static HttpRequestService delete(final CharSequence baseUrl,
1203 final Map<?, ?> params, final boolean encode) {
1204 String url = append(baseUrl, params);
1205 return delete(encode ? encode(url) : url);
1206 }
1207
1208 /**
1209 * Start a 'DELETE' request to the given URL along with the query params
1210 *
1211 * @param baseUrl
1212 * @param encode
1213 * true to encode the full URL
1214 * @param params
1215 * the name/value query parameter pairs to include as part of the
1216 * baseUrl
1217 *
1218 * @see #append(CharSequence, Object...)
1219 * @see #encode(CharSequence)
1220 *
1221 * @return request
1222 */
1223 public static HttpRequestService delete(final CharSequence baseUrl,
1224 final boolean encode, final Object... params) {
1225 String url = append(baseUrl, params);
1226 return delete(encode ? encode(url) : url);
1227 }
1228
1229 /**
1230 * Start a 'HEAD' request to the given URL
1231 *
1232 * @param url
1233 * @return request
1234 * @throws HttpRequestException
1235 */
1236 public static HttpRequestService head(final CharSequence url)
1237 throws HttpRequestException {
1238 return new HttpRequestService(url, METHOD_HEAD);
1239 }
1240
1241 /**
1242 * Start a 'HEAD' request to the given URL
1243 *
1244 * @param url
1245 * @return request
1246 * @throws HttpRequestException
1247 */
1248 public static HttpRequestService head(final URL url) throws HttpRequestException {
1249 return new HttpRequestService(url, METHOD_HEAD);
1250 }
1251
1252 /**
1253 * Start a 'HEAD' request to the given URL along with the query params
1254 *
1255 * @param baseUrl
1256 * @param params
1257 * The query parameters to include as part of the baseUrl
1258 * @param encode
1259 * true to encode the full URL
1260 *
1261 * @see #append(CharSequence, Map)
1262 * @see #encode(CharSequence)
1263 *
1264 * @return request
1265 */
1266 public static HttpRequestService head(final CharSequence baseUrl,
1267 final Map<?, ?> params, final boolean encode) {
1268 String url = append(baseUrl, params);
1269 return head(encode ? encode(url) : url);
1270 }
1271
1272 /**
1273 * Start a 'GET' request to the given URL along with the query params
1274 *
1275 * @param baseUrl
1276 * @param encode
1277 * true to encode the full URL
1278 * @param params
1279 * the name/value query parameter pairs to include as part of the
1280 * baseUrl
1281 *
1282 * @see #append(CharSequence, Object...)
1283 * @see #encode(CharSequence)
1284 *
1285 * @return request
1286 */
1287 public static HttpRequestService head(final CharSequence baseUrl,
1288 final boolean encode, final Object... params) {
1289 String url = append(baseUrl, params);
1290 return head(encode ? encode(url) : url);
1291 }
1292
1293 /**
1294 * Start an 'OPTIONS' request to the given URL
1295 *
1296 * @param url
1297 * @return request
1298 * @throws HttpRequestException
1299 */
1300 public static HttpRequestService options(final CharSequence url)
1301 throws HttpRequestException {
1302 return new HttpRequestService(url, METHOD_OPTIONS);
1303 }
1304
1305 /**
1306 * Start an 'OPTIONS' request to the given URL
1307 *
1308 * @param url
1309 * @return request
1310 * @throws HttpRequestException
1311 */
1312 public static HttpRequestService options(final URL url) throws HttpRequestException {
1313 return new HttpRequestService(url, METHOD_OPTIONS);
1314 }
1315
1316 /**
1317 * Start a 'TRACE' request to the given URL
1318 *
1319 * @param url
1320 * @return request
1321 * @throws HttpRequestException
1322 */
1323 public static HttpRequestService trace(final CharSequence url)
1324 throws HttpRequestException {
1325 return new HttpRequestService(url, METHOD_TRACE);
1326 }
1327
1328 /**
1329 * Start a 'TRACE' request to the given URL
1330 *
1331 * @param url
1332 * @return request
1333 * @throws HttpRequestException
1334 */
1335 public static HttpRequestService trace(final URL url) throws HttpRequestException {
1336 return new HttpRequestService(url, METHOD_TRACE);
1337 }
1338
1339 /**
1340 * Set the 'http.keepAlive' property to the given value.
1341 * <p>
1342 * This setting will apply to all requests.
1343 *
1344 * @param keepAlive
1345 */
1346 public static void keepAlive(final boolean keepAlive) {
1347 setProperty("http.keepAlive", Boolean.toString(keepAlive));
1348 }
1349
1350 /**
1351 * Set the 'http.maxConnections' property to the given value.
1352 * <p>
1353 * This setting will apply to all requests.
1354 *
1355 * @param maxConnections
1356 */
1357 public static void maxConnections(final int maxConnections) {
1358 setProperty("http.maxConnections", Integer.toString(maxConnections));
1359 }
1360
1361 /**
1362 * Set the 'http.proxyHost' and 'https.proxyHost' properties to the given host
1363 * value.
1364 * <p>
1365 * This setting will apply to all requests.
1366 *
1367 * @param host
1368 */
1369 public static void proxyHost(final String host) {
1370 setProperty("http.proxyHost", host);
1371 setProperty("https.proxyHost", host);
1372 }
1373
1374 /**
1375 * Set the 'http.proxyPort' and 'https.proxyPort' properties to the given port
1376 * number.
1377 * <p>
1378 * This setting will apply to all requests.
1379 *
1380 * @param port
1381 */
1382 public static void proxyPort(final int port) {
1383 final String portValue = Integer.toString(port);
1384 setProperty("http.proxyPort", portValue);
1385 setProperty("https.proxyPort", portValue);
1386 }
1387
1388 /**
1389 * Set the 'http.nonProxyHosts' property to the given host values.
1390 * <p>
1391 * Hosts will be separated by a '|' character.
1392 * <p>
1393 * This setting will apply to all requests.
1394 *
1395 * @param hosts
1396 */
1397 public static void nonProxyHosts(final String... hosts) {
1398 if (hosts != null && hosts.length > 0) {
1399 StringBuilder separated = new StringBuilder();
1400 int last = hosts.length - 1;
1401 for (int i = 0; i < last; i++)
1402 separated.append(hosts[i]).append('|');
1403 separated.append(hosts[last]);
1404 setProperty("http.nonProxyHosts", separated.toString());
1405 } else
1406 setProperty("http.nonProxyHosts", null);
1407 }
1408
1409 /**
1410 * Set property to given value.
1411 * <p>
1412 * Specifying a null value will cause the property to be cleared
1413 *
1414 * @param name
1415 * @param value
1416 * @return previous value
1417 */
1418 private static String setProperty(final String name, final String value) {
1419 final PrivilegedAction<String> action;
1420 if (value != null)
1421 action = new PrivilegedAction<String>() {
1422
1423 public String run() {
1424 return System.setProperty(name, value);
1425 }
1426 };
1427 else
1428 action = new PrivilegedAction<String>() {
1429
1430 public String run() {
1431 return System.clearProperty(name);
1432 }
1433 };
1434 return AccessController.doPrivileged(action);
1435 }
1436
1437 private HttpURLConnection connection = null;
1438
1439 private final URL url;
1440
1441 private final String requestMethod;
1442
1443 private RequestOutputStream output;
1444
1445 private boolean multipart;
1446
1447 private boolean form;
1448
1449 private boolean ignoreCloseExceptions = true;
1450
1451 private boolean uncompress = false;
1452
1453 private int bufferSize = 8192;
1454
1455 private long totalSize = -1;
1456
1457 private long totalWritten = 0;
1458
1459 private String httpProxyHost;
1460
1461 private int httpProxyPort;
1462
1463 private UploadProgress progress = UploadProgress.DEFAULT;
1464
1465 /**
1466 * Create HTTP connection wrapper
1467 *
1468 * @param url Remote resource URL.
1469 * @param method HTTP request method (e.g., "GET", "POST").
1470 * @throws HttpRequestException
1471 */
1472 public HttpRequestService(final CharSequence url, final String method)
1473 throws HttpRequestException {
1474 try {
1475 this.url = new URL(url.toString());
1476 } catch (MalformedURLException e) {
1477 throw new HttpRequestException(e);
1478 }
1479 this.requestMethod = method;
1480 }
1481
1482 /**
1483 * Create HTTP connection wrapper
1484 *
1485 * @param url Remote resource URL.
1486 * @param method HTTP request method (e.g., "GET", "POST").
1487 * @throws HttpRequestException
1488 */
1489 public HttpRequestService(final URL url, final String method)
1490 throws HttpRequestException {
1491 this.url = url;
1492 this.requestMethod = method;
1493 }
1494
1495 private Proxy createProxy() {
1496 return new Proxy(HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort));
1497 }
1498
1499 private HttpURLConnection createConnection() {
1500 try {
1501 final HttpURLConnection connection;
1502 if (httpProxyHost != null)
1503 connection = CONNECTION_FACTORY.create(url, createProxy());
1504 else
1505 connection = CONNECTION_FACTORY.create(url);
1506 connection.setRequestMethod(requestMethod);
1507 return connection;
1508 } catch (IOException e) {
1509 throw new HttpRequestException(e);
1510 }
1511 }
1512
1513 @Override
1514 public String toString() {
1515 return method() + ' ' + url();
1516 }
1517
1518 /**
1519 * Get underlying connection
1520 *
1521 * @return connection
1522 */
1523 public HttpURLConnection getConnection() {
1524 if (connection == null)
1525 connection = createConnection();
1526 return connection;
1527 }
1528
1529 /**
1530 * Set whether or not to ignore exceptions that occur from calling
1531 * {@link Closeable#close()}
1532 * <p>
1533 * The default value of this setting is <code>true</code>
1534 *
1535 * @param ignore
1536 * @return this request
1537 */
1538 public HttpRequestService ignoreCloseExceptions(final boolean ignore) {
1539 ignoreCloseExceptions = ignore;
1540 return this;
1541 }
1542
1543 /**
1544 * Get whether or not exceptions thrown by {@link Closeable#close()} are
1545 * ignored
1546 *
1547 * @return true if ignoring, false if throwing
1548 */
1549 public boolean ignoreCloseExceptions() {
1550 return ignoreCloseExceptions;
1551 }
1552
1553 /**
1554 * Get the status code of the response
1555 *
1556 * @return the response code
1557 * @throws HttpRequestException
1558 */
1559 public int code() throws HttpRequestException {
1560 try {
1561 closeOutput();
1562 return getConnection().getResponseCode();
1563 } catch (IOException e) {
1564 throw new HttpRequestException(e);
1565 }
1566 }
1567
1568 /**
1569 * Set the value of the given {@link AtomicInteger} to the status code of the
1570 * response
1571 *
1572 * @param output
1573 * @return this request
1574 * @throws HttpRequestException
1575 */
1576 public HttpRequestService code(final AtomicInteger output)
1577 throws HttpRequestException {
1578 output.set(code());
1579 return this;
1580 }
1581
1582 /**
1583 * Is the response code a 200 OK?
1584 *
1585 * @return true if 200, false otherwise
1586 * @throws HttpRequestException
1587 */
1588 public boolean ok() throws HttpRequestException {
1589 return HTTP_OK == code();
1590 }
1591
1592 /**
1593 * Is the response code a 201 Created?
1594 *
1595 * @return true if 201, false otherwise
1596 * @throws HttpRequestException
1597 */
1598 public boolean created() throws HttpRequestException {
1599 return HTTP_CREATED == code();
1600 }
1601
1602 /**
1603 * Is the response code a 204 No Content?
1604 *
1605 * @return true if 204, false otherwise
1606 * @throws HttpRequestException
1607 */
1608 public boolean noContent() throws HttpRequestException {
1609 return HTTP_NO_CONTENT == code();
1610 }
1611
1612 /**
1613 * Is the response code a 500 Internal Server Error?
1614 *
1615 * @return true if 500, false otherwise
1616 * @throws HttpRequestException
1617 */
1618 public boolean serverError() throws HttpRequestException {
1619 return HTTP_INTERNAL_ERROR == code();
1620 }
1621
1622 /**
1623 * Is the response code a 400 Bad Request?
1624 *
1625 * @return true if 400, false otherwise
1626 * @throws HttpRequestException
1627 */
1628 public boolean badRequest() throws HttpRequestException {
1629 return HTTP_BAD_REQUEST == code();
1630 }
1631
1632 /**
1633 * Is the response code a 404 Not Found?
1634 *
1635 * @return true if 404, false otherwise
1636 * @throws HttpRequestException
1637 */
1638 public boolean notFound() throws HttpRequestException {
1639 return HTTP_NOT_FOUND == code();
1640 }
1641
1642 /**
1643 * Is the response code a 304 Not Modified?
1644 *
1645 * @return true if 304, false otherwise
1646 * @throws HttpRequestException
1647 */
1648 public boolean notModified() throws HttpRequestException {
1649 return HTTP_NOT_MODIFIED == code();
1650 }
1651
1652 /**
1653 * Get status message of the response
1654 *
1655 * @return message
1656 * @throws HttpRequestException
1657 */
1658 public String message() throws HttpRequestException {
1659 try {
1660 closeOutput();
1661 return getConnection().getResponseMessage();
1662 } catch (IOException e) {
1663 throw new HttpRequestException(e);
1664 }
1665 }
1666
1667 /**
1668 * Disconnect the connection
1669 *
1670 * @return this request
1671 */
1672 public HttpRequestService disconnect() {
1673 getConnection().disconnect();
1674 return this;
1675 }
1676
1677 /**
1678 * Set chunked streaming mode to the given size
1679 *
1680 * @param size
1681 * @return this request
1682 */
1683 public HttpRequestService chunk(final int size) {
1684 getConnection().setChunkedStreamingMode(size);
1685 return this;
1686 }
1687
1688 /**
1689 * Set the size used when buffering and copying between streams
1690 * <p>
1691 * This size is also used for send and receive buffers created for both char
1692 * and byte arrays
1693 * <p>
1694 * The default buffer size is 8,192 bytes
1695 *
1696 * @param size
1697 * @return this request
1698 */
1699 public HttpRequestService bufferSize(final int size) {
1700 if (size < 1)
1701 throw new IllegalArgumentException("Size must be greater than zero");
1702 bufferSize = size;
1703 return this;
1704 }
1705
1706 /**
1707 * Get the configured buffer size
1708 * <p>
1709 * The default buffer size is 8,192 bytes
1710 *
1711 * @return buffer size
1712 */
1713 public int bufferSize() {
1714 return bufferSize;
1715 }
1716
1717 /**
1718 * Set whether or not the response body should be automatically uncompressed
1719 * when read from.
1720 * <p>
1721 * This will only affect requests that have the 'Content-Encoding' response
1722 * header set to 'gzip'.
1723 * <p>
1724 * This causes all receive methods to use a {@link GZIPInputStream} when
1725 * applicable so that higher level streams and readers can read the data
1726 * uncompressed.
1727 * <p>
1728 * Setting this option does not cause any request headers to be set
1729 * automatically so {@link #acceptGzipEncoding()} should be used in
1730 * conjunction with this setting to tell the server to gzip the response.
1731 *
1732 * @param uncompress
1733 * @return this request
1734 */
1735 public HttpRequestService uncompress(final boolean uncompress) {
1736 this.uncompress = uncompress;
1737 return this;
1738 }
1739
1740 /**
1741 * Create byte array output stream
1742 *
1743 * @return stream
1744 */
1745 protected ByteArrayOutputStream byteStream() {
1746 final int size = contentLength();
1747 if (size > 0)
1748 return new ByteArrayOutputStream(size);
1749 else
1750 return new ByteArrayOutputStream();
1751 }
1752
1753 /**
1754 * Get response as {@link String} in given character set
1755 * <p>
1756 * This will fall back to using the UTF-8 character set if the given charset
1757 * is null
1758 *
1759 * @param charset
1760 * @return string
1761 * @throws HttpRequestException
1762 */
1763 public String body(final String charset) throws HttpRequestException {
1764 final ByteArrayOutputStream output = byteStream();
1765 try {
1766 copy(buffer(), output);
1767 return output.toString(getValidCharset(charset));
1768 } catch (IOException e) {
1769 throw new HttpRequestException(e);
1770 }
1771 }
1772
1773 /**
1774 * Get response as {@link String} using character set returned from
1775 * {@link #charset()}
1776 *
1777 * @return string
1778 * @throws HttpRequestException
1779 */
1780 public String body() throws HttpRequestException {
1781 return body(charset());
1782 }
1783
1784 /**
1785 * Get the response body as a {@link String} and set it as the value of the
1786 * given reference.
1787 *
1788 * @param output
1789 * @return this request
1790 * @throws HttpRequestException
1791 */
1792 public HttpRequestService body(final AtomicReference<String> output) throws HttpRequestException {
1793 output.set(body());
1794 return this;
1795 }
1796
1797 /**
1798 * Get the response body as a {@link String} and set it as the value of the
1799 * given reference.
1800 *
1801 * @param output
1802 * @param charset
1803 * @return this request
1804 * @throws HttpRequestException
1805 */
1806 public HttpRequestService body(final AtomicReference<String> output, final String charset) throws HttpRequestException {
1807 output.set(body(charset));
1808 return this;
1809 }
1810
1811
1812 /**
1813 * Is the response body empty?
1814 *
1815 * @return true if the Content-Length response header is 0, false otherwise
1816 * @throws HttpRequestException
1817 */
1818 public boolean isBodyEmpty() throws HttpRequestException {
1819 return contentLength() == 0;
1820 }
1821
1822 /**
1823 * Get response as byte array
1824 *
1825 * @return byte array
1826 * @throws HttpRequestException
1827 */
1828 public byte[] bytes() throws HttpRequestException {
1829 final ByteArrayOutputStream output = byteStream();
1830 try {
1831 copy(buffer(), output);
1832 } catch (IOException e) {
1833 throw new HttpRequestException(e);
1834 }
1835 return output.toByteArray();
1836 }
1837
1838 /**
1839 * Get response in a buffered stream
1840 *
1841 * @see #bufferSize(int)
1842 * @return stream
1843 * @throws HttpRequestException
1844 */
1845 public BufferedInputStream buffer() throws HttpRequestException {
1846 return new BufferedInputStream(stream(), bufferSize);
1847 }
1848
1849 /**
1850 * Get stream to response body
1851 *
1852 * @return stream
1853 * @throws HttpRequestException
1854 */
1855 public InputStream stream() throws HttpRequestException {
1856 InputStream stream;
1857 if (code() < HTTP_BAD_REQUEST)
1858 try {
1859 stream = getConnection().getInputStream();
1860 } catch (IOException e) {
1861 throw new HttpRequestException(e);
1862 }
1863 else {
1864 stream = getConnection().getErrorStream();
1865 if (stream == null)
1866 try {
1867 stream = getConnection().getInputStream();
1868 } catch (IOException e) {
1869 if (contentLength() > 0)
1870 throw new HttpRequestException(e);
1871 else
1872 stream = new ByteArrayInputStream(new byte[0]);
1873 }
1874 }
1875
1876 if (!uncompress || !ENCODING_GZIP.equals(contentEncoding()))
1877 return stream;
1878 else
1879 try {
1880 return new GZIPInputStream(stream);
1881 } catch (IOException e) {
1882 throw new HttpRequestException(e);
1883 }
1884 }
1885
1886 /**
1887 * Get reader to response body using given character set.
1888 * <p>
1889 * This will fall back to using the UTF-8 character set if the given charset
1890 * is null
1891 *
1892 * @param charset
1893 * @return reader
1894 * @throws HttpRequestException
1895 */
1896 public InputStreamReader reader(final String charset)
1897 throws HttpRequestException {
1898 try {
1899 return new InputStreamReader(stream(), getValidCharset(charset));
1900 } catch (UnsupportedEncodingException e) {
1901 throw new HttpRequestException(e);
1902 }
1903 }
1904
1905 /**
1906 * Get reader to response body using the character set returned from
1907 * {@link #charset()}
1908 *
1909 * @return reader
1910 * @throws HttpRequestException
1911 */
1912 public InputStreamReader reader() throws HttpRequestException {
1913 return reader(charset());
1914 }
1915
1916 /**
1917 * Get buffered reader to response body using the given character set r and
1918 * the configured buffer size
1919 *
1920 *
1921 * @see #bufferSize(int)
1922 * @param charset
1923 * @return reader
1924 * @throws HttpRequestException
1925 */
1926 public BufferedReader bufferedReader(final String charset)
1927 throws HttpRequestException {
1928 return new BufferedReader(reader(charset), bufferSize);
1929 }
1930
1931 /**
1932 * Get buffered reader to response body using the character set returned from
1933 * {@link #charset()} and the configured buffer size
1934 *
1935 * @see #bufferSize(int)
1936 * @return reader
1937 * @throws HttpRequestException
1938 */
1939 public BufferedReader bufferedReader() throws HttpRequestException {
1940 return bufferedReader(charset());
1941 }
1942
1943 /**
1944 * Stream response body to file
1945 *
1946 * @param file
1947 * @return this request
1948 * @throws HttpRequestException
1949 */
1950 public HttpRequestService receive(final File file) throws HttpRequestException {
1951 final OutputStream output;
1952 try {
1953 output = new BufferedOutputStream(new FileOutputStream(file), bufferSize);
1954 } catch (FileNotFoundException e) {
1955 throw new HttpRequestException(e);
1956 }
1957 return new CloseOperation<HttpRequestService>(output, ignoreCloseExceptions) {
1958
1959 @Override
1960 protected HttpRequestService run() throws HttpRequestException, IOException {
1961 return receive(output);
1962 }
1963 }.call();
1964 }
1965
1966 /**
1967 * Stream response to given output stream
1968 *
1969 * @param output
1970 * @return this request
1971 * @throws HttpRequestException
1972 */
1973 public HttpRequestService receive(final OutputStream output)
1974 throws HttpRequestException {
1975 try {
1976 return copy(buffer(), output);
1977 } catch (IOException e) {
1978 throw new HttpRequestException(e);
1979 }
1980 }
1981
1982 /**
1983 * Stream response to given print stream
1984 *
1985 * @param output
1986 * @return this request
1987 * @throws HttpRequestException
1988 */
1989 public HttpRequestService receive(final PrintStream output)
1990 throws HttpRequestException {
1991 return receive((OutputStream) output);
1992 }
1993
1994 /**
1995 * Receive response into the given appendable
1996 *
1997 * @param appendable
1998 * @return this request
1999 * @throws HttpRequestException
2000 */
2001 public HttpRequestService receive(final Appendable appendable)
2002 throws HttpRequestException {
2003 final BufferedReader reader = bufferedReader();
2004 return new CloseOperation<HttpRequestService>(reader, ignoreCloseExceptions) {
2005
2006 @Override
2007 public HttpRequestService run() throws IOException {
2008 final CharBuffer buffer = CharBuffer.allocate(bufferSize);
2009 int read;
2010 while ((read = reader.read(buffer)) != -1) {
2011 buffer.rewind();
2012 appendable.append(buffer, 0, read);
2013 buffer.rewind();
2014 }
2015 return HttpRequestService.this;
2016 }
2017 }.call();
2018 }
2019
2020 /**
2021 * Receive response into the given writer
2022 *
2023 * @param writer
2024 * @return this request
2025 * @throws HttpRequestException
2026 */
2027 public HttpRequestService receive(final Writer writer) throws HttpRequestException {
2028 final BufferedReader reader = bufferedReader();
2029 return new CloseOperation<HttpRequestService>(reader, ignoreCloseExceptions) {
2030
2031 @Override
2032 public HttpRequestService run() throws IOException {
2033 return copy(reader, writer);
2034 }
2035 }.call();
2036 }
2037
2038 /**
2039 * Set read timeout on connection to given value
2040 *
2041 * @param timeout
2042 * @return this request
2043 */
2044 public HttpRequestService readTimeout(final int timeout) {
2045 getConnection().setReadTimeout(timeout);
2046 return this;
2047 }
2048
2049 /**
2050 * Set connect timeout on connection to given value
2051 *
2052 * @param timeout
2053 * @return this request
2054 */
2055 public HttpRequestService connectTimeout(final int timeout) {
2056 getConnection().setConnectTimeout(timeout);
2057 return this;
2058 }
2059
2060 /**
2061 * Set header name to given value
2062 *
2063 * @param name
2064 * @param value
2065 * @return this request
2066 */
2067 public HttpRequestService header(final String name, final String value) {
2068 getConnection().setRequestProperty(name, value);
2069 return this;
2070 }
2071
2072 /**
2073 * Set header name to given value
2074 *
2075 * @param name
2076 * @param value
2077 * @return this request
2078 */
2079 public HttpRequestService header(final String name, final Number value) {
2080 return header(name, value != null ? value.toString() : null);
2081 }
2082
2083 /**
2084 * Set all headers found in given map where the keys are the header names and
2085 * the values are the header values
2086 *
2087 * @param headers
2088 * @return this request
2089 */
2090 public HttpRequestService headers(final Map<String, String> headers) {
2091 if (!headers.isEmpty())
2092 for (Entry<String, String> header : headers.entrySet())
2093 header(header);
2094 return this;
2095 }
2096
2097 /**
2098 * Set header to have given entry's key as the name and value as the value
2099 *
2100 * @param header
2101 * @return this request
2102 */
2103 public HttpRequestService header(final Entry<String, String> header) {
2104 return header(header.getKey(), header.getValue());
2105 }
2106
2107 /**
2108 * Get a response header
2109 *
2110 * @param name
2111 * @return response header
2112 * @throws HttpRequestException
2113 */
2114 public String header(final String name) throws HttpRequestException {
2115 closeOutputQuietly();
2116 return getConnection().getHeaderField(name);
2117 }
2118
2119 /**
2120 * Get all the response headers
2121 *
2122 * @return map of response header names to their value(s)
2123 * @throws HttpRequestException
2124 */
2125 public Map<String, List<String>> headers() throws HttpRequestException {
2126 closeOutputQuietly();
2127 return getConnection().getHeaderFields();
2128 }
2129
2130 /**
2131 * Get a date header from the response falling back to returning -1 if the
2132 * header is missing or parsing fails
2133 *
2134 * @param name
2135 * @return date, -1 on failures
2136 * @throws HttpRequestException
2137 */
2138 public long dateHeader(final String name) throws HttpRequestException {
2139 return dateHeader(name, -1L);
2140 }
2141
2142 /**
2143 * Get a date header from the response falling back to returning the given
2144 * default value if the header is missing or parsing fails
2145 *
2146 * @param name
2147 * @param defaultValue
2148 * @return date, default value on failures
2149 * @throws HttpRequestException
2150 */
2151 public long dateHeader(final String name, final long defaultValue)
2152 throws HttpRequestException {
2153 closeOutputQuietly();
2154 return getConnection().getHeaderFieldDate(name, defaultValue);
2155 }
2156
2157 /**
2158 * Get an integer header from the response falling back to returning -1 if the
2159 * header is missing or parsing fails
2160 *
2161 * @param name
2162 * @return header value as an integer, -1 when missing or parsing fails
2163 * @throws HttpRequestException
2164 */
2165 public int intHeader(final String name) throws HttpRequestException {
2166 return intHeader(name, -1);
2167 }
2168
2169 /**
2170 * Get an integer header value from the response falling back to the given
2171 * default value if the header is missing or if parsing fails
2172 *
2173 * @param name
2174 * @param defaultValue
2175 * @return header value as an integer, default value when missing or parsing
2176 * fails
2177 * @throws HttpRequestException
2178 */
2179 public int intHeader(final String name, final int defaultValue)
2180 throws HttpRequestException {
2181 closeOutputQuietly();
2182 return getConnection().getHeaderFieldInt(name, defaultValue);
2183 }
2184
2185 /**
2186 * Get all values of the given header from the response
2187 *
2188 * @param name
2189 * @return non-null but possibly empty array of {@link String} header values
2190 */
2191 public String[] headers(final String name) {
2192 final Map<String, List<String>> headers = headers();
2193 if (headers == null || headers.isEmpty())
2194 return EMPTY_STRINGS;
2195
2196 final List<String> values = headers.get(name);
2197 if (values != null && !values.isEmpty())
2198 return values.toArray(new String[values.size()]);
2199 else
2200 return EMPTY_STRINGS;
2201 }
2202
2203 /**
2204 * Get parameter with given name from header value in response
2205 *
2206 * @param headerName
2207 * @param paramName
2208 * @return parameter value or null if missing
2209 */
2210 public String parameter(final String headerName, final String paramName) {
2211 return getParam(header(headerName), paramName);
2212 }
2213
2214 /**
2215 * Get all parameters from header value in response
2216 * <p>
2217 * This will be all key=value pairs after the first ';' that are separated by
2218 * a ';'
2219 *
2220 * @param headerName
2221 * @return non-null but possibly empty map of parameter headers
2222 */
2223 public Map<String, String> parameters(final String headerName) {
2224 return getParams(header(headerName));
2225 }
2226
2227 /**
2228 * Get parameter values from header value
2229 *
2230 * @param header
2231 * @return parameter value or null if none
2232 */
2233 protected Map<String, String> getParams(final String header) {
2234 if (header == null || header.length() == 0)
2235 return Collections.emptyMap();
2236
2237 final int headerLength = header.length();
2238 int start = header.indexOf(';') + 1;
2239 if (start == 0 || start == headerLength)
2240 return Collections.emptyMap();
2241
2242 int end = header.indexOf(';', start);
2243 if (end == -1)
2244 end = headerLength;
2245
2246 Map<String, String> params = new LinkedHashMap<String, String>();
2247 while (start < end) {
2248 int nameEnd = header.indexOf('=', start);
2249 if (nameEnd != -1 && nameEnd < end) {
2250 String name = header.substring(start, nameEnd).trim();
2251 if (name.length() > 0) {
2252 String value = header.substring(nameEnd + 1, end).trim();
2253 int length = value.length();
2254 if (length != 0)
2255 if (length > 2 && '"' == value.charAt(0)
2256 && '"' == value.charAt(length - 1))
2257 params.put(name, value.substring(1, length - 1));
2258 else
2259 params.put(name, value);
2260 }
2261 }
2262
2263 start = end + 1;
2264 end = header.indexOf(';', start);
2265 if (end == -1)
2266 end = headerLength;
2267 }
2268
2269 return params;
2270 }
2271
2272 /**
2273 * Get parameter value from header value
2274 *
2275 * @param value
2276 * @param paramName
2277 * @return parameter value or null if none
2278 */
2279 protected String getParam(final String value, final String paramName) {
2280 if (value == null || value.length() == 0)
2281 return null;
2282
2283 final int length = value.length();
2284 int start = value.indexOf(';') + 1;
2285 if (start == 0 || start == length)
2286 return null;
2287
2288 int end = value.indexOf(';', start);
2289 if (end == -1)
2290 end = length;
2291
2292 while (start < end) {
2293 int nameEnd = value.indexOf('=', start);
2294 if (nameEnd != -1 && nameEnd < end
2295 && paramName.equals(value.substring(start, nameEnd).trim())) {
2296 String paramValue = value.substring(nameEnd + 1, end).trim();
2297 int valueLength = paramValue.length();
2298 if (valueLength != 0)
2299 if (valueLength > 2 && '"' == paramValue.charAt(0)
2300 && '"' == paramValue.charAt(valueLength - 1))
2301 return paramValue.substring(1, valueLength - 1);
2302 else
2303 return paramValue;
2304 }
2305
2306 start = end + 1;
2307 end = value.indexOf(';', start);
2308 if (end == -1)
2309 end = length;
2310 }
2311
2312 return null;
2313 }
2314
2315 /**
2316 * Get 'charset' parameter from 'Content-Type' response header
2317 *
2318 * @return charset or null if none
2319 */
2320 public String charset() {
2321 return parameter(HEADER_CONTENT_TYPE, PARAM_CHARSET);
2322 }
2323
2324 /**
2325 * Set the 'User-Agent' header to given value
2326 *
2327 * @param userAgent
2328 * @return this request
2329 */
2330 public HttpRequestService userAgent(final String userAgent) {
2331 return header(HEADER_USER_AGENT, userAgent);
2332 }
2333
2334 /**
2335 * Set the 'Referer' header to given value
2336 *
2337 * @param referer
2338 * @return this request
2339 */
2340 public HttpRequestService referer(final String referer) {
2341 return header(HEADER_REFERER, referer);
2342 }
2343
2344 /**
2345 * Set value of {@link HttpURLConnection#setUseCaches(boolean)}
2346 *
2347 * @param useCaches
2348 * @return this request
2349 */
2350 public HttpRequestService useCaches(final boolean useCaches) {
2351 getConnection().setUseCaches(useCaches);
2352 return this;
2353 }
2354
2355 /**
2356 * Set the 'Accept-Encoding' header to given value
2357 *
2358 * @param acceptEncoding
2359 * @return this request
2360 */
2361 public HttpRequestService acceptEncoding(final String acceptEncoding) {
2362 return header(HEADER_ACCEPT_ENCODING, acceptEncoding);
2363 }
2364
2365 /**
2366 * Set the 'Accept-Encoding' header to 'gzip'
2367 *
2368 * @see #uncompress(boolean)
2369 * @return this request
2370 */
2371 public HttpRequestService acceptGzipEncoding() {
2372 return acceptEncoding(ENCODING_GZIP);
2373 }
2374
2375 /**
2376 * Set the 'Accept-Charset' header to given value
2377 *
2378 * @param acceptCharset
2379 * @return this request
2380 */
2381 public HttpRequestService acceptCharset(final String acceptCharset) {
2382 return header(HEADER_ACCEPT_CHARSET, acceptCharset);
2383 }
2384
2385 /**
2386 * Get the 'Content-Encoding' header from the response
2387 *
2388 * @return this request
2389 */
2390 public String contentEncoding() {
2391 return header(HEADER_CONTENT_ENCODING);
2392 }
2393
2394 /**
2395 * Get the 'Server' header from the response
2396 *
2397 * @return server
2398 */
2399 public String server() {
2400 return header(HEADER_SERVER);
2401 }
2402
2403 /**
2404 * Get the 'Date' header from the response
2405 *
2406 * @return date value, -1 on failures
2407 */
2408 public long date() {
2409 return dateHeader(HEADER_DATE);
2410 }
2411
2412 /**
2413 * Get the 'Cache-Control' header from the response
2414 *
2415 * @return cache control
2416 */
2417 public String cacheControl() {
2418 return header(HEADER_CACHE_CONTROL);
2419 }
2420
2421 /**
2422 * Get the 'ETag' header from the response
2423 *
2424 * @return entity tag
2425 */
2426 public String eTag() {
2427 return header(HEADER_ETAG);
2428 }
2429
2430 /**
2431 * Get the 'Expires' header from the response
2432 *
2433 * @return expires value, -1 on failures
2434 */
2435 public long expires() {
2436 return dateHeader(HEADER_EXPIRES);
2437 }
2438
2439 /**
2440 * Get the 'Last-Modified' header from the response
2441 *
2442 * @return last modified value, -1 on failures
2443 */
2444 public long lastModified() {
2445 return dateHeader(HEADER_LAST_MODIFIED);
2446 }
2447
2448 /**
2449 * Get the 'Location' header from the response
2450 *
2451 * @return location
2452 */
2453 public String location() {
2454 return header(HEADER_LOCATION);
2455 }
2456
2457 /**
2458 * Set the 'Authorization' header to given value
2459 *
2460 * @param authorization
2461 * @return this request
2462 */
2463 public HttpRequestService authorization(final String authorization) {
2464 return header(HEADER_AUTHORIZATION, authorization);
2465 }
2466
2467 /**
2468 * Set the 'Proxy-Authorization' header to given value
2469 *
2470 * @param proxyAuthorization
2471 * @return this request
2472 */
2473 public HttpRequestService proxyAuthorization(final String proxyAuthorization) {
2474 return header(HEADER_PROXY_AUTHORIZATION, proxyAuthorization);
2475 }
2476
2477 /**
2478 * Set the 'Authorization' header to given values in Basic authentication
2479 * format
2480 *
2481 * @param name
2482 * @param password
2483 * @return this request
2484 */
2485 public HttpRequestService basic(final String name, final String password) {
2486 return authorization("Basic " + Base64.encode(name + ':' + password));
2487 }
2488
2489 /**
2490 * Set the 'Proxy-Authorization' header to given values in Basic authentication
2491 * format
2492 *
2493 * @param name
2494 * @param password
2495 * @return this request
2496 */
2497 public HttpRequestService proxyBasic(final String name, final String password) {
2498 return proxyAuthorization("Basic " + Base64.encode(name + ':' + password));
2499 }
2500
2501 /**
2502 * Set the 'If-Modified-Since' request header to the given value
2503 *
2504 * @param ifModifiedSince
2505 * @return this request
2506 */
2507 public HttpRequestService ifModifiedSince(final long ifModifiedSince) {
2508 getConnection().setIfModifiedSince(ifModifiedSince);
2509 return this;
2510 }
2511
2512 /**
2513 * Set the 'If-None-Match' request header to the given value
2514 *
2515 * @param ifNoneMatch
2516 * @return this request
2517 */
2518 public HttpRequestService ifNoneMatch(final String ifNoneMatch) {
2519 return header(HEADER_IF_NONE_MATCH, ifNoneMatch);
2520 }
2521
2522 /**
2523 * Set the 'Content-Type' request header to the given value
2524 *
2525 * @param contentType
2526 * @return this request
2527 */
2528 public HttpRequestService contentType(final String contentType) {
2529 return contentType(contentType, null);
2530 }
2531
2532 /**
2533 * Set the 'Content-Type' request header to the given value and charset
2534 *
2535 * @param contentType
2536 * @param charset
2537 * @return this request
2538 */
2539 public HttpRequestService contentType(final String contentType, final String charset) {
2540 if (charset != null && charset.length() > 0) {
2541 final String separator = "; " + PARAM_CHARSET + '=';
2542 return header(HEADER_CONTENT_TYPE, contentType + separator + charset);
2543 } else
2544 return header(HEADER_CONTENT_TYPE, contentType);
2545 }
2546
2547 /**
2548 * Get the 'Content-Type' header from the response
2549 *
2550 * @return response header value
2551 */
2552 public String contentType() {
2553 return header(HEADER_CONTENT_TYPE);
2554 }
2555
2556 /**
2557 * Get the 'Content-Length' header from the response
2558 *
2559 * @return response header value
2560 */
2561 public int contentLength() {
2562 return intHeader(HEADER_CONTENT_LENGTH);
2563 }
2564
2565 /**
2566 * Set the 'Content-Length' request header to the given value
2567 *
2568 * @param contentLength
2569 * @return this request
2570 */
2571 public HttpRequestService contentLength(final String contentLength) {
2572 return contentLength(Integer.parseInt(contentLength));
2573 }
2574
2575 /**
2576 * Set the 'Content-Length' request header to the given value
2577 *
2578 * @param contentLength
2579 * @return this request
2580 */
2581 public HttpRequestService contentLength(final int contentLength) {
2582 getConnection().setFixedLengthStreamingMode(contentLength);
2583 return this;
2584 }
2585
2586 /**
2587 * Set the 'Accept' header to given value
2588 *
2589 * @param accept
2590 * @return this request
2591 */
2592 public HttpRequestService accept(final String accept) {
2593 return header(HEADER_ACCEPT, accept);
2594 }
2595
2596 /**
2597 * Set the 'Accept' header to 'application/json'
2598 *
2599 * @return this request
2600 */
2601 public HttpRequestService acceptJson() {
2602 return accept(CONTENT_TYPE_JSON);
2603 }
2604
2605 /**
2606 * Copy from input stream to output stream
2607 *
2608 * @param input
2609 * @param output
2610 * @return this request
2611 * @throws IOException
2612 */
2613 protected HttpRequestService copy(final InputStream input, final OutputStream output)
2614 throws IOException {
2615 return new CloseOperation<HttpRequestService>(input, ignoreCloseExceptions) {
2616
2617 @Override
2618 public HttpRequestService run() throws IOException {
2619 final byte[] buffer = new byte[bufferSize];
2620 int read;
2621 while ((read = input.read(buffer)) != -1) {
2622 output.write(buffer, 0, read);
2623 totalWritten += read;
2624 progress.onUpload(totalWritten, totalSize);
2625 }
2626 return HttpRequestService.this;
2627 }
2628 }.call();
2629 }
2630
2631 /**
2632 * Copy from reader to writer
2633 *
2634 * @param input
2635 * @param output
2636 * @return this request
2637 * @throws IOException
2638 */
2639 protected HttpRequestService copy(final Reader input, final Writer output)
2640 throws IOException {
2641 return new CloseOperation<HttpRequestService>(input, ignoreCloseExceptions) {
2642
2643 @Override
2644 public HttpRequestService run() throws IOException {
2645 final char[] buffer = new char[bufferSize];
2646 int read;
2647 while ((read = input.read(buffer)) != -1) {
2648 output.write(buffer, 0, read);
2649 totalWritten += read;
2650 progress.onUpload(totalWritten, -1);
2651 }
2652 return HttpRequestService.this;
2653 }
2654 }.call();
2655 }
2656
2657 /**
2658 * Set the UploadProgress callback for this request
2659 *
2660 * @param callback
2661 * @return this request
2662 */
2663 public HttpRequestService progress(final UploadProgress callback) {
2664 if (callback == null)
2665 progress = UploadProgress.DEFAULT;
2666 else
2667 progress = callback;
2668 return this;
2669 }
2670
2671 private HttpRequestService incrementTotalSize(final long size) {
2672 if (totalSize == -1)
2673 totalSize = 0;
2674 totalSize += size;
2675 return this;
2676 }
2677
2678 /**
2679 * Close output stream
2680 *
2681 * @return this request
2682 * @throws HttpRequestException
2683 * @throws IOException
2684 */
2685 protected HttpRequestService closeOutput() throws IOException {
2686 progress(null);
2687 if (output == null)
2688 return this;
2689 if (multipart)
2690 output.write(CRLF + "--" + BOUNDARY + "--" + CRLF);
2691 if (ignoreCloseExceptions)
2692 try {
2693 output.close();
2694 } catch (IOException ignored) {
2695 // Ignored
2696 }
2697 else
2698 output.close();
2699 output = null;
2700 return this;
2701 }
2702
2703 /**
2704 * Call {@link #closeOutput()} and re-throw a caught {@link IOException}s as
2705 * an {@link HttpRequestException}
2706 *
2707 * @return this request
2708 * @throws HttpRequestException
2709 */
2710 protected HttpRequestService closeOutputQuietly() throws HttpRequestException {
2711 try {
2712 return closeOutput();
2713 } catch (IOException e) {
2714 throw new HttpRequestException(e);
2715 }
2716 }
2717
2718 /**
2719 * Open output stream
2720 *
2721 * @return this request
2722 * @throws IOException
2723 */
2724 protected HttpRequestService openOutput() throws IOException {
2725 if (output != null)
2726 return this;
2727 getConnection().setDoOutput(true);
2728 final String charset = getParam(
2729 getConnection().getRequestProperty(HEADER_CONTENT_TYPE), PARAM_CHARSET);
2730 output = new RequestOutputStream(getConnection().getOutputStream(), charset,
2731 bufferSize);
2732 return this;
2733 }
2734
2735 /**
2736 * Start part of a multipart
2737 *
2738 * @return this request
2739 * @throws IOException
2740 */
2741 protected HttpRequestService startPart() throws IOException {
2742 if (!multipart) {
2743 multipart = true;
2744 contentType(CONTENT_TYPE_MULTIPART).openOutput();
2745 output.write("--" + BOUNDARY + CRLF);
2746 } else
2747 output.write(CRLF + "--" + BOUNDARY + CRLF);
2748 return this;
2749 }
2750
2751 /**
2752 * Write part header
2753 *
2754 * @param name
2755 * @param filename
2756 * @return this request
2757 * @throws IOException
2758 */
2759 protected HttpRequestService writePartHeader(final String name, final String filename)
2760 throws IOException {
2761 return writePartHeader(name, filename, null);
2762 }
2763
2764 /**
2765 * Write part header
2766 *
2767 * @param name
2768 * @param filename
2769 * @param contentType
2770 * @return this request
2771 * @throws IOException
2772 */
2773 protected HttpRequestService writePartHeader(final String name,
2774 final String filename, final String contentType) throws IOException {
2775 final StringBuilder partBuffer = new StringBuilder();
2776 partBuffer.append("form-data; name=\"").append(name);
2777 if (filename != null)
2778 partBuffer.append("\"; filename=\"").append(filename);
2779 partBuffer.append('"');
2780 partHeader("Content-Disposition", partBuffer.toString());
2781 if (contentType != null)
2782 partHeader(HEADER_CONTENT_TYPE, contentType);
2783 return send(CRLF);
2784 }
2785
2786 /**
2787 * Write part of a multipart request to the request body
2788 *
2789 * @param name
2790 * @param part
2791 * @return this request
2792 */
2793 public HttpRequestService part(final String name, final String part) {
2794 return part(name, null, part);
2795 }
2796
2797 /**
2798 * Write part of a multipart request to the request body
2799 *
2800 * @param name
2801 * @param filename
2802 * @param part
2803 * @return this request
2804 * @throws HttpRequestException
2805 */
2806 public HttpRequestService part(final String name, final String filename,
2807 final String part) throws HttpRequestException {
2808 return part(name, filename, null, part);
2809 }
2810
2811 /**
2812 * Write part of a multipart request to the request body
2813 *
2814 * @param name
2815 * @param filename
2816 * @param contentType
2817 * value of the Content-Type part header
2818 * @param part
2819 * @return this request
2820 * @throws HttpRequestException
2821 */
2822 public HttpRequestService part(final String name, final String filename,
2823 final String contentType, final String part) throws HttpRequestException {
2824 try {
2825 startPart();
2826 writePartHeader(name, filename, contentType);
2827 output.write(part);
2828 } catch (IOException e) {
2829 throw new HttpRequestException(e);
2830 }
2831 return this;
2832 }
2833
2834 /**
2835 * Write part of a multipart request to the request body
2836 *
2837 * @param name
2838 * @param part
2839 * @return this request
2840 * @throws HttpRequestException
2841 */
2842 public HttpRequestService part(final String name, final Number part)
2843 throws HttpRequestException {
2844 return part(name, null, part);
2845 }
2846
2847 /**
2848 * Write part of a multipart request to the request body
2849 *
2850 * @param name
2851 * @param filename
2852 * @param part
2853 * @return this request
2854 * @throws HttpRequestException
2855 */
2856 public HttpRequestService part(final String name, final String filename,
2857 final Number part) throws HttpRequestException {
2858 return part(name, filename, part != null ? part.toString() : null);
2859 }
2860
2861 /**
2862 * Write part of a multipart request to the request body
2863 *
2864 * @param name
2865 * @param part
2866 * @return this request
2867 * @throws HttpRequestException
2868 */
2869 public HttpRequestService part(final String name, final File part)
2870 throws HttpRequestException {
2871 return part(name, null, part);
2872 }
2873
2874 /**
2875 * Write part of a multipart request to the request body
2876 *
2877 * @param name
2878 * @param filename
2879 * @param part
2880 * @return this request
2881 * @throws HttpRequestException
2882 */
2883 public HttpRequestService part(final String name, final String filename,
2884 final File part) throws HttpRequestException {
2885 return part(name, filename, null, part);
2886 }
2887
2888 /**
2889 * Write part of a multipart request to the request body
2890 *
2891 * @param name
2892 * @param filename
2893 * @param contentType
2894 * value of the Content-Type part header
2895 * @param part
2896 * @return this request
2897 * @throws HttpRequestException
2898 */
2899 public HttpRequestService part(final String name, final String filename,
2900 final String contentType, final File part) throws HttpRequestException {
2901 final InputStream stream;
2902 try {
2903 stream = new BufferedInputStream(new FileInputStream(part));
2904 incrementTotalSize(part.length());
2905 } catch (IOException e) {
2906 throw new HttpRequestException(e);
2907 }
2908 return part(name, filename, contentType, stream);
2909 }
2910
2911 /**
2912 * Write part of a multipart request to the request body
2913 *
2914 * @param name
2915 * @param part
2916 * @return this request
2917 * @throws HttpRequestException
2918 */
2919 public HttpRequestService part(final String name, final InputStream part)
2920 throws HttpRequestException {
2921 return part(name, null, null, part);
2922 }
2923
2924 /**
2925 * Write part of a multipart request to the request body
2926 *
2927 * @param name
2928 * @param filename
2929 * @param contentType
2930 * value of the Content-Type part header
2931 * @param part
2932 * @return this request
2933 * @throws HttpRequestException
2934 */
2935 public HttpRequestService part(final String name, final String filename,
2936 final String contentType, final InputStream part)
2937 throws HttpRequestException {
2938 try {
2939 startPart();
2940 writePartHeader(name, filename, contentType);
2941 copy(part, output);
2942 } catch (IOException e) {
2943 throw new HttpRequestException(e);
2944 }
2945 return this;
2946 }
2947
2948 /**
2949 * Write a multipart header to the response body
2950 *
2951 * @param name
2952 * @param value
2953 * @return this request
2954 * @throws HttpRequestException
2955 */
2956 public HttpRequestService partHeader(final String name, final String value)
2957 throws HttpRequestException {
2958 return send(name).send(": ").send(value).send(CRLF);
2959 }
2960
2961 /**
2962 * Write contents of file to request body
2963 *
2964 * @param input
2965 * @return this request
2966 * @throws HttpRequestException
2967 */
2968 public HttpRequestService send(final File input) throws HttpRequestException {
2969 final InputStream stream;
2970 try {
2971 stream = new BufferedInputStream(new FileInputStream(input));
2972 incrementTotalSize(input.length());
2973 } catch (FileNotFoundException e) {
2974 throw new HttpRequestException(e);
2975 }
2976 return send(stream);
2977 }
2978
2979 /**
2980 * Write byte array to request body
2981 *
2982 * @param input
2983 * @return this request
2984 * @throws HttpRequestException
2985 */
2986 public HttpRequestService send(final byte[] input) throws HttpRequestException {
2987 if (input != null)
2988 incrementTotalSize(input.length);
2989 return send(new ByteArrayInputStream(input));
2990 }
2991
2992 /**
2993 * Write stream to request body
2994 * <p>
2995 * The given stream will be closed once sending completes
2996 *
2997 * @param input
2998 * @return this request
2999 * @throws HttpRequestException
3000 */
3001 public HttpRequestService send(final InputStream input) throws HttpRequestException {
3002 try {
3003 openOutput();
3004 copy(input, output);
3005 } catch (IOException e) {
3006 throw new HttpRequestException(e);
3007 }
3008 return this;
3009 }
3010
3011 /**
3012 * Write reader to request body
3013 * <p>
3014 * The given reader will be closed once sending completes
3015 *
3016 * @param input
3017 * @return this request
3018 * @throws HttpRequestException
3019 */
3020 public HttpRequestService send(final Reader input) throws HttpRequestException {
3021 try {
3022 openOutput();
3023 } catch (IOException e) {
3024 throw new HttpRequestException(e);
3025 }
3026 final Writer writer = new OutputStreamWriter(output,
3027 output.encoder.charset());
3028 return new FlushOperation<HttpRequestService>(writer) {
3029
3030 @Override
3031 protected HttpRequestService run() throws IOException {
3032 return copy(input, writer);
3033 }
3034 }.call();
3035 }
3036
3037 /**
3038 * Write char sequence to request body
3039 * <p>
3040 * The charset configured via {@link #contentType(String)} will be used and
3041 * UTF-8 will be used if it is unset.
3042 *
3043 * @param value
3044 * @return this request
3045 * @throws HttpRequestException
3046 */
3047 public HttpRequestService send(final CharSequence value) throws HttpRequestException {
3048 try {
3049 openOutput();
3050 output.write(value.toString());
3051 } catch (IOException e) {
3052 throw new HttpRequestException(e);
3053 }
3054 return this;
3055 }
3056
3057 /**
3058 * Create writer to request output stream
3059 *
3060 * @return writer
3061 * @throws HttpRequestException
3062 */
3063 public OutputStreamWriter writer() throws HttpRequestException {
3064 try {
3065 openOutput();
3066 return new OutputStreamWriter(output, output.encoder.charset());
3067 } catch (IOException e) {
3068 throw new HttpRequestException(e);
3069 }
3070 }
3071
3072 /**
3073 * Write the values in the map as form data to the request body
3074 * <p>
3075 * The pairs specified will be URL-encoded in UTF-8 and sent with the
3076 * 'application/x-www-form-urlencoded' content-type
3077 *
3078 * @param values
3079 * @return this request
3080 * @throws HttpRequestException
3081 */
3082 public HttpRequestService form(final Map<?, ?> values) throws HttpRequestException {
3083 return form(values, CHARSET_UTF8);
3084 }
3085
3086 /**
3087 * Write the key and value in the entry as form data to the request body
3088 * <p>
3089 * The pair specified will be URL-encoded in UTF-8 and sent with the
3090 * 'application/x-www-form-urlencoded' content-type
3091 *
3092 * @param entry
3093 * @return this request
3094 * @throws HttpRequestException
3095 */
3096 public HttpRequestService form(final Entry<?, ?> entry) throws HttpRequestException {
3097 return form(entry, CHARSET_UTF8);
3098 }
3099
3100 /**
3101 * Write the key and value in the entry as form data to the request body
3102 * <p>
3103 * The pair specified will be URL-encoded and sent with the
3104 * 'application/x-www-form-urlencoded' content-type
3105 *
3106 * @param entry
3107 * @param charset
3108 * @return this request
3109 * @throws HttpRequestException
3110 */
3111 public HttpRequestService form(final Entry<?, ?> entry, final String charset)
3112 throws HttpRequestException {
3113 return form(entry.getKey(), entry.getValue(), charset);
3114 }
3115
3116 /**
3117 * Write the name/value pair as form data to the request body
3118 * <p>
3119 * The pair specified will be URL-encoded in UTF-8 and sent with the
3120 * 'application/x-www-form-urlencoded' content-type
3121 *
3122 * @param name
3123 * @param value
3124 * @return this request
3125 * @throws HttpRequestException
3126 */
3127 public HttpRequestService form(final Object name, final Object value)
3128 throws HttpRequestException {
3129 return form(name, value, CHARSET_UTF8);
3130 }
3131
3132 /**
3133 * Write the name/value pair as form data to the request body
3134 * <p>
3135 * The values specified will be URL-encoded and sent with the
3136 * 'application/x-www-form-urlencoded' content-type
3137 *
3138 * @param name
3139 * @param value
3140 * @param charset
3141 * @return this request
3142 * @throws HttpRequestException
3143 */
3144 public HttpRequestService form(final Object name, final Object value, String charset)
3145 throws HttpRequestException {
3146 final boolean first = !form;
3147 if (first) {
3148 contentType(CONTENT_TYPE_FORM, charset);
3149 form = true;
3150 }
3151 charset = getValidCharset(charset);
3152 try {
3153 openOutput();
3154 if (!first)
3155 output.write('&');
3156 output.write(URLEncoder.encode(name.toString(), charset));
3157 output.write('=');
3158 if (value != null)
3159 output.write(URLEncoder.encode(value.toString(), charset));
3160 } catch (IOException e) {
3161 throw new HttpRequestException(e);
3162 }
3163 return this;
3164 }
3165
3166 /**
3167 * Write the values in the map as encoded form data to the request body
3168 *
3169 * @param values
3170 * @param charset
3171 * @return this request
3172 * @throws HttpRequestException
3173 */
3174 public HttpRequestService form(final Map<?, ?> values, final String charset)
3175 throws HttpRequestException {
3176 if (!values.isEmpty())
3177 for (Entry<?, ?> entry : values.entrySet())
3178 form(entry, charset);
3179 return this;
3180 }
3181
3182 /**
3183 * Configure HTTPS connection to trust all certificates
3184 * <p>
3185 * This method does nothing if the current request is not a HTTPS request
3186 *
3187 * @return this request
3188 * @throws HttpRequestException
3189 */
3190 public HttpRequestService trustAllCerts() throws HttpRequestException {
3191 final HttpURLConnection connection = getConnection();
3192 if (connection instanceof HttpsURLConnection)
3193 ((HttpsURLConnection) connection)
3194 .setSSLSocketFactory(getTrustedFactory());
3195 return this;
3196 }
3197
3198 /**
3199 * Configure HTTPS connection to trust all hosts using a custom
3200 * {@link HostnameVerifier} that always returns <code>true</code> for each
3201 * host verified
3202 * <p>
3203 * This method does nothing if the current request is not a HTTPS request
3204 *
3205 * @return this request
3206 */
3207 public HttpRequestService trustAllHosts() {
3208 final HttpURLConnection connection = getConnection();
3209 if (connection instanceof HttpsURLConnection)
3210 ((HttpsURLConnection) connection)
3211 .setHostnameVerifier(getTrustedVerifier());
3212 return this;
3213 }
3214
3215 /**
3216 * Get the {@link URL} of this request's connection
3217 *
3218 * @return request URL
3219 */
3220 public URL url() {
3221 return getConnection().getURL();
3222 }
3223
3224 /**
3225 * Get the HTTP method of this request
3226 *
3227 * @return method
3228 */
3229 public String method() {
3230 return getConnection().getRequestMethod();
3231 }
3232
3233 /**
3234 * Configure an HTTP proxy on this connection. Use {{@link #proxyBasic(String, String)} if
3235 * this proxy requires basic authentication.
3236 *
3237 * @param proxyHost
3238 * @param proxyPort
3239 * @return this request
3240 */
3241 public HttpRequestService useProxy(final String proxyHost, final int proxyPort) {
3242 if (connection != null)
3243 throw new IllegalStateException("The connection has already been created. This method must be called before reading or writing to the request.");
3244
3245 this.httpProxyHost = proxyHost;
3246 this.httpProxyPort = proxyPort;
3247 return this;
3248 }
3249
3250 /**
3251 * Set whether or not the underlying connection should follow redirects in
3252 * the response.
3253 *
3254 * @param followRedirects - true fo follow redirects, false to not.
3255 * @return this request
3256 */
3257 public HttpRequestService followRedirects(final boolean followRedirects) {
3258 getConnection().setInstanceFollowRedirects(followRedirects);
3259 return this;
3260 }
3261}
Note: See TracBrowser for help on using the repository browser.