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