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