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