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