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