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 getHeader("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 getHeader("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,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,String> header: getHeaders().entrySet()) { 781 conn.setRequestProperty(header.getKey(), header.getValue()); 782 } 783 784 conn.setRequestMethod(method.name()); 785 conn.setConnectTimeout(connectTimeout); 786 conn.setReadTimeout(readTimeout); 787 conn.setInstanceFollowRedirects(followRedirects); 788 789 if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) { 790 791 conn.setDoOutput(true); 792 793 if (getContentType() != null) 794 conn.setRequestProperty("Content-Type", getContentType().toString()); 795 796 if (query != null) { 797 try { 798 OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); 799 writer.write(query); 800 writer.close(); 801 } catch (IOException e) { 802 closeStreams(conn); 803 throw e; // Rethrow 804 } 805 } 806 } 807 808 return conn; 809 } 810 811 812 /** 813 * Sends this HTTP request to the request URL and retrieves the 814 * resulting HTTP response. Deprecated as of v5.31, use 815 * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and 816 * {@link #setSSLSocketFactory} instead. 817 * 818 * @param hostnameVerifier The hostname verifier for outgoing HTTPS 819 * requests, {@code null} implies use of the 820 * {@link #getDefaultHostnameVerifier() default 821 * one}. 822 * @param sslSocketFactory The SSL socket factory for HTTPS requests, 823 * {@code null} implies use of the 824 * {@link #getDefaultSSLSocketFactory() default 825 * one}. 826 * 827 * @return The resulting HTTP response. 828 * 829 * @throws IOException If the HTTP request couldn't be made, due to a 830 * network or other error. 831 */ 832 @Deprecated 833 public HTTPResponse send(final HostnameVerifier hostnameVerifier, 834 final SSLSocketFactory sslSocketFactory) 835 throws IOException { 836 837 HostnameVerifier savedHostnameVerifier = getHostnameVerifier(); 838 SSLSocketFactory savedSSLFactory = getSSLSocketFactory(); 839 840 try { 841 // Set for this HTTP URL connection only 842 setHostnameVerifier(hostnameVerifier); 843 setSSLSocketFactory(sslSocketFactory); 844 845 return send(); 846 847 } finally { 848 setHostnameVerifier(savedHostnameVerifier); 849 setSSLSocketFactory(savedSSLFactory); 850 } 851 } 852 853 854 /** 855 * Sends this HTTP request to the request URL and retrieves the 856 * resulting HTTP response. 857 * 858 * @return The resulting HTTP response. 859 * 860 * @throws IOException If the HTTP request couldn't be made, due to a 861 * network or other error. 862 */ 863 public HTTPResponse send() 864 throws IOException { 865 866 HttpURLConnection conn = toHttpURLConnection(); 867 868 int statusCode; 869 870 BufferedReader reader; 871 872 try { 873 // Open a connection, then send method and headers 874 reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), Charset.forName("UTF-8"))); 875 876 // The next step is to get the status 877 statusCode = conn.getResponseCode(); 878 879 } catch (IOException e) { 880 881 // HttpUrlConnection will throw an IOException if any 882 // 4XX response is sent. If we request the status 883 // again, this time the internal status will be 884 // properly set, and we'll be able to retrieve it. 885 statusCode = conn.getResponseCode(); 886 887 if (statusCode == -1) { 888 throw e; // Rethrow IO exception 889 } else { 890 // HTTP status code indicates the response got 891 // through, read the content but using error stream 892 InputStream errStream = conn.getErrorStream(); 893 894 if (errStream != null) { 895 // We have useful HTTP error body 896 reader = new BufferedReader(new InputStreamReader(errStream)); 897 } else { 898 // No content, set to empty string 899 reader = new BufferedReader(new StringReader("")); 900 } 901 } 902 } 903 904 StringBuilder body = new StringBuilder(); 905 String line; 906 while ((line = reader.readLine()) != null) { 907 body.append(line); 908 body.append(System.getProperty("line.separator")); 909 } 910 reader.close(); 911 912 913 HTTPResponse response = new HTTPResponse(statusCode); 914 915 response.setStatusMessage(conn.getResponseMessage()); 916 917 // Set headers 918 for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) { 919 920 if (responseHeader.getKey() == null) { 921 continue; // skip header 922 } 923 924 List<String> values = responseHeader.getValue(); 925 if (values == null || values.isEmpty() || values.get(0) == null) { 926 continue; // skip header 927 } 928 929 response.setHeader(responseHeader.getKey(), values.get(0)); 930 } 931 932 closeStreams(conn); 933 934 final String bodyContent = body.toString(); 935 if (! bodyContent.isEmpty()) 936 response.setContent(bodyContent); 937 938 return response; 939 } 940 941 942 /** 943 * Closes the input, output and error streams of the specified HTTP URL 944 * connection. No attempt is made to close the underlying socket with 945 * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live). 946 * See http://techblog.bozho.net/caveats-of-httpurlconnection/ 947 * 948 * @param conn The HTTP URL connection. May be {@code null}. 949 */ 950 private static void closeStreams(final HttpURLConnection conn) { 951 952 if (conn == null) { 953 return; 954 } 955 956 try { 957 if (conn.getInputStream() != null) { 958 conn.getInputStream().close(); 959 } 960 } catch (Exception e) { 961 // ignore 962 } 963 964 try { 965 if (conn.getOutputStream() != null) { 966 conn.getOutputStream().close(); 967 } 968 } catch (Exception e) { 969 // ignore 970 } 971 972 try { 973 if (conn.getErrorStream() != null) { 974 conn.getOutputStream().close(); 975 } 976 } catch (Exception e) { 977 // ignore 978 } 979 } 980}