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