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. 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 * 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()) ? 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. 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 requests the body. {@code null} if not specified. 530 */ 531 @Deprecated 532 public void setQuery(final String query) { 533 534 if (Method.POST.equals(getMethod())) { 535 setBody(query); 536 } else { 537 appendQueryString(query); 538 } 539 } 540 541 542 /** 543 * Ensures this HTTP response has a specified query string or entity 544 * body. 545 * 546 * @throws ParseException If the query string or entity body is missing 547 * or empty. 548 */ 549 private void ensureQuery() 550 throws ParseException { 551 552 if (getQuery() == null || getQuery().trim().isEmpty()) 553 throw new ParseException("Missing or empty HTTP query string / entity body"); 554 } 555 556 557 /** 558 * Gets the query string as a parameter map. The parameters are decoded 559 * according to {@code application/x-www-form-urlencoded}. 560 * 561 * @return The query string parameters to, decoded. If none the map 562 * will be empty. 563 */ 564 public Map<String,List<String>> getQueryStringParameters() { 565 566 return URLUtils.parseParameters(url.getQuery()); 567 } 568 569 570 /** 571 * Gets the request query as a parameter map. The parameters are 572 * decoded according to {@code application/x-www-form-urlencoded}. 573 * 574 * @deprecated Use {@link #getQueryStringParameters()}. 575 * 576 * @return The request query parameters, decoded. If none the map will 577 * be empty. 578 */ 579 @Deprecated 580 public Map<String,List<String>> getQueryParameters() { 581 582 return URLUtils.parseParameters(getQuery()); 583 } 584 585 586 /** 587 * Gets the request query or entity body as a JSON Object. 588 * 589 * @deprecated Use {@link #getBodyAsJSONObject()}. 590 * 591 * @return The request query or entity body as a JSON object. 592 * 593 * @throws ParseException If the Content-Type header isn't 594 * {@code application/json}, the request query 595 * or entity body is {@code null}, empty or 596 * couldn't be parsed to a valid JSON object. 597 */ 598 @Deprecated 599 public JSONObject getQueryAsJSONObject() 600 throws ParseException { 601 602 ensureEntityContentType(ContentType.APPLICATION_JSON); 603 604 ensureQuery(); 605 606 return JSONObjectUtils.parse(getQuery()); 607 } 608 609 610 /** 611 * Gets the raw (encoded) fragment of the URL. 612 * 613 * @deprecated Use {@link #getURL()}. 614 * 615 * @return The fragment, {@code null} if not specified. 616 */ 617 @Deprecated 618 public String getFragment() { 619 620 return url.getRef(); 621 } 622 623 624 /** 625 * Sets the raw (encoded) fragment of the URL. 626 * 627 * @param fragment The fragment, {@code null} if not specified. 628 */ 629 public void setFragment(final String fragment) { 630 631 url = URLUtils.setEncodedFragment(url, fragment); 632 } 633 634 635 @Override 636 public int getConnectTimeout() { 637 638 return connectTimeout; 639 } 640 641 642 /** 643 * Sets the HTTP connect timeout. 644 * 645 * @param connectTimeout The HTTP connect timeout, in milliseconds. 646 * Zero implies no timeout. Must not be negative. 647 */ 648 public void setConnectTimeout(final int connectTimeout) { 649 650 if (connectTimeout < 0) { 651 throw new IllegalArgumentException("The HTTP connect timeout must be zero or positive"); 652 } 653 654 this.connectTimeout = connectTimeout; 655 } 656 657 658 @Override 659 public int getReadTimeout() { 660 661 return readTimeout; 662 } 663 664 665 /** 666 * Sets the HTTP response read timeout. 667 * 668 * @param readTimeout The HTTP response read timeout, in milliseconds. 669 * Zero implies no timeout. Must not be negative. 670 */ 671 public void setReadTimeout(final int readTimeout) { 672 673 if (readTimeout < 0) { 674 throw new IllegalArgumentException("The HTTP response read timeout must be zero or positive"); 675 } 676 677 this.readTimeout = readTimeout; 678 } 679 680 /** 681 * Returns the proxy to use for this HTTP request. 682 * 683 * @return The connection specific proxy for this request, {@code null} 684 * for the default proxy strategy. 685 */ 686 public Proxy getProxy() { 687 688 return this.proxy; 689 } 690 691 692 /** 693 * Tunnels this HTTP request via the specified {@link Proxy} by 694 * directly configuring the proxy on the {@link java.net.URLConnection}. 695 * The proxy is only used for this instance and bypasses any other 696 * proxy settings (such as set via System properties or 697 * {@link java.net.ProxySelector}). Supplying {@code null} (the 698 * default) reverts to the default proxy strategy of 699 * {@link java.net.URLConnection}. If the goal is to avoid using a 700 * proxy at all supply {@link Proxy#NO_PROXY}. 701 * 702 * @param proxy The connection specific proxy to use, {@code null} to 703 * use the default proxy strategy. 704 * 705 * @see URL#openConnection(Proxy) 706 */ 707 public void setProxy(final Proxy proxy) { 708 709 this.proxy = proxy; 710 } 711 712 713 /** 714 * Gets the boolean setting whether HTTP redirects (requests with 715 * response code 3xx) should be automatically followed. 716 * 717 * @return {@code true} if HTTP redirects are automatically followed, 718 * else {@code false}. 719 */ 720 public boolean getFollowRedirects() { 721 722 return followRedirects; 723 } 724 725 726 /** 727 * Sets whether HTTP redirects (requests with response code 3xx) should 728 * be automatically followed. 729 * 730 * @param follow {@code true} if HTTP redirects are automatically 731 * followed, else {@code false}. 732 */ 733 public void setFollowRedirects(final boolean follow) { 734 735 followRedirects = follow; 736 } 737 738 739 /** 740 * Gets the received validated client X.509 certificate for a received 741 * HTTPS request. 742 * 743 * @return The client X.509 certificate, {@code null} if not specified. 744 */ 745 public X509Certificate getClientX509Certificate() { 746 747 return clientX509Certificate; 748 } 749 750 751 /** 752 * Sets the received validated client X.509 certificate for a received 753 * HTTPS request. 754 * 755 * @param clientX509Certificate The client X.509 certificate, 756 * {@code null} if not specified. 757 */ 758 public void setClientX509Certificate(final X509Certificate clientX509Certificate) { 759 760 this.clientX509Certificate = clientX509Certificate; 761 } 762 763 764 /** 765 * Gets the subject DN of a received validated client X.509 certificate 766 * for a received HTTPS request. 767 * 768 * @return The subject DN, {@code null} if not specified. 769 */ 770 public String getClientX509CertificateSubjectDN() { 771 772 return clientX509CertificateSubjectDN; 773 } 774 775 776 /** 777 * Sets the subject DN of a received validated client X.509 certificate 778 * for a received HTTPS request. 779 * 780 * @param subjectDN The subject DN, {@code null} if not specified. 781 */ 782 public void setClientX509CertificateSubjectDN(final String subjectDN) { 783 784 this.clientX509CertificateSubjectDN = subjectDN; 785 } 786 787 788 /** 789 * Gets the root issuer DN of a received validated client X.509 790 * certificate for a received HTTPS request. 791 * 792 * @return The root DN, {@code null} if not specified. 793 */ 794 public String getClientX509CertificateRootDN() { 795 796 return clientX509CertificateRootDN; 797 } 798 799 800 /** 801 * Sets the root issuer DN of a received validated client X.509 802 * certificate for a received HTTPS request. 803 * 804 * @param rootDN The root DN, {@code null} if not specified. 805 */ 806 public void setClientX509CertificateRootDN(final String rootDN) { 807 808 this.clientX509CertificateRootDN = rootDN; 809 } 810 811 812 /** 813 * Gets the hostname verifier for outgoing HTTPS requests. 814 * 815 * @return The hostname verifier, {@code null} implies use of the 816 * {@link #getDefaultHostnameVerifier() default one}. 817 */ 818 public HostnameVerifier getHostnameVerifier() { 819 820 return hostnameVerifier; 821 } 822 823 824 /** 825 * Sets the hostname verifier for outgoing HTTPS requests. 826 * 827 * @param hostnameVerifier The hostname verifier, {@code null} implies 828 * use of the 829 * {@link #getDefaultHostnameVerifier() default 830 * one}. 831 */ 832 public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) { 833 834 this.hostnameVerifier = hostnameVerifier; 835 } 836 837 838 /** 839 * Gets the SSL factory for outgoing HTTPS requests. 840 * 841 * @return The SSL factory, {@code null} implies of the default one. 842 */ 843 public SSLSocketFactory getSSLSocketFactory() { 844 845 return sslSocketFactory; 846 } 847 848 849 /** 850 * Sets the SSL factory for outgoing HTTPS requests. Use the 851 * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to 852 * set a custom trust store for server and CA certificates and / or a 853 * custom key store for client private keys and certificates, also to 854 * select a specific TLS protocol version. 855 * 856 * @param sslSocketFactory The SSL factory, {@code null} implies use of 857 * the default one. 858 */ 859 public void setSSLSocketFactory(final SSLSocketFactory sslSocketFactory) { 860 861 this.sslSocketFactory = sslSocketFactory; 862 } 863 864 865 /** 866 * Returns the default hostname verifier for all outgoing HTTPS 867 * requests. 868 * 869 * @return The hostname verifier. 870 */ 871 public static HostnameVerifier getDefaultHostnameVerifier() { 872 873 return defaultHostnameVerifier; 874 } 875 876 877 /** 878 * Sets the default hostname verifier for all outgoing HTTPS requests. 879 * Can be overridden on a individual request basis. 880 * 881 * @param defaultHostnameVerifier The hostname verifier. Must not be 882 * {@code null}. 883 */ 884 public static void setDefaultHostnameVerifier(final HostnameVerifier defaultHostnameVerifier) { 885 886 if (defaultHostnameVerifier == null) { 887 throw new IllegalArgumentException("The hostname verifier must not be null"); 888 } 889 890 HTTPRequest.defaultHostnameVerifier = defaultHostnameVerifier; 891 } 892 893 894 /** 895 * Returns the default SSL socket factory for all outgoing HTTPS 896 * requests. 897 * 898 * @return The SSL socket factory. 899 */ 900 public static SSLSocketFactory getDefaultSSLSocketFactory() { 901 902 return defaultSSLSocketFactory; 903 } 904 905 906 /** 907 * Sets the default SSL socket factory for all outgoing HTTPS requests. 908 * Can be overridden on a individual request basis. Use the 909 * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to 910 * set a custom trust store for server and CA certificates and / or a 911 * custom key store for client private keys and certificates, also to 912 * select a specific TLS protocol version. 913 * 914 * @param sslSocketFactory The SSL socket factory. Must not be 915 * {@code null}. 916 */ 917 public static void setDefaultSSLSocketFactory(final SSLSocketFactory sslSocketFactory) { 918 919 if (sslSocketFactory == null) { 920 throw new IllegalArgumentException("The SSL socket factory must not be null"); 921 } 922 923 HTTPRequest.defaultSSLSocketFactory = sslSocketFactory; 924 } 925 926 927 /** 928 * Returns an established HTTP URL connection for this HTTP request. 929 * Deprecated as of v5.31, use {@link #toHttpURLConnection()} with 930 * {@link #setHostnameVerifier} and {@link #setSSLSocketFactory} 931 * instead. 932 * 933 * @param hostnameVerifier The hostname verifier for outgoing HTTPS 934 * requests, {@code null} implies use of the 935 * {@link #getDefaultHostnameVerifier() default 936 * one}. 937 * @param sslSocketFactory The SSL socket factory for HTTPS requests, 938 * {@code null} implies use of the 939 * {@link #getDefaultSSLSocketFactory() default 940 * one}. 941 * 942 * @return The HTTP URL connection, with the request sent and ready to 943 * read the response. 944 * 945 * @throws IOException If the HTTP request couldn't be made, due to a 946 * network or other error. 947 */ 948 @Deprecated 949 public HttpURLConnection toHttpURLConnection(final HostnameVerifier hostnameVerifier, 950 final SSLSocketFactory sslSocketFactory) 951 throws IOException { 952 953 HostnameVerifier savedHostnameVerifier = getHostnameVerifier(); 954 SSLSocketFactory savedSSLFactory = getSSLSocketFactory(); 955 956 try { 957 // Set for this HTTP URL connection only 958 setHostnameVerifier(hostnameVerifier); 959 setSSLSocketFactory(sslSocketFactory); 960 961 return toHttpURLConnection(); 962 963 } finally { 964 setHostnameVerifier(savedHostnameVerifier); 965 setSSLSocketFactory(savedSSLFactory); 966 } 967 } 968 969 970 /** 971 * Returns an established HTTP URL connection for this HTTP request. 972 * 973 * @return The HTTP URL connection, with the request sent and ready to 974 * read the response. 975 * 976 * @throws IOException If the HTTP request couldn't be made, due to a 977 * network or other error. 978 */ 979 public HttpURLConnection toHttpURLConnection() 980 throws IOException { 981 982 final URL finalURL = getURL(); 983 984 HttpURLConnection conn = (HttpURLConnection) (proxy == null ? finalURL.openConnection() : finalURL.openConnection(proxy)); 985 986 if (conn instanceof HttpsURLConnection) { 987 HttpsURLConnection sslConn = (HttpsURLConnection)conn; 988 sslConn.setHostnameVerifier(hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier()); 989 sslConn.setSSLSocketFactory(sslSocketFactory != null ? sslSocketFactory : getDefaultSSLSocketFactory()); 990 } 991 992 for (Map.Entry<String,List<String>> header: getHeaderMap().entrySet()) { 993 for (String headerValue: header.getValue()) { 994 conn.addRequestProperty(header.getKey(), headerValue); 995 } 996 } 997 998 conn.setRequestMethod(method.name()); 999 conn.setConnectTimeout(connectTimeout); 1000 conn.setReadTimeout(readTimeout); 1001 conn.setInstanceFollowRedirects(followRedirects); 1002 1003 if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) { 1004 1005 conn.setDoOutput(true); 1006 1007 if (getEntityContentType() != null) 1008 conn.setRequestProperty("Content-Type", getEntityContentType().toString()); 1009 1010 if (getBody() != null) { 1011 OutputStream outputStream = null; 1012 try { 1013 outputStream = conn.getOutputStream(); 1014 OutputStreamWriter writer = new OutputStreamWriter(outputStream); 1015 writer.write(getBody()); 1016 writer.close(); 1017 } catch (IOException e) { 1018 closeStreams(conn.getInputStream(), outputStream, conn.getErrorStream(), debugCloseStreams); 1019 throw e; // Rethrow 1020 } 1021 } 1022 } 1023 1024 return conn; 1025 } 1026 1027 1028 /** 1029 * Sends this HTTP request to the request URL and retrieves the 1030 * resulting HTTP response. Deprecated as of v5.31, use 1031 * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and 1032 * {@link #setSSLSocketFactory} instead. 1033 * 1034 * @param hostnameVerifier The hostname verifier for outgoing HTTPS 1035 * requests, {@code null} implies use of the 1036 * {@link #getDefaultHostnameVerifier() default 1037 * one}. 1038 * @param sslSocketFactory The SSL socket factory for HTTPS requests, 1039 * {@code null} implies use of the 1040 * {@link #getDefaultSSLSocketFactory() default 1041 * one}. 1042 * 1043 * @return The resulting HTTP response. 1044 * 1045 * @throws IOException If the HTTP request couldn't be made, due to a 1046 * network or other error. 1047 */ 1048 @Deprecated 1049 public HTTPResponse send(final HostnameVerifier hostnameVerifier, 1050 final SSLSocketFactory sslSocketFactory) 1051 throws IOException { 1052 1053 HostnameVerifier savedHostnameVerifier = getHostnameVerifier(); 1054 SSLSocketFactory savedSSLFactory = getSSLSocketFactory(); 1055 1056 try { 1057 // Set for this HTTP URL connection only 1058 setHostnameVerifier(hostnameVerifier); 1059 setSSLSocketFactory(sslSocketFactory); 1060 1061 return send(); 1062 1063 } finally { 1064 setHostnameVerifier(savedHostnameVerifier); 1065 setSSLSocketFactory(savedSSLFactory); 1066 } 1067 } 1068 1069 1070 /** 1071 * Sends this HTTP request to the {@link #getURL() URL} and retrieves 1072 * the resulting HTTP response. 1073 * 1074 * @return The resulting HTTP response. 1075 * 1076 * @throws IOException If the HTTP request couldn't be sent, due to a 1077 * network or another error. 1078 */ 1079 public HTTPResponse send() 1080 throws IOException { 1081 1082 HttpURLConnection conn = toHttpURLConnection(); 1083 1084 int statusCode; 1085 1086 BufferedReader reader; 1087 1088 InputStream inputStream = null; 1089 InputStream errStream = null; 1090 OutputStream outputStream = null; 1091 try { 1092 // getOutputStream() can only be retrieved before calling getInputStream() 1093 if (conn.getDoOutput()) { 1094 outputStream = conn.getOutputStream(); 1095 } 1096 // Open a connection, then send method and headers 1097 inputStream = conn.getInputStream(); 1098 reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 1099 1100 // The next step is to get the status 1101 statusCode = conn.getResponseCode(); 1102 1103 } catch (IOException e) { 1104 1105 // HttpUrlConnection will throw an IOException if any 1106 // 4XX response is sent. If we request the status 1107 // again, this time the internal status will be 1108 // properly set, and we'll be able to retrieve it. 1109 statusCode = conn.getResponseCode(); 1110 1111 if (statusCode == -1) { 1112 throw e; // Rethrow IO exception 1113 } else { 1114 // HTTP status code indicates the response got 1115 // through, read the content but using error stream 1116 errStream = conn.getErrorStream(); 1117 1118 if (errStream != null) { 1119 // We have useful HTTP error body 1120 reader = new BufferedReader(new InputStreamReader(errStream, StandardCharsets.UTF_8)); 1121 } else { 1122 // No content, set to empty string 1123 reader = new BufferedReader(new StringReader("")); 1124 } 1125 } 1126 } 1127 1128 StringBuilder body = new StringBuilder(); 1129 String line; 1130 while ((line = reader.readLine()) != null) { 1131 body.append(line); 1132 body.append(System.getProperty("line.separator")); 1133 } 1134 reader.close(); 1135 1136 1137 HTTPResponse response = new HTTPResponse(statusCode); 1138 1139 response.setStatusMessage(conn.getResponseMessage()); 1140 1141 // Set headers 1142 for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) { 1143 1144 if (responseHeader.getKey() == null) { 1145 continue; // skip header 1146 } 1147 1148 List<String> values = responseHeader.getValue(); 1149 if (values == null || values.isEmpty() || values.get(0) == null) { 1150 continue; // skip header 1151 } 1152 1153 response.setHeader(responseHeader.getKey(), values.toArray(new String[]{})); 1154 } 1155 1156 closeStreams(inputStream, outputStream, errStream, debugCloseStreams); 1157 1158 final String bodyContent = body.toString(); 1159 if (! bodyContent.isEmpty()) 1160 response.setBody(bodyContent); 1161 1162 return response; 1163 } 1164 1165 1166 /** 1167 * Sends this HTTP request to the {@link #getURL() URL} and retrieves 1168 * the resulting HTTP response. 1169 * 1170 * @param httpRequestSender The HTTP request sender. Must not be 1171 * {@code null}. 1172 * 1173 * @return The resulting HTTP response. 1174 * 1175 * @throws IOException If the HTTP request couldn't be sent, due to a 1176 * network or another error. 1177 */ 1178 public HTTPResponse send(final HTTPRequestSender httpRequestSender) 1179 throws IOException { 1180 1181 ReadOnlyHTTPResponse roResponse = httpRequestSender.send(this); 1182 1183 HTTPResponse response = new HTTPResponse(roResponse.getStatusCode()); 1184 response.setStatusMessage(roResponse.getStatusMessage()); 1185 for (Map.Entry<String, List<String>> en: roResponse.getHeaderMap().entrySet()) { 1186 if (en.getKey() != null && en.getValue() != null && ! en.getValue().isEmpty()) { 1187 response.setHeader(en.getKey(), en.getValue().toArray(new String[0])); 1188 } 1189 } 1190 response.setBody(roResponse.getBody()); 1191 return response; 1192 } 1193 1194 1195 /** 1196 * Closes the input, output and error streams of the specified HTTP URL 1197 * connection. No attempt is made to close the underlying socket with 1198 * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live). 1199 * See http://techblog.bozho.net/caveats-of-httpurlconnection/ 1200 */ 1201 private static void closeStreams(final InputStream inputStream, 1202 final OutputStream outputStream, 1203 final InputStream errStream, 1204 final boolean debugCloseStreams) 1205 throws IOException { 1206 1207 try { 1208 if (inputStream != null) { 1209 inputStream.close(); 1210 } 1211 } catch (IOException e) { 1212 if (debugCloseStreams) { 1213 throw e; 1214 } 1215 } catch (Exception e) { 1216 // ignore 1217 } 1218 1219 try { 1220 if (outputStream != null) { 1221 outputStream.close(); 1222 } 1223 } catch (IOException e) { 1224 if (debugCloseStreams) { 1225 throw e; 1226 } 1227 } catch (Exception e) { 1228 // ignore 1229 } 1230 1231 try { 1232 if (errStream != null) { 1233 errStream.close(); 1234 } 1235 } catch (IOException e) { 1236 if (debugCloseStreams) { 1237 throw e; 1238 } 1239 } catch (Exception e) { 1240 // ignore 1241 } 1242 } 1243}