001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.http;
019
020
021import java.io.*;
022import java.net.*;
023import java.nio.charset.StandardCharsets;
024import java.security.cert.X509Certificate;
025import java.util.List;
026import java.util.Map;
027import javax.net.ssl.HostnameVerifier;
028import javax.net.ssl.HttpsURLConnection;
029import javax.net.ssl.SSLSocketFactory;
030
031import com.nimbusds.common.contenttype.ContentType;
032import com.nimbusds.oauth2.sdk.ParseException;
033import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
034import com.nimbusds.oauth2.sdk.util.URLUtils;
035import net.jcip.annotations.ThreadSafe;
036import net.minidev.json.JSONObject;
037
038
039/**
040 * HTTP request with support for the parameters required to construct an 
041 * {@link com.nimbusds.oauth2.sdk.Request OAuth 2.0 request message}.
042 *
043 * <p>Supported HTTP methods:
044 *
045 * <ul>
046 *     <li>{@link Method#GET HTTP GET}
047 *     <li>{@link Method#POST HTTP POST}
048 *     <li>{@link Method#POST HTTP PUT}
049 *     <li>{@link Method#POST HTTP DELETE}
050 * </ul>
051 *
052 * <p>Supported request headers:
053 *
054 * <ul>
055 *     <li>Content-Type
056 *     <li>Authorization
057 *     <li>Accept
058 *     <li>Etc.
059 * </ul>
060 *
061 * <p>Supported timeouts:
062 *
063 * <ul>
064 *     <li>On HTTP connect
065 *     <li>On HTTP response read
066 * </ul>
067 *
068 * <p>HTTP 3xx redirection: follow (default) / don't follow
069 */
070@ThreadSafe
071public class HTTPRequest extends HTTPMessage {
072
073
074        /**
075         * Enumeration of the HTTP methods used in OAuth 2.0 requests.
076         */
077        public enum Method {
078        
079                /**
080                 * HTTP GET.
081                 */
082                GET,
083                
084                
085                /**
086                 * HTTP POST.
087                 */
088                POST,
089                
090                
091                /**
092                 * HTTP PUT.
093                 */
094                PUT,
095                
096                
097                /**
098                 * HTTP DELETE.
099                 */
100                DELETE
101        }
102        
103        
104        /**
105         * The request method.
106         */
107        private final Method method;
108
109
110        /**
111         * The request URL.
112         */
113        private final URL url;
114        
115        
116        /**
117         * The query string / post body.
118         */
119        private String query = null;
120
121
122        /**
123         * The fragment.
124         */
125        private String fragment = null;
126
127
128        /**
129         * The HTTP connect timeout, in milliseconds. Zero implies none.
130         */
131        private int connectTimeout = 0;
132
133
134        /**
135         * The HTTP response read timeout, in milliseconds. Zero implies none.
136
137         */
138        private int readTimeout = 0;
139
140
141        /**
142         * Controls HTTP 3xx redirections.
143         */
144        private boolean followRedirects = true;
145        
146        
147        /**
148         * The received validated client X.509 certificate for a received HTTPS
149         * request, {@code null} if not specified.
150         */
151        private X509Certificate clientX509Certificate = null;
152        
153        
154        /**
155         * The subject DN of a received client X.509 certificate for a received
156         * HTTPS request, {@code null} if not specified.
157         */
158        private String clientX509CertificateSubjectDN = null;
159        
160        
161        /**
162         * The root issuer DN of a received client X.509 certificate for a
163         * received HTTPS request, {@code null} if not specified.
164         */
165        private String clientX509CertificateRootDN = null;
166        
167        
168        /**
169         * The hostname verifier to use for outgoing HTTPS requests,
170         * {@code null} implies the default one.
171         */
172        private HostnameVerifier hostnameVerifier = null;
173        
174        
175        /**
176         * The SSL socket factory to use for outgoing HTTPS requests,
177         * {@code null} implies the default one.
178         */
179        private SSLSocketFactory sslSocketFactory = null;
180
181
182        /**
183         * The default hostname verifier for all outgoing HTTPS requests.
184         */
185        private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
186
187
188        /**
189         * The default socket factory for all outgoing HTTPS requests.
190         */
191        private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
192        
193        
194        /**
195         * Creates a new minimally specified HTTP request.
196         *
197         * @param method The HTTP request method. Must not be {@code null}.
198         * @param url    The HTTP request URL. Must not be {@code null}.
199         */
200        public HTTPRequest(final Method method, final URL url) {
201        
202                if (method == null)
203                        throw new IllegalArgumentException("The HTTP method must not be null");
204                
205                this.method = method;
206
207
208                if (url == null)
209                        throw new IllegalArgumentException("The HTTP URL must not be null");
210
211                this.url = url;
212        }
213        
214        
215        /**
216         * Gets the request method.
217         *
218         * @return The request method.
219         */
220        public Method getMethod() {
221        
222                return method;
223        }
224
225
226        /**
227         * Gets the request URL.
228         *
229         * @return The request URL.
230         */
231        public URL getURL() {
232
233                return url;
234        }
235
236
237        /**
238         * Gets the request URL as URI.
239         *
240         * @return The request URL as URI.
241         */
242        public URI getURI() {
243                
244                try {
245                        return url.toURI();
246                } catch (URISyntaxException e) {
247                        // Should never happen
248                        throw new IllegalStateException(e.getMessage(), e);
249                }
250        }
251        
252        
253        /**
254         * Ensures this HTTP request has the specified method.
255         *
256         * @param expectedMethod The expected method. Must not be {@code null}.
257         *
258         * @throws ParseException If the method doesn't match the expected.
259         */
260        public void ensureMethod(final Method expectedMethod)
261                throws ParseException {
262                
263                if (method != expectedMethod)
264                        throw new ParseException("The HTTP request method must be " + expectedMethod);
265        }
266        
267        
268        /**
269         * Gets the {@code Authorization} header value.
270         *
271         * @return The {@code Authorization} header value, {@code null} if not 
272         *         specified.
273         */
274        public String getAuthorization() {
275        
276                return getHeaderValue("Authorization");
277        }
278        
279        
280        /**
281         * Sets the {@code Authorization} header value.
282         *
283         * @param authz The {@code Authorization} header value, {@code null} if 
284         *              not specified.
285         */
286        public void setAuthorization(final String authz) {
287        
288                setHeader("Authorization", authz);
289        }
290
291
292        /**
293         * Gets the {@code Accept} header value.
294         *
295         * @return The {@code Accept} header value, {@code null} if not
296         *         specified.
297         */
298        public String getAccept() {
299
300                return getHeaderValue("Accept");
301        }
302
303
304        /**
305         * Sets the {@code Accept} header value.
306         *
307         * @param accept The {@code Accept} header value, {@code null} if not
308         *               specified.
309         */
310        public void setAccept(final String accept) {
311
312                setHeader("Accept", accept);
313        }
314        
315        
316        /**
317         * Gets the raw (undecoded) query string if the request is HTTP GET or
318         * the entity body if the request is HTTP POST.
319         *
320         * <p>Note that the '?' character preceding the query string in GET
321         * requests is not included in the returned string.
322         *
323         * <p>Example query string (line breaks for clarity):
324         *
325         * <pre>
326         * response_type=code
327         * &amp;client_id=s6BhdRkqt3
328         * &amp;state=xyz
329         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
330         * </pre>
331         *
332         * @return For HTTP GET requests the URL query string, for HTTP POST 
333         *         requests the body. {@code null} if not specified.
334         */
335        public String getQuery() {
336        
337                return query;
338        }
339        
340        
341        /**
342         * Sets the raw (undecoded) query string if the request is HTTP GET or
343         * the entity body if the request is HTTP POST.
344         *
345         * <p>Note that the '?' character preceding the query string in GET
346         * requests must not be included.
347         *
348         * <p>Example query string (line breaks for clarity):
349         *
350         * <pre>
351         * response_type=code
352         * &amp;client_id=s6BhdRkqt3
353         * &amp;state=xyz
354         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
355         * </pre>
356         *
357         * @param query For HTTP GET requests the URL query string, for HTTP 
358         *              POST requests the body. {@code null} if not specified.
359         */
360        public void setQuery(final String query) {
361        
362                this.query = query;
363        }
364
365
366        /**
367         * Ensures this HTTP response has a specified query string or entity
368         * body.
369         *
370         * @throws ParseException If the query string or entity body is missing
371         *                        or empty.
372         */
373        private void ensureQuery()
374                throws ParseException {
375                
376                if (query == null || query.trim().isEmpty())
377                        throw new ParseException("Missing or empty HTTP query string / entity body");
378        }
379        
380        
381        /**
382         * Gets the request query as a parameter map. The parameters are 
383         * decoded according to {@code application/x-www-form-urlencoded}.
384         *
385         * @return The request query parameters, decoded. If none the map will
386         *         be empty.
387         */
388        public Map<String,List<String>> getQueryParameters() {
389        
390                return URLUtils.parseParameters(query);
391        }
392
393
394        /**
395         * Gets the request query or entity body as a JSON Object.
396         *
397         * @return The request query or entity body as a JSON object.
398         *
399         * @throws ParseException If the Content-Type header isn't 
400         *                        {@code application/json}, the request query
401         *                        or entity body is {@code null}, empty or 
402         *                        couldn't be parsed to a valid JSON object.
403         */
404        public JSONObject getQueryAsJSONObject()
405                throws ParseException {
406
407                ensureEntityContentType(ContentType.APPLICATION_JSON);
408
409                ensureQuery();
410
411                return JSONObjectUtils.parse(query);
412        }
413
414
415        /**
416         * Gets the raw (undecoded) request fragment.
417         *
418         * @return The request fragment, {@code null} if not specified.
419         */
420        public String getFragment() {
421
422                return fragment;
423        }
424
425
426        /**
427         * Sets the raw (undecoded) request fragment.
428         *
429         * @param fragment The request fragment, {@code null} if not specified.
430         */
431        public void setFragment(final String fragment) {
432
433                this.fragment = fragment;
434        }
435
436
437        /**
438         * Gets the HTTP connect timeout.
439         *
440         * @return The HTTP connect timeout, in milliseconds. Zero implies no
441         *         timeout.
442         */
443        public int getConnectTimeout() {
444
445                return connectTimeout;
446        }
447
448
449        /**
450         * Sets the HTTP connect timeout.
451         *
452         * @param connectTimeout The HTTP connect timeout, in milliseconds.
453         *                       Zero implies no timeout. Must not be negative.
454         */
455        public void setConnectTimeout(final int connectTimeout) {
456
457                if (connectTimeout < 0) {
458                        throw new IllegalArgumentException("The HTTP connect timeout must be zero or positive");
459                }
460
461                this.connectTimeout = connectTimeout;
462        }
463
464
465        /**
466         * Gets the HTTP response read timeout.
467         *
468         * @return The HTTP response read timeout, in milliseconds. Zero
469         *         implies no timeout.
470         */
471        public int getReadTimeout() {
472
473                return readTimeout;
474        }
475
476
477        /**
478         * Sets the HTTP response read timeout.
479         *
480         * @param readTimeout The HTTP response read timeout, in milliseconds.
481         *                    Zero implies no timeout. Must not be negative.
482         */
483        public void setReadTimeout(final int readTimeout) {
484
485                if (readTimeout < 0) {
486                        throw new IllegalArgumentException("The HTTP response read timeout must be zero or positive");
487                }
488
489                this.readTimeout = readTimeout;
490        }
491
492
493        /**
494         * Gets the boolean setting whether HTTP redirects (requests with
495         * response code 3xx) should be automatically followed.
496         *
497         * @return {@code true} if HTTP redirects are automatically followed,
498         *         else {@code false}.
499         */
500        public boolean getFollowRedirects() {
501
502                return followRedirects;
503        }
504
505
506        /**
507         * Sets whether HTTP redirects (requests with response code 3xx) should
508         * be automatically followed.
509         *
510         * @param follow Whether or not to follow HTTP redirects.
511         */
512        public void setFollowRedirects(final boolean follow) {
513
514                followRedirects = follow;
515        }
516        
517        
518        /**
519         * Gets the received validated client X.509 certificate for a received
520         * HTTPS request.
521         *
522         * @return The client X.509 certificate, {@code null} if not specified.
523         */
524        public X509Certificate getClientX509Certificate() {
525                
526                return clientX509Certificate;
527        }
528        
529        
530        /**
531         * Sets the received validated client X.509 certificate for a received
532         * HTTPS request.
533         *
534         * @param clientX509Certificate The client X.509 certificate,
535         *                              {@code null} if not specified.
536         */
537        public void setClientX509Certificate(final X509Certificate clientX509Certificate) {
538                
539                this.clientX509Certificate = clientX509Certificate;
540        }
541        
542        
543        /**
544         * Gets the subject DN of a received validated client X.509 certificate
545         * for a received HTTPS request.
546         *
547         * @return The subject DN, {@code null} if not specified.
548         */
549        public String getClientX509CertificateSubjectDN() {
550                
551                return clientX509CertificateSubjectDN;
552        }
553        
554        
555        /**
556         * Sets the subject DN of a received validated client X.509 certificate
557         * for a received HTTPS request.
558         *
559         * @param subjectDN The subject DN, {@code null} if not specified.
560         */
561        public void setClientX509CertificateSubjectDN(final String subjectDN) {
562                
563                this.clientX509CertificateSubjectDN = subjectDN;
564        }
565        
566        
567        /**
568         * Gets the root issuer DN of a received validated client X.509
569         * certificate for a received HTTPS request.
570         *
571         * @return The root DN, {@code null} if not specified.
572         */
573        public String getClientX509CertificateRootDN() {
574                
575                return clientX509CertificateRootDN;
576        }
577        
578        
579        /**
580         * Sets the root issuer DN of a received validated client X.509
581         * certificate for a received HTTPS request.
582         *
583         * @param rootDN The root DN, {@code null} if not specified.
584         */
585        public void setClientX509CertificateRootDN(final String rootDN) {
586                
587                this.clientX509CertificateRootDN = rootDN;
588        }
589        
590        
591        /**
592         * Gets the hostname verifier for outgoing HTTPS requests.
593         *
594         * @return The hostname verifier, {@code null} implies use of the
595         *         {@link #getDefaultHostnameVerifier() default one}.
596         */
597        public HostnameVerifier getHostnameVerifier() {
598                
599                return hostnameVerifier;
600        }
601        
602        
603        /**
604         * Sets the hostname verifier for outgoing HTTPS requests.
605         *
606         * @param hostnameVerifier The hostname verifier, {@code null} implies
607         *                         use of the
608         *                         {@link #getDefaultHostnameVerifier() default
609         *                         one}.
610         */
611        public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
612                
613                this.hostnameVerifier = hostnameVerifier;
614        }
615        
616        
617        /**
618         * Gets the SSL factory for outgoing HTTPS requests.
619         *
620         * @return The SSL factory, {@code null} implies of the default one.
621         */
622        public SSLSocketFactory getSSLSocketFactory() {
623                
624                return sslSocketFactory;
625        }
626        
627        
628        /**
629         * Sets the SSL factory for outgoing HTTPS requests.
630         *
631         * @param sslSocketFactory The SSL factory, {@code null} implies use of
632         *                         the default one.
633         */
634        public void setSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
635                
636                this.sslSocketFactory = sslSocketFactory;
637        }
638        
639        
640        /**
641         * Returns the default hostname verifier for all outgoing HTTPS
642         * requests.
643         *
644         * @return The hostname verifier.
645         */
646        public static HostnameVerifier getDefaultHostnameVerifier() {
647
648                return defaultHostnameVerifier;
649        }
650
651
652        /**
653         * Sets the default hostname verifier for all outgoing HTTPS requests.
654         * May be overridden on a individual request basis.
655         *
656         * @param defaultHostnameVerifier The hostname verifier. Must not be
657         *                         {@code null}.
658         */
659        public static void setDefaultHostnameVerifier(final HostnameVerifier defaultHostnameVerifier) {
660
661                if (defaultHostnameVerifier == null) {
662                        throw new IllegalArgumentException("The hostname verifier must not be null");
663                }
664
665                HTTPRequest.defaultHostnameVerifier = defaultHostnameVerifier;
666        }
667
668
669        /**
670         * Returns the default SSL socket factory for all outgoing HTTPS
671         * requests.
672         *
673         * @return The SSL socket factory.
674         */
675        public static SSLSocketFactory getDefaultSSLSocketFactory() {
676
677                return defaultSSLSocketFactory;
678        }
679
680
681        /**
682         * Sets the default SSL socket factory for all outgoing HTTPS requests.
683         * May be overridden on a individual request basis.
684         *
685         * @param sslSocketFactory The SSL socket factory. Must not be
686         *                         {@code null}.
687         */
688        public static void setDefaultSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
689
690                if (sslSocketFactory == null) {
691                        throw new IllegalArgumentException("The SSL socket factory must not be null");
692                }
693
694                HTTPRequest.defaultSSLSocketFactory = sslSocketFactory;
695        }
696
697
698        /**
699         * Returns an established HTTP URL connection for this HTTP request.
700         * Deprecated as of v5.31, use {@link #toHttpURLConnection()} with
701         * {@link #setHostnameVerifier} and {@link #setSSLSocketFactory}
702         * instead.
703         *
704         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
705         *                         requests, {@code null} implies use of the
706         *                         {@link #getDefaultHostnameVerifier() default
707         *                         one}.
708         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
709         *                         {@code null} implies use of the
710         *                         {@link #getDefaultSSLSocketFactory() default
711         *                         one}.
712         *
713         * @return The HTTP URL connection, with the request sent and ready to
714         *         read the response.
715         *
716         * @throws IOException If the HTTP request couldn't be made, due to a
717         *                     network or other error.
718         */
719        @Deprecated
720        public HttpURLConnection toHttpURLConnection(final HostnameVerifier hostnameVerifier,
721                                                     final SSLSocketFactory sslSocketFactory)
722                throws IOException {
723                
724                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
725                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
726                
727                try {
728                        // Set for this HTTP URL connection only
729                        setHostnameVerifier(hostnameVerifier);
730                        setSSLSocketFactory(sslSocketFactory);
731                        
732                        return toHttpURLConnection();
733                        
734                } finally {
735                        setHostnameVerifier(savedHostnameVerifier);
736                        setSSLSocketFactory(savedSSLFactory);
737                }
738        }
739
740
741        /**
742         * Returns an established HTTP URL connection for this HTTP request.
743         *
744         * @return The HTTP URL connection, with the request sent and ready to
745         *         read the response.
746         *
747         * @throws IOException If the HTTP request couldn't be made, due to a
748         *                     network or other error.
749         */
750        public HttpURLConnection toHttpURLConnection()
751                throws IOException {
752
753                URL finalURL = url;
754
755                if (query != null && (method.equals(HTTPRequest.Method.GET) || method.equals(Method.DELETE))) {
756
757                        // Append query string
758                        StringBuilder sb = new StringBuilder(url.toString());
759                        sb.append('?');
760                        sb.append(query);
761
762                        try {
763                                finalURL = new URL(sb.toString());
764
765                        } catch (MalformedURLException e) {
766
767                                throw new IOException("Couldn't append query string: " + e.getMessage(), e);
768                        }
769                }
770
771                if (fragment != null) {
772
773                        // Append raw fragment
774                        StringBuilder sb = new StringBuilder(finalURL.toString());
775                        sb.append('#');
776                        sb.append(fragment);
777
778                        try {
779                                finalURL = new URL(sb.toString());
780
781                        } catch (MalformedURLException e) {
782
783                                throw new IOException("Couldn't append raw fragment: " + e.getMessage(), e);
784                        }
785                }
786
787                HttpURLConnection conn = (HttpURLConnection)finalURL.openConnection();
788
789                if (conn instanceof HttpsURLConnection) {
790                        HttpsURLConnection sslConn = (HttpsURLConnection)conn;
791                        sslConn.setHostnameVerifier(hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier());
792                        sslConn.setSSLSocketFactory(sslSocketFactory != null ? sslSocketFactory : getDefaultSSLSocketFactory());
793                }
794
795                for (Map.Entry<String,List<String>> header: getHeaderMap().entrySet()) {
796                        for (String headerValue: header.getValue()) {
797                                conn.addRequestProperty(header.getKey(), headerValue);
798                        }
799                }
800
801                conn.setRequestMethod(method.name());
802                conn.setConnectTimeout(connectTimeout);
803                conn.setReadTimeout(readTimeout);
804                conn.setInstanceFollowRedirects(followRedirects);
805
806                if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) {
807
808                        conn.setDoOutput(true);
809
810                        if (getEntityContentType() != null)
811                                conn.setRequestProperty("Content-Type", getEntityContentType().toString());
812
813                        if (query != null) {
814                                try {
815                                        OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
816                                        writer.write(query);
817                                        writer.close();
818                                } catch (IOException e) {
819                                        closeStreams(conn);
820                                        throw e; // Rethrow
821                                }
822                        }
823                }
824
825                return conn;
826        }
827
828
829        /**
830         * Sends this HTTP request to the request URL and retrieves the
831         * resulting HTTP response. Deprecated as of v5.31, use
832         * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and
833         * {@link #setSSLSocketFactory} instead.
834         *
835         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
836         *                         requests, {@code null} implies use of the
837         *                         {@link #getDefaultHostnameVerifier() default
838         *                         one}.
839         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
840         *                         {@code null} implies use of the
841         *                         {@link #getDefaultSSLSocketFactory() default
842         *                         one}.
843         *
844         * @return The resulting HTTP response.
845         *
846         * @throws IOException If the HTTP request couldn't be made, due to a
847         *                     network or other error.
848         */
849        @Deprecated
850        public HTTPResponse send(final HostnameVerifier hostnameVerifier,
851                                 final SSLSocketFactory sslSocketFactory)
852                throws IOException {
853                
854                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
855                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
856                
857                try {
858                        // Set for this HTTP URL connection only
859                        setHostnameVerifier(hostnameVerifier);
860                        setSSLSocketFactory(sslSocketFactory);
861                        
862                        return send();
863                        
864                } finally {
865                        setHostnameVerifier(savedHostnameVerifier);
866                        setSSLSocketFactory(savedSSLFactory);
867                }
868        }
869
870
871        /**
872         * Sends this HTTP request to the request URL and retrieves the
873         * resulting HTTP response.
874         *
875         * @return The resulting HTTP response.
876         *
877         * @throws IOException If the HTTP request couldn't be made, due to a
878         *                     network or other error.
879         */
880        public HTTPResponse send()
881                throws IOException {
882
883                HttpURLConnection conn = toHttpURLConnection();
884
885                int statusCode;
886
887                BufferedReader reader;
888
889                try {
890                        // Open a connection, then send method and headers
891                        reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
892
893                        // The next step is to get the status
894                        statusCode = conn.getResponseCode();
895
896                } catch (IOException e) {
897
898                        // HttpUrlConnection will throw an IOException if any
899                        // 4XX response is sent. If we request the status
900                        // again, this time the internal status will be
901                        // properly set, and we'll be able to retrieve it.
902                        statusCode = conn.getResponseCode();
903
904                        if (statusCode == -1) {
905                                throw e; // Rethrow IO exception
906                        } else {
907                                // HTTP status code indicates the response got
908                                // through, read the content but using error stream
909                                InputStream errStream = conn.getErrorStream();
910
911                                if (errStream != null) {
912                                        // We have useful HTTP error body
913                                        reader = new BufferedReader(new InputStreamReader(errStream));
914                                } else {
915                                        // No content, set to empty string
916                                        reader = new BufferedReader(new StringReader(""));
917                                }
918                        }
919                }
920
921                StringBuilder body = new StringBuilder();
922                String line;
923                while ((line = reader.readLine()) != null) {
924                        body.append(line);
925                        body.append(System.getProperty("line.separator"));
926                }
927                reader.close();
928
929
930                HTTPResponse response = new HTTPResponse(statusCode);
931                
932                response.setStatusMessage(conn.getResponseMessage());
933
934                // Set headers
935                for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) {
936
937                        if (responseHeader.getKey() == null) {
938                                continue; // skip header
939                        }
940
941                        List<String> values = responseHeader.getValue();
942                        if (values == null || values.isEmpty() || values.get(0) == null) {
943                                continue; // skip header
944                        }
945
946                        response.setHeader(responseHeader.getKey(), values.toArray(new String[]{}));
947                }
948
949                closeStreams(conn);
950
951                final String bodyContent = body.toString();
952                if (! bodyContent.isEmpty())
953                        response.setContent(bodyContent);
954
955                return response;
956        }
957
958
959        /**
960         * Closes the input, output and error streams of the specified HTTP URL
961         * connection. No attempt is made to close the underlying socket with
962         * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live).
963         * See http://techblog.bozho.net/caveats-of-httpurlconnection/
964         *
965         * @param conn The HTTP URL connection. May be {@code null}.
966         */
967        private static void closeStreams(final HttpURLConnection conn) {
968
969                if (conn == null) {
970                        return;
971                }
972
973                try {
974                        if (conn.getInputStream() != null) {
975                                conn.getInputStream().close();
976                        }
977                } catch (Exception e) {
978                        // ignore
979                }
980
981                try {
982                        if (conn.getOutputStream() != null) {
983                                conn.getOutputStream().close();
984                        }
985                } catch (Exception e) {
986                        // ignore
987                }
988
989                try {
990                        if (conn.getErrorStream() != null) {
991                                conn.getOutputStream().close();
992                        }
993                } catch (Exception e) {
994                        // ignore
995                }
996        }
997}