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 String dPoP = getHeaderValue("DPoP"); 330 if (dPoP == null) { 331 return null; 332 } 333 334 try { 335 return SignedJWT.parse(dPoP); 336 } catch (java.text.ParseException e) { 337 return null; 338 } 339 } 340 341 342 /** 343 * Sets the {@code DPoP} header value. 344 * 345 * @param dPoPJWT The {@code DPoP} header value, {@code null} if not 346 * specified. 347 */ 348 public void setDPoP(final SignedJWT dPoPJWT) { 349 350 if (dPoPJWT != null) { 351 setHeader("DPoP", dPoPJWT.serialize()); 352 } else { 353 setHeader("DPoP", (String[]) null); 354 } 355 } 356 357 358 /** 359 * Gets the {@code Accept} header value. 360 * 361 * @return The {@code Accept} header value, {@code null} if not 362 * specified. 363 */ 364 public String getAccept() { 365 366 return getHeaderValue("Accept"); 367 } 368 369 370 /** 371 * Sets the {@code Accept} header value. 372 * 373 * @param accept The {@code Accept} header value, {@code null} if not 374 * specified. 375 */ 376 public void setAccept(final String accept) { 377 378 setHeader("Accept", accept); 379 } 380 381 382 /** 383 * Gets the raw (undecoded) query string if the request is HTTP GET or 384 * the entity body if the request is HTTP POST. 385 * 386 * <p>Note that the '?' character preceding the query string in GET 387 * requests is not included in the returned string. 388 * 389 * <p>Example query string (line breaks for clarity): 390 * 391 * <pre> 392 * response_type=code 393 * &client_id=s6BhdRkqt3 394 * &state=xyz 395 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 396 * </pre> 397 * 398 * @return For HTTP GET requests the URL query string, for HTTP POST 399 * requests the body. {@code null} if not specified. 400 */ 401 public String getQuery() { 402 403 return query; 404 } 405 406 407 /** 408 * Sets the raw (undecoded) query string if the request is HTTP GET or 409 * the entity body if the request is HTTP POST. 410 * 411 * <p>Note that the '?' character preceding the query string in GET 412 * requests must not be included. 413 * 414 * <p>Example query string (line breaks for clarity): 415 * 416 * <pre> 417 * response_type=code 418 * &client_id=s6BhdRkqt3 419 * &state=xyz 420 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 421 * </pre> 422 * 423 * @param query For HTTP GET requests the URL query string, for HTTP 424 * POST requests the body. {@code null} if not specified. 425 */ 426 public void setQuery(final String query) { 427 428 this.query = query; 429 } 430 431 432 /** 433 * Ensures this HTTP response has a specified query string or entity 434 * body. 435 * 436 * @throws ParseException If the query string or entity body is missing 437 * or empty. 438 */ 439 private void ensureQuery() 440 throws ParseException { 441 442 if (query == null || query.trim().isEmpty()) 443 throw new ParseException("Missing or empty HTTP query string / entity body"); 444 } 445 446 447 /** 448 * Gets the request query as a parameter map. The parameters are 449 * decoded according to {@code application/x-www-form-urlencoded}. 450 * 451 * @return The request query parameters, decoded. If none the map will 452 * be empty. 453 */ 454 public Map<String,List<String>> getQueryParameters() { 455 456 return URLUtils.parseParameters(query); 457 } 458 459 460 /** 461 * Gets the request query or entity body as a JSON Object. 462 * 463 * @return The request query or entity body as a JSON object. 464 * 465 * @throws ParseException If the Content-Type header isn't 466 * {@code application/json}, the request query 467 * or entity body is {@code null}, empty or 468 * couldn't be parsed to a valid JSON object. 469 */ 470 public JSONObject getQueryAsJSONObject() 471 throws ParseException { 472 473 ensureEntityContentType(ContentType.APPLICATION_JSON); 474 475 ensureQuery(); 476 477 return JSONObjectUtils.parse(query); 478 } 479 480 481 /** 482 * Gets the raw (undecoded) request fragment. 483 * 484 * @return The request fragment, {@code null} if not specified. 485 */ 486 public String getFragment() { 487 488 return fragment; 489 } 490 491 492 /** 493 * Sets the raw (undecoded) request fragment. 494 * 495 * @param fragment The request fragment, {@code null} if not specified. 496 */ 497 public void setFragment(final String fragment) { 498 499 this.fragment = fragment; 500 } 501 502 503 /** 504 * Gets the HTTP connect timeout. 505 * 506 * @return The HTTP connect timeout, in milliseconds. Zero implies no 507 * timeout. 508 */ 509 public int getConnectTimeout() { 510 511 return connectTimeout; 512 } 513 514 515 /** 516 * Sets the HTTP connect timeout. 517 * 518 * @param connectTimeout The HTTP connect timeout, in milliseconds. 519 * Zero implies no timeout. Must not be negative. 520 */ 521 public void setConnectTimeout(final int connectTimeout) { 522 523 if (connectTimeout < 0) { 524 throw new IllegalArgumentException("The HTTP connect timeout must be zero or positive"); 525 } 526 527 this.connectTimeout = connectTimeout; 528 } 529 530 531 /** 532 * Gets the HTTP response read timeout. 533 * 534 * @return The HTTP response read timeout, in milliseconds. Zero 535 * implies no timeout. 536 */ 537 public int getReadTimeout() { 538 539 return readTimeout; 540 } 541 542 543 /** 544 * Sets the HTTP response read timeout. 545 * 546 * @param readTimeout The HTTP response read timeout, in milliseconds. 547 * Zero implies no timeout. Must not be negative. 548 */ 549 public void setReadTimeout(final int readTimeout) { 550 551 if (readTimeout < 0) { 552 throw new IllegalArgumentException("The HTTP response read timeout must be zero or positive"); 553 } 554 555 this.readTimeout = readTimeout; 556 } 557 558 /** 559 * Returns the proxy to use for this HTTP request. 560 * 561 * @return The connection specific proxy for this request, {@code null} 562 * for the default proxy strategy. 563 */ 564 public Proxy getProxy() { 565 566 return this.proxy; 567 } 568 569 570 /** 571 * Tunnels this HTTP request via the specified {@link Proxy} by 572 * directly configuring the proxy on the {@link java.net.URLConnection}. 573 * The proxy is only used for this instance and bypasses any other 574 * proxy settings (such as set via System properties or 575 * {@link java.net.ProxySelector}). Supplying {@code null} (the 576 * default) reverts to the default proxy strategy of 577 * {@link java.net.URLConnection}. If the goal is to avoid using a 578 * proxy at all supply {@link Proxy#NO_PROXY}. 579 * 580 * @param proxy The connection specific proxy to use, {@code null} to 581 * use the default proxy strategy. 582 * 583 * @see URL#openConnection(Proxy) 584 */ 585 public void setProxy(final Proxy proxy) { 586 587 this.proxy = proxy; 588 } 589 590 591 /** 592 * Gets the boolean setting whether HTTP redirects (requests with 593 * response code 3xx) should be automatically followed. 594 * 595 * @return {@code true} if HTTP redirects are automatically followed, 596 * else {@code false}. 597 */ 598 public boolean getFollowRedirects() { 599 600 return followRedirects; 601 } 602 603 604 /** 605 * Sets whether HTTP redirects (requests with response code 3xx) should 606 * be automatically followed. 607 * 608 * @param follow Whether or not to follow HTTP redirects. 609 */ 610 public void setFollowRedirects(final boolean follow) { 611 612 followRedirects = follow; 613 } 614 615 616 /** 617 * Gets the received validated client X.509 certificate for a received 618 * HTTPS request. 619 * 620 * @return The client X.509 certificate, {@code null} if not specified. 621 */ 622 public X509Certificate getClientX509Certificate() { 623 624 return clientX509Certificate; 625 } 626 627 628 /** 629 * Sets the received validated client X.509 certificate for a received 630 * HTTPS request. 631 * 632 * @param clientX509Certificate The client X.509 certificate, 633 * {@code null} if not specified. 634 */ 635 public void setClientX509Certificate(final X509Certificate clientX509Certificate) { 636 637 this.clientX509Certificate = clientX509Certificate; 638 } 639 640 641 /** 642 * Gets the subject DN of a received validated client X.509 certificate 643 * for a received HTTPS request. 644 * 645 * @return The subject DN, {@code null} if not specified. 646 */ 647 public String getClientX509CertificateSubjectDN() { 648 649 return clientX509CertificateSubjectDN; 650 } 651 652 653 /** 654 * Sets the subject DN of a received validated client X.509 certificate 655 * for a received HTTPS request. 656 * 657 * @param subjectDN The subject DN, {@code null} if not specified. 658 */ 659 public void setClientX509CertificateSubjectDN(final String subjectDN) { 660 661 this.clientX509CertificateSubjectDN = subjectDN; 662 } 663 664 665 /** 666 * Gets the root issuer DN of a received validated client X.509 667 * certificate for a received HTTPS request. 668 * 669 * @return The root DN, {@code null} if not specified. 670 */ 671 public String getClientX509CertificateRootDN() { 672 673 return clientX509CertificateRootDN; 674 } 675 676 677 /** 678 * Sets the root issuer DN of a received validated client X.509 679 * certificate for a received HTTPS request. 680 * 681 * @param rootDN The root DN, {@code null} if not specified. 682 */ 683 public void setClientX509CertificateRootDN(final String rootDN) { 684 685 this.clientX509CertificateRootDN = rootDN; 686 } 687 688 689 /** 690 * Gets the hostname verifier for outgoing HTTPS requests. 691 * 692 * @return The hostname verifier, {@code null} implies use of the 693 * {@link #getDefaultHostnameVerifier() default one}. 694 */ 695 public HostnameVerifier getHostnameVerifier() { 696 697 return hostnameVerifier; 698 } 699 700 701 /** 702 * Sets the hostname verifier for outgoing HTTPS requests. 703 * 704 * @param hostnameVerifier The hostname verifier, {@code null} implies 705 * use of the 706 * {@link #getDefaultHostnameVerifier() default 707 * one}. 708 */ 709 public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) { 710 711 this.hostnameVerifier = hostnameVerifier; 712 } 713 714 715 /** 716 * Gets the SSL factory for outgoing HTTPS requests. 717 * 718 * @return The SSL factory, {@code null} implies of the default one. 719 */ 720 public SSLSocketFactory getSSLSocketFactory() { 721 722 return sslSocketFactory; 723 } 724 725 726 /** 727 * Sets the SSL factory for outgoing HTTPS requests. Use the 728 * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to 729 * set a custom trust store for server and CA certificates and / or a 730 * custom key store for client private keys and certificates, also to 731 * select a specific TLS protocol version. 732 * 733 * @param sslSocketFactory The SSL factory, {@code null} implies use of 734 * the default one. 735 */ 736 public void setSSLSocketFactory(final SSLSocketFactory sslSocketFactory) { 737 738 this.sslSocketFactory = sslSocketFactory; 739 } 740 741 742 /** 743 * Returns the default hostname verifier for all outgoing HTTPS 744 * requests. 745 * 746 * @return The hostname verifier. 747 */ 748 public static HostnameVerifier getDefaultHostnameVerifier() { 749 750 return defaultHostnameVerifier; 751 } 752 753 754 /** 755 * Sets the default hostname verifier for all outgoing HTTPS requests. 756 * Can be overridden on a individual request basis. 757 * 758 * @param defaultHostnameVerifier The hostname verifier. Must not be 759 * {@code null}. 760 */ 761 public static void setDefaultHostnameVerifier(final HostnameVerifier defaultHostnameVerifier) { 762 763 if (defaultHostnameVerifier == null) { 764 throw new IllegalArgumentException("The hostname verifier must not be null"); 765 } 766 767 HTTPRequest.defaultHostnameVerifier = defaultHostnameVerifier; 768 } 769 770 771 /** 772 * Returns the default SSL socket factory for all outgoing HTTPS 773 * requests. 774 * 775 * @return The SSL socket factory. 776 */ 777 public static SSLSocketFactory getDefaultSSLSocketFactory() { 778 779 return defaultSSLSocketFactory; 780 } 781 782 783 /** 784 * Sets the default SSL socket factory for all outgoing HTTPS requests. 785 * Can be overridden on a individual request basis. Use the 786 * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to 787 * set a custom trust store for server and CA certificates and / or a 788 * custom key store for client private keys and certificates, also to 789 * select a specific TLS protocol version. 790 * 791 * @param sslSocketFactory The SSL socket factory. Must not be 792 * {@code null}. 793 */ 794 public static void setDefaultSSLSocketFactory(final SSLSocketFactory sslSocketFactory) { 795 796 if (sslSocketFactory == null) { 797 throw new IllegalArgumentException("The SSL socket factory must not be null"); 798 } 799 800 HTTPRequest.defaultSSLSocketFactory = sslSocketFactory; 801 } 802 803 804 /** 805 * Returns an established HTTP URL connection for this HTTP request. 806 * Deprecated as of v5.31, use {@link #toHttpURLConnection()} with 807 * {@link #setHostnameVerifier} and {@link #setSSLSocketFactory} 808 * instead. 809 * 810 * @param hostnameVerifier The hostname verifier for outgoing HTTPS 811 * requests, {@code null} implies use of the 812 * {@link #getDefaultHostnameVerifier() default 813 * one}. 814 * @param sslSocketFactory The SSL socket factory for HTTPS requests, 815 * {@code null} implies use of the 816 * {@link #getDefaultSSLSocketFactory() default 817 * one}. 818 * 819 * @return The HTTP URL connection, with the request sent and ready to 820 * read the response. 821 * 822 * @throws IOException If the HTTP request couldn't be made, due to a 823 * network or other error. 824 */ 825 @Deprecated 826 public HttpURLConnection toHttpURLConnection(final HostnameVerifier hostnameVerifier, 827 final SSLSocketFactory sslSocketFactory) 828 throws IOException { 829 830 HostnameVerifier savedHostnameVerifier = getHostnameVerifier(); 831 SSLSocketFactory savedSSLFactory = getSSLSocketFactory(); 832 833 try { 834 // Set for this HTTP URL connection only 835 setHostnameVerifier(hostnameVerifier); 836 setSSLSocketFactory(sslSocketFactory); 837 838 return toHttpURLConnection(); 839 840 } finally { 841 setHostnameVerifier(savedHostnameVerifier); 842 setSSLSocketFactory(savedSSLFactory); 843 } 844 } 845 846 847 /** 848 * Returns an established HTTP URL connection for this HTTP request. 849 * 850 * @return The HTTP URL connection, with the request sent and ready to 851 * read the response. 852 * 853 * @throws IOException If the HTTP request couldn't be made, due to a 854 * network or other error. 855 */ 856 public HttpURLConnection toHttpURLConnection() 857 throws IOException { 858 859 URL finalURL = url; 860 861 if (query != null && (method.equals(HTTPRequest.Method.GET) || method.equals(Method.DELETE))) { 862 863 // Append query string 864 StringBuilder sb = new StringBuilder(url.toString()); 865 sb.append('?'); 866 sb.append(query); 867 868 try { 869 finalURL = new URL(sb.toString()); 870 871 } catch (MalformedURLException e) { 872 873 throw new IOException("Couldn't append query string: " + e.getMessage(), e); 874 } 875 } 876 877 if (fragment != null) { 878 879 // Append raw fragment 880 StringBuilder sb = new StringBuilder(finalURL.toString()); 881 sb.append('#'); 882 sb.append(fragment); 883 884 try { 885 finalURL = new URL(sb.toString()); 886 887 } catch (MalformedURLException e) { 888 889 throw new IOException("Couldn't append raw fragment: " + e.getMessage(), e); 890 } 891 } 892 893 HttpURLConnection conn = (HttpURLConnection) (proxy == null ? finalURL.openConnection() : finalURL.openConnection(proxy)); 894 895 if (conn instanceof HttpsURLConnection) { 896 HttpsURLConnection sslConn = (HttpsURLConnection)conn; 897 sslConn.setHostnameVerifier(hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier()); 898 sslConn.setSSLSocketFactory(sslSocketFactory != null ? sslSocketFactory : getDefaultSSLSocketFactory()); 899 } 900 901 for (Map.Entry<String,List<String>> header: getHeaderMap().entrySet()) { 902 for (String headerValue: header.getValue()) { 903 conn.addRequestProperty(header.getKey(), headerValue); 904 } 905 } 906 907 conn.setRequestMethod(method.name()); 908 conn.setConnectTimeout(connectTimeout); 909 conn.setReadTimeout(readTimeout); 910 conn.setInstanceFollowRedirects(followRedirects); 911 912 if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) { 913 914 conn.setDoOutput(true); 915 916 if (getEntityContentType() != null) 917 conn.setRequestProperty("Content-Type", getEntityContentType().toString()); 918 919 if (query != null) { 920 try { 921 OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); 922 writer.write(query); 923 writer.close(); 924 } catch (IOException e) { 925 closeStreams(conn); 926 throw e; // Rethrow 927 } 928 } 929 } 930 931 return conn; 932 } 933 934 935 /** 936 * Sends this HTTP request to the request URL and retrieves the 937 * resulting HTTP response. Deprecated as of v5.31, use 938 * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and 939 * {@link #setSSLSocketFactory} instead. 940 * 941 * @param hostnameVerifier The hostname verifier for outgoing HTTPS 942 * requests, {@code null} implies use of the 943 * {@link #getDefaultHostnameVerifier() default 944 * one}. 945 * @param sslSocketFactory The SSL socket factory for HTTPS requests, 946 * {@code null} implies use of the 947 * {@link #getDefaultSSLSocketFactory() default 948 * one}. 949 * 950 * @return The resulting HTTP response. 951 * 952 * @throws IOException If the HTTP request couldn't be made, due to a 953 * network or other error. 954 */ 955 @Deprecated 956 public HTTPResponse send(final HostnameVerifier hostnameVerifier, 957 final SSLSocketFactory sslSocketFactory) 958 throws IOException { 959 960 HostnameVerifier savedHostnameVerifier = getHostnameVerifier(); 961 SSLSocketFactory savedSSLFactory = getSSLSocketFactory(); 962 963 try { 964 // Set for this HTTP URL connection only 965 setHostnameVerifier(hostnameVerifier); 966 setSSLSocketFactory(sslSocketFactory); 967 968 return send(); 969 970 } finally { 971 setHostnameVerifier(savedHostnameVerifier); 972 setSSLSocketFactory(savedSSLFactory); 973 } 974 } 975 976 977 /** 978 * Sends this HTTP request to the request URL and retrieves the 979 * resulting HTTP response. 980 * 981 * @return The resulting HTTP response. 982 * 983 * @throws IOException If the HTTP request couldn't be made, due to a 984 * network or other error. 985 */ 986 public HTTPResponse send() 987 throws IOException { 988 989 HttpURLConnection conn = toHttpURLConnection(); 990 991 int statusCode; 992 993 BufferedReader reader; 994 995 try { 996 // Open a connection, then send method and headers 997 reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); 998 999 // The next step is to get the status 1000 statusCode = conn.getResponseCode(); 1001 1002 } catch (IOException e) { 1003 1004 // HttpUrlConnection will throw an IOException if any 1005 // 4XX response is sent. If we request the status 1006 // again, this time the internal status will be 1007 // properly set, and we'll be able to retrieve it. 1008 statusCode = conn.getResponseCode(); 1009 1010 if (statusCode == -1) { 1011 throw e; // Rethrow IO exception 1012 } else { 1013 // HTTP status code indicates the response got 1014 // through, read the content but using error stream 1015 InputStream errStream = conn.getErrorStream(); 1016 1017 if (errStream != null) { 1018 // We have useful HTTP error body 1019 reader = new BufferedReader(new InputStreamReader(errStream)); 1020 } else { 1021 // No content, set to empty string 1022 reader = new BufferedReader(new StringReader("")); 1023 } 1024 } 1025 } 1026 1027 StringBuilder body = new StringBuilder(); 1028 String line; 1029 while ((line = reader.readLine()) != null) { 1030 body.append(line); 1031 body.append(System.getProperty("line.separator")); 1032 } 1033 reader.close(); 1034 1035 1036 HTTPResponse response = new HTTPResponse(statusCode); 1037 1038 response.setStatusMessage(conn.getResponseMessage()); 1039 1040 // Set headers 1041 for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) { 1042 1043 if (responseHeader.getKey() == null) { 1044 continue; // skip header 1045 } 1046 1047 List<String> values = responseHeader.getValue(); 1048 if (values == null || values.isEmpty() || values.get(0) == null) { 1049 continue; // skip header 1050 } 1051 1052 response.setHeader(responseHeader.getKey(), values.toArray(new String[]{})); 1053 } 1054 1055 closeStreams(conn); 1056 1057 final String bodyContent = body.toString(); 1058 if (! bodyContent.isEmpty()) 1059 response.setContent(bodyContent); 1060 1061 return response; 1062 } 1063 1064 1065 /** 1066 * Closes the input, output and error streams of the specified HTTP URL 1067 * connection. No attempt is made to close the underlying socket with 1068 * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live). 1069 * See http://techblog.bozho.net/caveats-of-httpurlconnection/ 1070 * 1071 * @param conn The HTTP URL connection. May be {@code null}. 1072 */ 1073 private static void closeStreams(final HttpURLConnection conn) { 1074 1075 if (conn == null) { 1076 return; 1077 } 1078 1079 try { 1080 if (conn.getInputStream() != null) { 1081 conn.getInputStream().close(); 1082 } 1083 } catch (Exception e) { 1084 // ignore 1085 } 1086 1087 try { 1088 if (conn.getOutputStream() != null) { 1089 conn.getOutputStream().close(); 1090 } 1091 } catch (Exception e) { 1092 // ignore 1093 } 1094 1095 try { 1096 if (conn.getErrorStream() != null) { 1097 conn.getOutputStream().close(); 1098 } 1099 } catch (Exception e) { 1100 // ignore 1101 } 1102 } 1103}