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 * &client_id=s6BhdRkqt3 333 * &state=xyz 334 * &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 * &client_id=s6BhdRkqt3 358 * &state=xyz 359 * &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}