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