001/* 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020package org.apache.hadoop.hbase.rest.client; 021 022import java.io.ByteArrayInputStream; 023import java.io.ByteArrayOutputStream; 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.net.URI; 029import java.net.URISyntaxException; 030import java.net.URL; 031import java.security.KeyManagementException; 032import java.security.KeyStore; 033import java.security.KeyStoreException; 034import java.security.NoSuchAlgorithmException; 035import java.security.cert.CertificateException; 036import java.util.Collections; 037import java.util.Map; 038import java.util.Optional; 039import java.util.concurrent.ConcurrentHashMap; 040import javax.net.ssl.SSLContext; 041import org.apache.hadoop.security.authentication.client.AuthenticatedURL; 042import org.apache.hadoop.security.authentication.client.AuthenticationException; 043import org.apache.hadoop.security.authentication.client.KerberosAuthenticator; 044import org.apache.http.Header; 045import org.apache.http.HttpResponse; 046import org.apache.http.HttpStatus; 047import org.apache.http.client.HttpClient; 048import org.apache.http.client.config.RequestConfig; 049import org.apache.http.client.methods.HttpDelete; 050import org.apache.http.client.methods.HttpGet; 051import org.apache.http.client.methods.HttpHead; 052import org.apache.http.client.methods.HttpPost; 053import org.apache.http.client.methods.HttpPut; 054import org.apache.http.client.methods.HttpUriRequest; 055import org.apache.http.entity.InputStreamEntity; 056import org.apache.http.impl.client.HttpClientBuilder; 057import org.apache.http.impl.client.HttpClients; 058import org.apache.http.message.BasicHeader; 059import org.apache.http.ssl.SSLContexts; 060import org.apache.http.util.EntityUtils; 061import org.apache.yetus.audience.InterfaceAudience; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065/** 066 * A wrapper around HttpClient which provides some useful function and 067 * semantics for interacting with the REST gateway. 068 */ 069@InterfaceAudience.Public 070public class Client { 071 public static final Header[] EMPTY_HEADER_ARRAY = new Header[0]; 072 073 private static final Logger LOG = LoggerFactory.getLogger(Client.class); 074 075 private HttpClient httpClient; 076 private Cluster cluster; 077 private boolean sslEnabled; 078 private HttpResponse resp; 079 private HttpGet httpGet = null; 080 081 private Map<String, String> extraHeaders; 082 083 private static final String AUTH_COOKIE = "hadoop.auth"; 084 private static final String AUTH_COOKIE_EQ = AUTH_COOKIE + "="; 085 private static final String COOKIE = "Cookie"; 086 087 /** 088 * Default Constructor 089 */ 090 public Client() { 091 this(null); 092 } 093 094 private void initialize(Cluster cluster, boolean sslEnabled, Optional<KeyStore> trustStore) { 095 this.cluster = cluster; 096 this.sslEnabled = sslEnabled; 097 extraHeaders = new ConcurrentHashMap<>(); 098 String clspath = System.getProperty("java.class.path"); 099 LOG.debug("classpath " + clspath); 100 HttpClientBuilder httpClientBuilder = HttpClients.custom(); 101 102 RequestConfig requestConfig = RequestConfig.custom(). 103 setConnectTimeout(2000).build(); 104 httpClientBuilder.setDefaultRequestConfig(requestConfig); 105 106 // Since HBASE-25267 we don't use the deprecated DefaultHttpClient anymore. 107 // The new http client would decompress the gzip content automatically. 108 // In order to keep the original behaviour of this public class, we disable 109 // automatic content compression. 110 httpClientBuilder.disableContentCompression(); 111 112 if(sslEnabled && trustStore.isPresent()) { 113 try { 114 SSLContext sslcontext = 115 SSLContexts.custom().loadTrustMaterial(trustStore.get(), null).build(); 116 httpClientBuilder.setSSLContext(sslcontext); 117 } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { 118 throw new ClientTrustStoreInitializationException("Error while processing truststore", e); 119 } 120 } 121 122 this.httpClient = httpClientBuilder.build(); 123 } 124 125 /** 126 * Constructor 127 * @param cluster the cluster definition 128 */ 129 public Client(Cluster cluster) { 130 this(cluster, false); 131 } 132 133 /** 134 * Constructor 135 * @param cluster the cluster definition 136 * @param sslEnabled enable SSL or not 137 */ 138 public Client(Cluster cluster, boolean sslEnabled) { 139 initialize(cluster, sslEnabled, Optional.empty()); 140 } 141 142 /** 143 * Constructor, allowing to define custom trust store (only for SSL connections) 144 * 145 * @param cluster the cluster definition 146 * @param trustStorePath custom trust store to use for SSL connections 147 * @param trustStorePassword password to use for custom trust store 148 * @param trustStoreType type of custom trust store 149 * 150 * @throws ClientTrustStoreInitializationException if the trust store file can not be loaded 151 */ 152 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "OBL_UNSATISFIED_OBLIGATION", 153 justification = "Correct use of try-with-resource") 154 public Client(Cluster cluster, String trustStorePath, 155 Optional<String> trustStorePassword, Optional<String> trustStoreType) { 156 157 char[] password = trustStorePassword.map(String::toCharArray).orElse(null); 158 String type = trustStoreType.orElse(KeyStore.getDefaultType()); 159 160 KeyStore trustStore; 161 try(FileInputStream inputStream = new FileInputStream(new File(trustStorePath))) { 162 trustStore = KeyStore.getInstance(type); 163 trustStore.load(inputStream, password); 164 } catch (KeyStoreException e) { 165 throw new ClientTrustStoreInitializationException( 166 "Invalid trust store type: " + type, e); 167 } catch (CertificateException | NoSuchAlgorithmException | IOException e) { 168 throw new ClientTrustStoreInitializationException( 169 "Trust store load error: " + trustStorePath, e); 170 } 171 172 initialize(cluster, true, Optional.of(trustStore)); 173 } 174 175 /** 176 * Shut down the client. Close any open persistent connections. 177 */ 178 public void shutdown() { 179 } 180 181 /** 182 * @return the wrapped HttpClient 183 */ 184 public HttpClient getHttpClient() { 185 return httpClient; 186 } 187 188 /** 189 * Add extra headers. These extra headers will be applied to all http 190 * methods before they are removed. If any header is not used any more, 191 * client needs to remove it explicitly. 192 */ 193 public void addExtraHeader(final String name, final String value) { 194 extraHeaders.put(name, value); 195 } 196 197 /** 198 * Get an extra header value. 199 */ 200 public String getExtraHeader(final String name) { 201 return extraHeaders.get(name); 202 } 203 204 /** 205 * Get all extra headers (read-only). 206 */ 207 public Map<String, String> getExtraHeaders() { 208 return Collections.unmodifiableMap(extraHeaders); 209 } 210 211 /** 212 * Remove an extra header. 213 */ 214 public void removeExtraHeader(final String name) { 215 extraHeaders.remove(name); 216 } 217 218 /** 219 * Execute a transaction method given only the path. Will select at random 220 * one of the members of the supplied cluster definition and iterate through 221 * the list until a transaction can be successfully completed. The 222 * definition of success here is a complete HTTP transaction, irrespective 223 * of result code. 224 * @param cluster the cluster definition 225 * @param method the transaction method 226 * @param headers HTTP header values to send 227 * @param path the properly urlencoded path 228 * @return the HTTP response code 229 * @throws IOException 230 */ 231 public HttpResponse executePathOnly(Cluster cluster, HttpUriRequest method, 232 Header[] headers, String path) throws IOException { 233 IOException lastException; 234 if (cluster.nodes.size() < 1) { 235 throw new IOException("Cluster is empty"); 236 } 237 int start = (int)Math.round((cluster.nodes.size() - 1) * Math.random()); 238 int i = start; 239 do { 240 cluster.lastHost = cluster.nodes.get(i); 241 try { 242 StringBuilder sb = new StringBuilder(); 243 if (sslEnabled) { 244 sb.append("https://"); 245 } else { 246 sb.append("http://"); 247 } 248 sb.append(cluster.lastHost); 249 sb.append(path); 250 URI uri = new URI(sb.toString()); 251 if (method instanceof HttpPut) { 252 HttpPut put = new HttpPut(uri); 253 put.setEntity(((HttpPut) method).getEntity()); 254 put.setHeaders(method.getAllHeaders()); 255 method = put; 256 } else if (method instanceof HttpGet) { 257 method = new HttpGet(uri); 258 } else if (method instanceof HttpHead) { 259 method = new HttpHead(uri); 260 } else if (method instanceof HttpDelete) { 261 method = new HttpDelete(uri); 262 } else if (method instanceof HttpPost) { 263 HttpPost post = new HttpPost(uri); 264 post.setEntity(((HttpPost) method).getEntity()); 265 post.setHeaders(method.getAllHeaders()); 266 method = post; 267 } 268 return executeURI(method, headers, uri.toString()); 269 } catch (IOException e) { 270 lastException = e; 271 } catch (URISyntaxException use) { 272 lastException = new IOException(use); 273 } 274 } while (++i != start && i < cluster.nodes.size()); 275 throw lastException; 276 } 277 278 /** 279 * Execute a transaction method given a complete URI. 280 * @param method the transaction method 281 * @param headers HTTP header values to send 282 * @param uri a properly urlencoded URI 283 * @return the HTTP response code 284 * @throws IOException 285 */ 286 public HttpResponse executeURI(HttpUriRequest method, Header[] headers, String uri) 287 throws IOException { 288 // method.setURI(new URI(uri, true)); 289 for (Map.Entry<String, String> e: extraHeaders.entrySet()) { 290 method.addHeader(e.getKey(), e.getValue()); 291 } 292 if (headers != null) { 293 for (Header header: headers) { 294 method.addHeader(header); 295 } 296 } 297 long startTime = System.currentTimeMillis(); 298 if (resp != null) EntityUtils.consumeQuietly(resp.getEntity()); 299 resp = httpClient.execute(method); 300 if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { 301 // Authentication error 302 LOG.debug("Performing negotiation with the server."); 303 negotiate(method, uri); 304 resp = httpClient.execute(method); 305 } 306 307 long endTime = System.currentTimeMillis(); 308 if (LOG.isTraceEnabled()) { 309 LOG.trace(method.getMethod() + " " + uri + " " + resp.getStatusLine().getStatusCode() + " " + 310 resp.getStatusLine().getReasonPhrase() + " in " + (endTime - startTime) + " ms"); 311 } 312 return resp; 313 } 314 315 /** 316 * Execute a transaction method. Will call either <tt>executePathOnly</tt> 317 * or <tt>executeURI</tt> depending on whether a path only is supplied in 318 * 'path', or if a complete URI is passed instead, respectively. 319 * @param cluster the cluster definition 320 * @param method the HTTP method 321 * @param headers HTTP header values to send 322 * @param path the properly urlencoded path or URI 323 * @return the HTTP response code 324 * @throws IOException 325 */ 326 public HttpResponse execute(Cluster cluster, HttpUriRequest method, Header[] headers, 327 String path) throws IOException { 328 if (path.startsWith("/")) { 329 return executePathOnly(cluster, method, headers, path); 330 } 331 return executeURI(method, headers, path); 332 } 333 334 /** 335 * Initiate client side Kerberos negotiation with the server. 336 * @param method method to inject the authentication token into. 337 * @param uri the String to parse as a URL. 338 * @throws IOException if unknown protocol is found. 339 */ 340 private void negotiate(HttpUriRequest method, String uri) throws IOException { 341 try { 342 AuthenticatedURL.Token token = new AuthenticatedURL.Token(); 343 KerberosAuthenticator authenticator = new KerberosAuthenticator(); 344 authenticator.authenticate(new URL(uri), token); 345 // Inject the obtained negotiated token in the method cookie 346 injectToken(method, token); 347 } catch (AuthenticationException e) { 348 LOG.error("Failed to negotiate with the server.", e); 349 throw new IOException(e); 350 } 351 } 352 353 /** 354 * Helper method that injects an authentication token to send with the method. 355 * @param method method to inject the authentication token into. 356 * @param token authentication token to inject. 357 */ 358 private void injectToken(HttpUriRequest method, AuthenticatedURL.Token token) { 359 String t = token.toString(); 360 if (t != null) { 361 if (!t.startsWith("\"")) { 362 t = "\"" + t + "\""; 363 } 364 method.addHeader(COOKIE, AUTH_COOKIE_EQ + t); 365 } 366 } 367 368 /** 369 * @return the cluster definition 370 */ 371 public Cluster getCluster() { 372 return cluster; 373 } 374 375 /** 376 * @param cluster the cluster definition 377 */ 378 public void setCluster(Cluster cluster) { 379 this.cluster = cluster; 380 } 381 382 /** 383 * Send a HEAD request 384 * @param path the path or URI 385 * @return a Response object with response detail 386 * @throws IOException 387 */ 388 public Response head(String path) throws IOException { 389 return head(cluster, path, null); 390 } 391 392 /** 393 * Send a HEAD request 394 * @param cluster the cluster definition 395 * @param path the path or URI 396 * @param headers the HTTP headers to include in the request 397 * @return a Response object with response detail 398 * @throws IOException 399 */ 400 public Response head(Cluster cluster, String path, Header[] headers) 401 throws IOException { 402 HttpHead method = new HttpHead(path); 403 try { 404 HttpResponse resp = execute(cluster, method, null, path); 405 return new Response(resp.getStatusLine().getStatusCode(), resp.getAllHeaders(), null); 406 } finally { 407 method.releaseConnection(); 408 } 409 } 410 411 /** 412 * Send a GET request 413 * @param path the path or URI 414 * @return a Response object with response detail 415 * @throws IOException 416 */ 417 public Response get(String path) throws IOException { 418 return get(cluster, path); 419 } 420 421 /** 422 * Send a GET request 423 * @param cluster the cluster definition 424 * @param path the path or URI 425 * @return a Response object with response detail 426 * @throws IOException 427 */ 428 public Response get(Cluster cluster, String path) throws IOException { 429 return get(cluster, path, EMPTY_HEADER_ARRAY); 430 } 431 432 /** 433 * Send a GET request 434 * @param path the path or URI 435 * @param accept Accept header value 436 * @return a Response object with response detail 437 * @throws IOException 438 */ 439 public Response get(String path, String accept) throws IOException { 440 return get(cluster, path, accept); 441 } 442 443 /** 444 * Send a GET request 445 * @param cluster the cluster definition 446 * @param path the path or URI 447 * @param accept Accept header value 448 * @return a Response object with response detail 449 * @throws IOException 450 */ 451 public Response get(Cluster cluster, String path, String accept) 452 throws IOException { 453 Header[] headers = new Header[1]; 454 headers[0] = new BasicHeader("Accept", accept); 455 return get(cluster, path, headers); 456 } 457 458 /** 459 * Send a GET request 460 * @param path the path or URI 461 * @param headers the HTTP headers to include in the request, 462 * <tt>Accept</tt> must be supplied 463 * @return a Response object with response detail 464 * @throws IOException 465 */ 466 public Response get(String path, Header[] headers) throws IOException { 467 return get(cluster, path, headers); 468 } 469 470 /** 471 * Returns the response body of the HTTPResponse, if any, as an array of bytes. 472 * If response body is not available or cannot be read, returns <tt>null</tt> 473 * 474 * Note: This will cause the entire response body to be buffered in memory. A 475 * malicious server may easily exhaust all the VM memory. It is strongly 476 * recommended, to use getResponseAsStream if the content length of the response 477 * is unknown or reasonably large. 478 * 479 * @param resp HttpResponse 480 * @return The response body, null if body is empty 481 * @throws IOException If an I/O (transport) problem occurs while obtaining the 482 * response body. 483 */ 484 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = 485 "NP_LOAD_OF_KNOWN_NULL_VALUE", justification = "null is possible return value") 486 public static byte[] getResponseBody(HttpResponse resp) throws IOException { 487 if (resp.getEntity() == null) return null; 488 try (InputStream instream = resp.getEntity().getContent()) { 489 if (instream != null) { 490 long contentLength = resp.getEntity().getContentLength(); 491 if (contentLength > Integer.MAX_VALUE) { 492 //guard integer cast from overflow 493 throw new IOException("Content too large to be buffered: " + contentLength +" bytes"); 494 } 495 ByteArrayOutputStream outstream = new ByteArrayOutputStream( 496 contentLength > 0 ? (int) contentLength : 4*1024); 497 byte[] buffer = new byte[4096]; 498 int len; 499 while ((len = instream.read(buffer)) > 0) { 500 outstream.write(buffer, 0, len); 501 } 502 outstream.close(); 503 return outstream.toByteArray(); 504 } 505 return null; 506 } 507 } 508 509 /** 510 * Send a GET request 511 * @param c the cluster definition 512 * @param path the path or URI 513 * @param headers the HTTP headers to include in the request 514 * @return a Response object with response detail 515 * @throws IOException 516 */ 517 public Response get(Cluster c, String path, Header[] headers) 518 throws IOException { 519 if (httpGet != null) { 520 httpGet.releaseConnection(); 521 } 522 httpGet = new HttpGet(path); 523 HttpResponse resp = execute(c, httpGet, headers, path); 524 return new Response(resp.getStatusLine().getStatusCode(), resp.getAllHeaders(), 525 resp, resp.getEntity() == null ? null : resp.getEntity().getContent()); 526 } 527 528 /** 529 * Send a PUT request 530 * @param path the path or URI 531 * @param contentType the content MIME type 532 * @param content the content bytes 533 * @return a Response object with response detail 534 * @throws IOException 535 */ 536 public Response put(String path, String contentType, byte[] content) 537 throws IOException { 538 return put(cluster, path, contentType, content); 539 } 540 541 /** 542 * Send a PUT request 543 * @param path the path or URI 544 * @param contentType the content MIME type 545 * @param content the content bytes 546 * @param extraHdr extra Header to send 547 * @return a Response object with response detail 548 * @throws IOException 549 */ 550 public Response put(String path, String contentType, byte[] content, Header extraHdr) 551 throws IOException { 552 return put(cluster, path, contentType, content, extraHdr); 553 } 554 555 /** 556 * Send a PUT request 557 * @param cluster the cluster definition 558 * @param path the path or URI 559 * @param contentType the content MIME type 560 * @param content the content bytes 561 * @return a Response object with response detail 562 * @throws IOException for error 563 */ 564 public Response put(Cluster cluster, String path, String contentType, 565 byte[] content) throws IOException { 566 Header[] headers = new Header[1]; 567 headers[0] = new BasicHeader("Content-Type", contentType); 568 return put(cluster, path, headers, content); 569 } 570 571 /** 572 * Send a PUT request 573 * @param cluster the cluster definition 574 * @param path the path or URI 575 * @param contentType the content MIME type 576 * @param content the content bytes 577 * @param extraHdr additional Header to send 578 * @return a Response object with response detail 579 * @throws IOException for error 580 */ 581 public Response put(Cluster cluster, String path, String contentType, 582 byte[] content, Header extraHdr) throws IOException { 583 int cnt = extraHdr == null ? 1 : 2; 584 Header[] headers = new Header[cnt]; 585 headers[0] = new BasicHeader("Content-Type", contentType); 586 if (extraHdr != null) { 587 headers[1] = extraHdr; 588 } 589 return put(cluster, path, headers, content); 590 } 591 592 /** 593 * Send a PUT request 594 * @param path the path or URI 595 * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be 596 * supplied 597 * @param content the content bytes 598 * @return a Response object with response detail 599 * @throws IOException 600 */ 601 public Response put(String path, Header[] headers, byte[] content) 602 throws IOException { 603 return put(cluster, path, headers, content); 604 } 605 606 /** 607 * Send a PUT request 608 * @param cluster the cluster definition 609 * @param path the path or URI 610 * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be 611 * supplied 612 * @param content the content bytes 613 * @return a Response object with response detail 614 * @throws IOException 615 */ 616 public Response put(Cluster cluster, String path, Header[] headers, 617 byte[] content) throws IOException { 618 HttpPut method = new HttpPut(path); 619 try { 620 method.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), content.length)); 621 HttpResponse resp = execute(cluster, method, headers, path); 622 headers = resp.getAllHeaders(); 623 content = getResponseBody(resp); 624 return new Response(resp.getStatusLine().getStatusCode(), headers, content); 625 } finally { 626 method.releaseConnection(); 627 } 628 } 629 630 /** 631 * Send a POST request 632 * @param path the path or URI 633 * @param contentType the content MIME type 634 * @param content the content bytes 635 * @return a Response object with response detail 636 * @throws IOException 637 */ 638 public Response post(String path, String contentType, byte[] content) 639 throws IOException { 640 return post(cluster, path, contentType, content); 641 } 642 643 /** 644 * Send a POST request 645 * @param path the path or URI 646 * @param contentType the content MIME type 647 * @param content the content bytes 648 * @param extraHdr additional Header to send 649 * @return a Response object with response detail 650 * @throws IOException 651 */ 652 public Response post(String path, String contentType, byte[] content, Header extraHdr) 653 throws IOException { 654 return post(cluster, path, contentType, content, extraHdr); 655 } 656 657 /** 658 * Send a POST request 659 * @param cluster the cluster definition 660 * @param path the path or URI 661 * @param contentType the content MIME type 662 * @param content the content bytes 663 * @return a Response object with response detail 664 * @throws IOException for error 665 */ 666 public Response post(Cluster cluster, String path, String contentType, 667 byte[] content) throws IOException { 668 Header[] headers = new Header[1]; 669 headers[0] = new BasicHeader("Content-Type", contentType); 670 return post(cluster, path, headers, content); 671 } 672 673 /** 674 * Send a POST request 675 * @param cluster the cluster definition 676 * @param path the path or URI 677 * @param contentType the content MIME type 678 * @param content the content bytes 679 * @param extraHdr additional Header to send 680 * @return a Response object with response detail 681 * @throws IOException for error 682 */ 683 public Response post(Cluster cluster, String path, String contentType, 684 byte[] content, Header extraHdr) throws IOException { 685 int cnt = extraHdr == null ? 1 : 2; 686 Header[] headers = new Header[cnt]; 687 headers[0] = new BasicHeader("Content-Type", contentType); 688 if (extraHdr != null) { 689 headers[1] = extraHdr; 690 } 691 return post(cluster, path, headers, content); 692 } 693 694 /** 695 * Send a POST request 696 * @param path the path or URI 697 * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be 698 * supplied 699 * @param content the content bytes 700 * @return a Response object with response detail 701 * @throws IOException 702 */ 703 public Response post(String path, Header[] headers, byte[] content) 704 throws IOException { 705 return post(cluster, path, headers, content); 706 } 707 708 /** 709 * Send a POST request 710 * @param cluster the cluster definition 711 * @param path the path or URI 712 * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be 713 * supplied 714 * @param content the content bytes 715 * @return a Response object with response detail 716 * @throws IOException 717 */ 718 public Response post(Cluster cluster, String path, Header[] headers, 719 byte[] content) throws IOException { 720 HttpPost method = new HttpPost(path); 721 try { 722 method.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), content.length)); 723 HttpResponse resp = execute(cluster, method, headers, path); 724 headers = resp.getAllHeaders(); 725 content = getResponseBody(resp); 726 return new Response(resp.getStatusLine().getStatusCode(), headers, content); 727 } finally { 728 method.releaseConnection(); 729 } 730 } 731 732 /** 733 * Send a DELETE request 734 * @param path the path or URI 735 * @return a Response object with response detail 736 * @throws IOException 737 */ 738 public Response delete(String path) throws IOException { 739 return delete(cluster, path); 740 } 741 742 /** 743 * Send a DELETE request 744 * @param path the path or URI 745 * @param extraHdr additional Header to send 746 * @return a Response object with response detail 747 * @throws IOException 748 */ 749 public Response delete(String path, Header extraHdr) throws IOException { 750 return delete(cluster, path, extraHdr); 751 } 752 753 /** 754 * Send a DELETE request 755 * @param cluster the cluster definition 756 * @param path the path or URI 757 * @return a Response object with response detail 758 * @throws IOException for error 759 */ 760 public Response delete(Cluster cluster, String path) throws IOException { 761 HttpDelete method = new HttpDelete(path); 762 try { 763 HttpResponse resp = execute(cluster, method, null, path); 764 Header[] headers = resp.getAllHeaders(); 765 byte[] content = getResponseBody(resp); 766 return new Response(resp.getStatusLine().getStatusCode(), headers, content); 767 } finally { 768 method.releaseConnection(); 769 } 770 } 771 772 /** 773 * Send a DELETE request 774 * @param cluster the cluster definition 775 * @param path the path or URI 776 * @return a Response object with response detail 777 * @throws IOException for error 778 */ 779 public Response delete(Cluster cluster, String path, Header extraHdr) throws IOException { 780 HttpDelete method = new HttpDelete(path); 781 try { 782 Header[] headers = { extraHdr }; 783 HttpResponse resp = execute(cluster, method, headers, path); 784 headers = resp.getAllHeaders(); 785 byte[] content = getResponseBody(resp); 786 return new Response(resp.getStatusLine().getStatusCode(), headers, content); 787 } finally { 788 method.releaseConnection(); 789 } 790 } 791 792 793 public static class ClientTrustStoreInitializationException extends RuntimeException { 794 795 public ClientTrustStoreInitializationException(String message, Throwable cause) { 796 super(message, cause); 797 } 798 } 799}