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