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 * &client_id=s6BhdRkqt3 368 * &state=xyz 369 * &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 * &client_id=s6BhdRkqt3 393 * &state=xyz 394 * &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}