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