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