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