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