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