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