View Javadoc

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