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