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  
25  import org.apache.commons.httpclient.Header;
26  import org.apache.commons.httpclient.HttpClient;
27  import org.apache.commons.httpclient.HttpMethod;
28  import org.apache.commons.httpclient.HttpVersion;
29  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
30  import org.apache.commons.httpclient.URI;
31  import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
32  import org.apache.commons.httpclient.methods.DeleteMethod;
33  import org.apache.commons.httpclient.methods.GetMethod;
34  import org.apache.commons.httpclient.methods.HeadMethod;
35  import org.apache.commons.httpclient.methods.PostMethod;
36  import org.apache.commons.httpclient.methods.PutMethod;
37  import org.apache.commons.httpclient.params.HttpClientParams;
38  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  /**
43   * A wrapper around HttpClient which provides some useful function and
44   * semantics for interacting with the REST gateway.
45   */
46  public class Client {
47    public static final Header[] EMPTY_HEADER_ARRAY = new Header[0];
48  
49    private static final Log LOG = LogFactory.getLog(Client.class);
50  
51    private HttpClient httpClient;
52    private Cluster cluster;
53  
54    /**
55     * Default Constructor
56     */
57    public Client() {
58      this(null);
59    }
60  
61    /**
62     * Constructor
63     * @param cluster the cluster definition
64     */
65    public Client(Cluster cluster) {
66      this.cluster = cluster;
67      MultiThreadedHttpConnectionManager manager = 
68        new MultiThreadedHttpConnectionManager();
69      HttpConnectionManagerParams managerParams = manager.getParams();
70      managerParams.setConnectionTimeout(2000); // 2 s
71      managerParams.setDefaultMaxConnectionsPerHost(10);
72      managerParams.setMaxTotalConnections(100);
73      this.httpClient = new HttpClient(manager);
74      HttpClientParams clientParams = httpClient.getParams();
75      clientParams.setVersion(HttpVersion.HTTP_1_1);
76    }
77  
78    /**
79     * Shut down the client. Close any open persistent connections. 
80     */
81    public void shutdown() {
82      MultiThreadedHttpConnectionManager manager = 
83        (MultiThreadedHttpConnectionManager) httpClient.getHttpConnectionManager();
84      manager.shutdown();
85    }
86  
87    /**
88     * Execute a transaction method given only the path. Will select at random
89     * one of the members of the supplied cluster definition and iterate through
90     * the list until a transaction can be successfully completed. The
91     * definition of success here is a complete HTTP transaction, irrespective
92     * of result code.  
93     * @param cluster the cluster definition
94     * @param method the transaction method
95     * @param headers HTTP header values to send
96     * @param path the path
97     * @return the HTTP response code
98     * @throws IOException
99     */
100   @SuppressWarnings("deprecation")
101   public int executePathOnly(Cluster cluster, HttpMethod method,
102       Header[] headers, String path) throws IOException {
103     IOException lastException;
104     if (cluster.nodes.size() < 1) {
105       throw new IOException("Cluster is empty");
106     }
107     int start = (int)Math.round((cluster.nodes.size() - 1) * Math.random());
108     int i = start;
109     do {
110       cluster.lastHost = cluster.nodes.get(i);
111       try {
112         StringBuilder sb = new StringBuilder();
113         sb.append("http://");
114         sb.append(cluster.lastHost);
115         sb.append(path);
116         URI uri = new URI(sb.toString());
117         return executeURI(method, headers, uri.toString());
118       } catch (IOException e) {
119         lastException = e;
120       }
121     } while (++i != start && i < cluster.nodes.size());
122     throw lastException;
123   }
124 
125   /**
126    * Execute a transaction method given a complete URI.
127    * @param method the transaction method
128    * @param headers HTTP header values to send
129    * @param uri the URI
130    * @return the HTTP response code
131    * @throws IOException
132    */
133   @SuppressWarnings("deprecation")
134   public int executeURI(HttpMethod method, Header[] headers, String uri)
135       throws IOException {
136     method.setURI(new URI(uri));
137     if (headers != null) {
138       for (Header header: headers) {
139         method.addRequestHeader(header);
140       }
141     }
142     long startTime = System.currentTimeMillis();
143     int code = httpClient.executeMethod(method);
144     long endTime = System.currentTimeMillis();
145     if (LOG.isDebugEnabled()) {
146       LOG.debug(method.getName() + " " + uri + " " + code + " " +
147         method.getStatusText() + " in " + (endTime - startTime) + " ms");
148     }
149     return code;
150   }
151 
152   /**
153    * Execute a transaction method. Will call either <tt>executePathOnly</tt>
154    * or <tt>executeURI</tt> depending on whether a path only is supplied in
155    * 'path', or if a complete URI is passed instead, respectively.
156    * @param cluster the cluster definition
157    * @param method the HTTP method
158    * @param headers HTTP header values to send
159    * @param path the path or URI
160    * @return the HTTP response code
161    * @throws IOException
162    */
163   public int execute(Cluster cluster, HttpMethod method, Header[] headers,
164       String path) throws IOException {
165     if (path.startsWith("/")) {
166       return executePathOnly(cluster, method, headers, path);
167     }
168     return executeURI(method, headers, path);
169   }
170 
171   /**
172    * @return the cluster definition
173    */
174   public Cluster getCluster() {
175     return cluster;
176   }
177 
178   /**
179    * @param cluster the cluster definition
180    */
181   public void setCluster(Cluster cluster) {
182     this.cluster = cluster;
183   }
184 
185   /**
186    * Send a HEAD request 
187    * @param path the path or URI
188    * @return a Response object with response detail
189    * @throws IOException
190    */
191   public Response head(String path) throws IOException {
192     return head(cluster, path, null);
193   }
194 
195   /**
196    * Send a HEAD request 
197    * @param cluster the cluster definition
198    * @param path the path or URI
199    * @param headers the HTTP headers to include in the request
200    * @return a Response object with response detail
201    * @throws IOException
202    */
203   public Response head(Cluster cluster, String path, Header[] headers) 
204       throws IOException {
205     HeadMethod method = new HeadMethod();
206     try {
207       int code = execute(cluster, method, null, path);
208       headers = method.getResponseHeaders();
209       return new Response(code, headers, null);
210     } finally {
211       method.releaseConnection();
212     }
213   }
214 
215   /**
216    * Send a GET request 
217    * @param path the path or URI
218    * @return a Response object with response detail
219    * @throws IOException
220    */
221   public Response get(String path) throws IOException {
222     return get(cluster, path);
223   }
224 
225   /**
226    * Send a GET request 
227    * @param cluster the cluster definition
228    * @param path the path or URI
229    * @return a Response object with response detail
230    * @throws IOException
231    */
232   public Response get(Cluster cluster, String path) throws IOException {
233     return get(cluster, path, EMPTY_HEADER_ARRAY);
234   }
235 
236   /**
237    * Send a GET request 
238    * @param path the path or URI
239    * @param accept Accept header value
240    * @return a Response object with response detail
241    * @throws IOException
242    */
243   public Response get(String path, String accept) throws IOException {
244     return get(cluster, path, accept);
245   }
246 
247   /**
248    * Send a GET request 
249    * @param cluster the cluster definition
250    * @param path the path or URI
251    * @param accept Accept header value
252    * @return a Response object with response detail
253    * @throws IOException
254    */
255   public Response get(Cluster cluster, String path, String accept)
256       throws IOException {
257     Header[] headers = new Header[1];
258     headers[0] = new Header("Accept", accept);
259     return get(cluster, path, headers);
260   }
261 
262   /**
263    * Send a GET request
264    * @param path the path or URI
265    * @param headers the HTTP headers to include in the request, 
266    * <tt>Accept</tt> must be supplied
267    * @return a Response object with response detail
268    * @throws IOException
269    */
270   public Response get(String path, Header[] headers) throws IOException {
271     return get(cluster, path, headers);
272   }
273 
274   /**
275    * Send a GET request
276    * @param c the cluster definition
277    * @param path the path or URI
278    * @param headers the HTTP headers to include in the request
279    * @return a Response object with response detail
280    * @throws IOException
281    */
282   public Response get(Cluster c, String path, Header[] headers) 
283       throws IOException {
284     GetMethod method = new GetMethod();
285     try {
286       int code = execute(c, method, headers, path);
287       headers = method.getResponseHeaders();
288       byte[] body = method.getResponseBody();
289       return new Response(code, headers, body);
290     } finally {
291       method.releaseConnection();
292     }
293   }
294 
295   /**
296    * Send a PUT request
297    * @param path the path or URI
298    * @param contentType the content MIME type
299    * @param content the content bytes
300    * @return a Response object with response detail
301    * @throws IOException
302    */
303   public Response put(String path, String contentType, byte[] content)
304       throws IOException {
305     return put(cluster, path, contentType, content);
306   }
307 
308   /**
309    * Send a PUT request
310    * @param cluster the cluster definition
311    * @param path the path or URI
312    * @param contentType the content MIME type
313    * @param content the content bytes
314    * @return a Response object with response detail
315    * @throws IOException
316    */
317   public Response put(Cluster cluster, String path, String contentType, 
318       byte[] content) throws IOException {
319     Header[] headers = new Header[1];
320     headers[0] = new Header("Content-Type", contentType);
321     return put(cluster, path, headers, content);
322   }
323 
324   /**
325    * Send a PUT request
326    * @param path the path or URI
327    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
328    * supplied
329    * @param content the content bytes
330    * @return a Response object with response detail
331    * @throws IOException
332    */
333   public Response put(String path, Header[] headers, byte[] content) 
334       throws IOException {
335     return put(cluster, path, headers, content);
336   }
337 
338   /**
339    * Send a PUT request
340    * @param cluster the cluster definition
341    * @param path the path or URI
342    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
343    * supplied
344    * @param content the content bytes
345    * @return a Response object with response detail
346    * @throws IOException
347    */
348   public Response put(Cluster cluster, String path, Header[] headers, 
349       byte[] content) throws IOException {
350     PutMethod method = new PutMethod();
351     try {
352       method.setRequestEntity(new ByteArrayRequestEntity(content));
353       int code = execute(cluster, method, headers, path);
354       headers = method.getResponseHeaders();
355       content = method.getResponseBody();
356       return new Response(code, headers, content);
357     } finally {
358       method.releaseConnection();
359     }
360   }
361 
362   /**
363    * Send a POST request
364    * @param path the path or URI
365    * @param contentType the content MIME type
366    * @param content the content bytes
367    * @return a Response object with response detail
368    * @throws IOException
369    */
370   public Response post(String path, String contentType, byte[] content)
371       throws IOException {
372     return post(cluster, path, contentType, content);
373   }
374 
375   /**
376    * Send a POST request
377    * @param cluster the cluster definition
378    * @param path the path or URI
379    * @param contentType the content MIME type
380    * @param content the content bytes
381    * @return a Response object with response detail
382    * @throws IOException
383    */
384   public Response post(Cluster cluster, String path, String contentType, 
385       byte[] content) throws IOException {
386     Header[] headers = new Header[1];
387     headers[0] = new Header("Content-Type", contentType);
388     return post(cluster, path, headers, content);
389   }
390 
391   /**
392    * Send a POST request
393    * @param path the path or URI
394    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
395    * supplied
396    * @param content the content bytes
397    * @return a Response object with response detail
398    * @throws IOException
399    */
400   public Response post(String path, Header[] headers, byte[] content) 
401       throws IOException {
402     return post(cluster, path, headers, content);
403   }
404 
405   /**
406    * Send a POST request
407    * @param cluster the cluster definition
408    * @param path the path or URI
409    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
410    * supplied
411    * @param content the content bytes
412    * @return a Response object with response detail
413    * @throws IOException
414    */
415   public Response post(Cluster cluster, String path, Header[] headers, 
416       byte[] content) throws IOException {
417     PostMethod method = new PostMethod();
418     try {
419       method.setRequestEntity(new ByteArrayRequestEntity(content));
420       int code = execute(cluster, method, headers, path);
421       headers = method.getResponseHeaders();
422       content = method.getResponseBody();
423       return new Response(code, headers, content);
424     } finally {
425       method.releaseConnection();
426     }
427   }
428 
429   /**
430    * Send a DELETE request
431    * @param path the path or URI
432    * @return a Response object with response detail
433    * @throws IOException
434    */
435   public Response delete(String path) throws IOException {
436     return delete(cluster, path);
437   }
438 
439   /**
440    * Send a DELETE request
441    * @param cluster the cluster definition
442    * @param path the path or URI
443    * @return a Response object with response detail
444    * @throws IOException
445    */
446   public Response delete(Cluster cluster, String path) throws IOException {
447     DeleteMethod method = new DeleteMethod();
448     try {
449       int code = execute(cluster, method, null, path);
450       Header[] headers = method.getResponseHeaders();
451       byte[] content = method.getResponseBody();
452       return new Response(code, headers, content);
453     } finally {
454       method.releaseConnection();
455     }
456   }
457 
458 }