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