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