View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.rest.client;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.util.Collections;
25  import java.util.Map;
26  import java.util.concurrent.ConcurrentHashMap;
27  
28  import org.apache.commons.httpclient.Header;
29  import org.apache.commons.httpclient.HttpClient;
30  import org.apache.commons.httpclient.HttpMethod;
31  import org.apache.commons.httpclient.HttpVersion;
32  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
33  import org.apache.commons.httpclient.URI;
34  import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
35  import org.apache.commons.httpclient.methods.DeleteMethod;
36  import org.apache.commons.httpclient.methods.GetMethod;
37  import org.apache.commons.httpclient.methods.HeadMethod;
38  import org.apache.commons.httpclient.methods.PostMethod;
39  import org.apache.commons.httpclient.methods.PutMethod;
40  import org.apache.commons.httpclient.params.HttpClientParams;
41  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.apache.hadoop.hbase.classification.InterfaceAudience;
45  import org.apache.hadoop.hbase.classification.InterfaceStability;
46  
47  /**
48   * A wrapper around HttpClient which provides some useful function and
49   * semantics for interacting with the REST gateway.
50   */
51  @InterfaceAudience.Public
52  @InterfaceStability.Stable
53  public class Client {
54    public static final Header[] EMPTY_HEADER_ARRAY = new Header[0];
55  
56    private static final Log LOG = LogFactory.getLog(Client.class);
57  
58    private HttpClient httpClient;
59    private Cluster cluster;
60    private boolean sslEnabled;
61  
62    private Map<String, String> extraHeaders;
63  
64    /**
65     * Default Constructor
66     */
67    public Client() {
68      this(null);
69    }
70  
71    private void initialize(Cluster cluster, boolean sslEnabled) {
72      this.cluster = cluster;
73      this.sslEnabled = sslEnabled;
74      MultiThreadedHttpConnectionManager manager =
75        new MultiThreadedHttpConnectionManager();
76      HttpConnectionManagerParams managerParams = manager.getParams();
77      managerParams.setConnectionTimeout(2000); // 2 s
78      managerParams.setDefaultMaxConnectionsPerHost(10);
79      managerParams.setMaxTotalConnections(100);
80      extraHeaders = new ConcurrentHashMap<String, String>();
81      this.httpClient = new HttpClient(manager);
82      HttpClientParams clientParams = httpClient.getParams();
83      clientParams.setVersion(HttpVersion.HTTP_1_1);
84  
85    }
86    /**
87     * Constructor
88     * @param cluster the cluster definition
89     */
90    public Client(Cluster cluster) {
91      initialize(cluster, false);
92    }
93  
94    /**
95     * Constructor
96     * @param cluster the cluster definition
97     * @param sslEnabled enable SSL or not
98     */
99    public Client(Cluster cluster, boolean sslEnabled) {
100     initialize(cluster, sslEnabled);
101   }
102 
103   /**
104    * Shut down the client. Close any open persistent connections. 
105    */
106   public void shutdown() {
107     MultiThreadedHttpConnectionManager manager = 
108       (MultiThreadedHttpConnectionManager) httpClient.getHttpConnectionManager();
109     manager.shutdown();
110   }
111 
112   /**
113    * @return the wrapped HttpClient
114    */
115   public HttpClient getHttpClient() {
116     return httpClient;
117   }
118 
119   /**
120    * Add extra headers.  These extra headers will be applied to all http
121    * methods before they are removed. If any header is not used any more,
122    * client needs to remove it explicitly.
123    */
124   public void addExtraHeader(final String name, final String value) {
125     extraHeaders.put(name, value);
126   }
127 
128   /**
129    * Get an extra header value.
130    */
131   public String getExtraHeader(final String name) {
132     return extraHeaders.get(name);
133   }
134 
135   /**
136    * Get all extra headers (read-only).
137    */
138   public Map<String, String> getExtraHeaders() {
139     return Collections.unmodifiableMap(extraHeaders);
140   }
141 
142   /**
143    * Remove an extra header.
144    */
145   public void removeExtraHeader(final String name) {
146     extraHeaders.remove(name);
147   }
148 
149   /**
150    * Execute a transaction method given only the path. Will select at random
151    * one of the members of the supplied cluster definition and iterate through
152    * the list until a transaction can be successfully completed. The
153    * definition of success here is a complete HTTP transaction, irrespective
154    * of result code.  
155    * @param cluster the cluster definition
156    * @param method the transaction method
157    * @param headers HTTP header values to send
158    * @param path the properly urlencoded path
159    * @return the HTTP response code
160    * @throws IOException
161    */
162   public int executePathOnly(Cluster cluster, HttpMethod method,
163       Header[] headers, String path) throws IOException {
164     IOException lastException;
165     if (cluster.nodes.size() < 1) {
166       throw new IOException("Cluster is empty");
167     }
168     int start = (int)Math.round((cluster.nodes.size() - 1) * Math.random());
169     int i = start;
170     do {
171       cluster.lastHost = cluster.nodes.get(i);
172       try {
173         StringBuilder sb = new StringBuilder();
174         if (sslEnabled) {
175           sb.append("https://");
176         } else {
177           sb.append("http://");
178         }
179         sb.append(cluster.lastHost);
180         sb.append(path);
181         URI uri = new URI(sb.toString(), true);
182         return executeURI(method, headers, uri.toString());
183       } catch (IOException e) {
184         lastException = e;
185       }
186     } while (++i != start && i < cluster.nodes.size());
187     throw lastException;
188   }
189 
190   /**
191    * Execute a transaction method given a complete URI.
192    * @param method the transaction method
193    * @param headers HTTP header values to send
194    * @param uri a properly urlencoded URI
195    * @return the HTTP response code
196    * @throws IOException
197    */
198   public int executeURI(HttpMethod method, Header[] headers, String uri)
199       throws IOException {
200     method.setURI(new URI(uri, true));
201     for (Map.Entry<String, String> e: extraHeaders.entrySet()) {
202       method.addRequestHeader(e.getKey(), e.getValue());
203     }
204     if (headers != null) {
205       for (Header header: headers) {
206         method.addRequestHeader(header);
207       }
208     }
209     long startTime = System.currentTimeMillis();
210     int code = httpClient.executeMethod(method);
211     long endTime = System.currentTimeMillis();
212     if (LOG.isDebugEnabled()) {
213       LOG.debug(method.getName() + " " + uri + " " + code + " " +
214         method.getStatusText() + " in " + (endTime - startTime) + " ms");
215     }
216     return code;
217   }
218 
219   /**
220    * Execute a transaction method. Will call either <tt>executePathOnly</tt>
221    * or <tt>executeURI</tt> depending on whether a path only is supplied in
222    * 'path', or if a complete URI is passed instead, respectively.
223    * @param cluster the cluster definition
224    * @param method the HTTP method
225    * @param headers HTTP header values to send
226    * @param path the properly urlencoded path or URI
227    * @return the HTTP response code
228    * @throws IOException
229    */
230   public int execute(Cluster cluster, HttpMethod method, Header[] headers,
231       String path) throws IOException {
232     if (path.startsWith("/")) {
233       return executePathOnly(cluster, method, headers, path);
234     }
235     return executeURI(method, headers, path);
236   }
237 
238   /**
239    * @return the cluster definition
240    */
241   public Cluster getCluster() {
242     return cluster;
243   }
244 
245   /**
246    * @param cluster the cluster definition
247    */
248   public void setCluster(Cluster cluster) {
249     this.cluster = cluster;
250   }
251 
252   /**
253    * Send a HEAD request 
254    * @param path the path or URI
255    * @return a Response object with response detail
256    * @throws IOException
257    */
258   public Response head(String path) throws IOException {
259     return head(cluster, path, null);
260   }
261 
262   /**
263    * Send a HEAD request 
264    * @param cluster the cluster definition
265    * @param path the path or URI
266    * @param headers the HTTP headers to include in the request
267    * @return a Response object with response detail
268    * @throws IOException
269    */
270   public Response head(Cluster cluster, String path, Header[] headers) 
271       throws IOException {
272     HeadMethod method = new HeadMethod();
273     try {
274       int code = execute(cluster, method, null, path);
275       headers = method.getResponseHeaders();
276       return new Response(code, headers, null);
277     } finally {
278       method.releaseConnection();
279     }
280   }
281 
282   /**
283    * Send a GET request 
284    * @param path the path or URI
285    * @return a Response object with response detail
286    * @throws IOException
287    */
288   public Response get(String path) throws IOException {
289     return get(cluster, path);
290   }
291 
292   /**
293    * Send a GET request 
294    * @param cluster the cluster definition
295    * @param path the path or URI
296    * @return a Response object with response detail
297    * @throws IOException
298    */
299   public Response get(Cluster cluster, String path) throws IOException {
300     return get(cluster, path, EMPTY_HEADER_ARRAY);
301   }
302 
303   /**
304    * Send a GET request 
305    * @param path the path or URI
306    * @param accept Accept header value
307    * @return a Response object with response detail
308    * @throws IOException
309    */
310   public Response get(String path, String accept) throws IOException {
311     return get(cluster, path, accept);
312   }
313 
314   /**
315    * Send a GET request 
316    * @param cluster the cluster definition
317    * @param path the path or URI
318    * @param accept Accept header value
319    * @return a Response object with response detail
320    * @throws IOException
321    */
322   public Response get(Cluster cluster, String path, String accept)
323       throws IOException {
324     Header[] headers = new Header[1];
325     headers[0] = new Header("Accept", accept);
326     return get(cluster, path, headers);
327   }
328 
329   /**
330    * Send a GET request
331    * @param path the path or URI
332    * @param headers the HTTP headers to include in the request, 
333    * <tt>Accept</tt> must be supplied
334    * @return a Response object with response detail
335    * @throws IOException
336    */
337   public Response get(String path, Header[] headers) throws IOException {
338     return get(cluster, path, headers);
339   }
340 
341   /**
342    * Send a GET request
343    * @param c the cluster definition
344    * @param path the path or URI
345    * @param headers the HTTP headers to include in the request
346    * @return a Response object with response detail
347    * @throws IOException
348    */
349   public Response get(Cluster c, String path, Header[] headers) 
350       throws IOException {
351     GetMethod method = new GetMethod();
352     try {
353       int code = execute(c, method, headers, path);
354       headers = method.getResponseHeaders();
355       byte[] body = method.getResponseBody();
356       InputStream in = method.getResponseBodyAsStream();
357       return new Response(code, headers, body, in);
358     } finally {
359       method.releaseConnection();
360     }
361   }
362 
363   /**
364    * Send a PUT request
365    * @param path the path or URI
366    * @param contentType the content MIME type
367    * @param content the content bytes
368    * @return a Response object with response detail
369    * @throws IOException
370    */
371   public Response put(String path, String contentType, byte[] content)
372       throws IOException {
373     return put(cluster, path, contentType, content);
374   }
375 
376   /**
377    * Send a PUT request
378    * @param cluster the cluster definition
379    * @param path the path or URI
380    * @param contentType the content MIME type
381    * @param content the content bytes
382    * @return a Response object with response detail
383    * @throws IOException
384    */
385   public Response put(Cluster cluster, String path, String contentType, 
386       byte[] content) throws IOException {
387     Header[] headers = new Header[1];
388     headers[0] = new Header("Content-Type", contentType);
389     return put(cluster, path, headers, content);
390   }
391 
392   /**
393    * Send a PUT request
394    * @param path the path or URI
395    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
396    * supplied
397    * @param content the content bytes
398    * @return a Response object with response detail
399    * @throws IOException
400    */
401   public Response put(String path, Header[] headers, byte[] content) 
402       throws IOException {
403     return put(cluster, path, headers, content);
404   }
405 
406   /**
407    * Send a PUT request
408    * @param cluster the cluster definition
409    * @param path the path or URI
410    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
411    * supplied
412    * @param content the content bytes
413    * @return a Response object with response detail
414    * @throws IOException
415    */
416   public Response put(Cluster cluster, String path, Header[] headers, 
417       byte[] content) throws IOException {
418     PutMethod method = new PutMethod();
419     try {
420       method.setRequestEntity(new ByteArrayRequestEntity(content));
421       int code = execute(cluster, method, headers, path);
422       headers = method.getResponseHeaders();
423       content = method.getResponseBody();
424       return new Response(code, headers, content);
425     } finally {
426       method.releaseConnection();
427     }
428   }
429 
430   /**
431    * Send a POST request
432    * @param path the path or URI
433    * @param contentType the content MIME type
434    * @param content the content bytes
435    * @return a Response object with response detail
436    * @throws IOException
437    */
438   public Response post(String path, String contentType, byte[] content)
439       throws IOException {
440     return post(cluster, path, contentType, content);
441   }
442 
443   /**
444    * Send a POST request
445    * @param cluster the cluster definition
446    * @param path the path or URI
447    * @param contentType the content MIME type
448    * @param content the content bytes
449    * @return a Response object with response detail
450    * @throws IOException
451    */
452   public Response post(Cluster cluster, String path, String contentType, 
453       byte[] content) throws IOException {
454     Header[] headers = new Header[1];
455     headers[0] = new Header("Content-Type", contentType);
456     return post(cluster, path, headers, content);
457   }
458 
459   /**
460    * Send a POST request
461    * @param path the path or URI
462    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
463    * supplied
464    * @param content the content bytes
465    * @return a Response object with response detail
466    * @throws IOException
467    */
468   public Response post(String path, Header[] headers, byte[] content) 
469       throws IOException {
470     return post(cluster, path, headers, content);
471   }
472 
473   /**
474    * Send a POST request
475    * @param cluster the cluster definition
476    * @param path the path or URI
477    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
478    * supplied
479    * @param content the content bytes
480    * @return a Response object with response detail
481    * @throws IOException
482    */
483   public Response post(Cluster cluster, String path, Header[] headers, 
484       byte[] content) throws IOException {
485     PostMethod method = new PostMethod();
486     try {
487       method.setRequestEntity(new ByteArrayRequestEntity(content));
488       int code = execute(cluster, method, headers, path);
489       headers = method.getResponseHeaders();
490       content = method.getResponseBody();
491       return new Response(code, headers, content);
492     } finally {
493       method.releaseConnection();
494     }
495   }
496 
497   /**
498    * Send a DELETE request
499    * @param path the path or URI
500    * @return a Response object with response detail
501    * @throws IOException
502    */
503   public Response delete(String path) throws IOException {
504     return delete(cluster, path);
505   }
506 
507   /**
508    * Send a DELETE request
509    * @param cluster the cluster definition
510    * @param path the path or URI
511    * @return a Response object with response detail
512    * @throws IOException
513    */
514   public Response delete(Cluster cluster, String path) throws IOException {
515     DeleteMethod method = new DeleteMethod();
516     try {
517       int code = execute(cluster, method, null, path);
518       Header[] headers = method.getResponseHeaders();
519       byte[] content = method.getResponseBody();
520       return new Response(code, headers, content);
521     } finally {
522       method.releaseConnection();
523     }
524   }
525 }