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