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 properly urlencoded path
97     * @return the HTTP response code
98     * @throws IOException
99     */
100   public int executePathOnly(Cluster cluster, HttpMethod method,
101       Header[] headers, String path) throws IOException {
102     IOException lastException;
103     if (cluster.nodes.size() < 1) {
104       throw new IOException("Cluster is empty");
105     }
106     int start = (int)Math.round((cluster.nodes.size() - 1) * Math.random());
107     int i = start;
108     do {
109       cluster.lastHost = cluster.nodes.get(i);
110       try {
111         StringBuilder sb = new StringBuilder();
112         sb.append("http://");
113         sb.append(cluster.lastHost);
114         sb.append(path);
115         URI uri = new URI(sb.toString(), true);
116         return executeURI(method, headers, uri.toString());
117       } catch (IOException e) {
118         lastException = e;
119       }
120     } while (++i != start && i < cluster.nodes.size());
121     throw lastException;
122   }
123 
124   /**
125    * Execute a transaction method given a complete URI.
126    * @param method the transaction method
127    * @param headers HTTP header values to send
128    * @param uri a properly urlencoded URI
129    * @return the HTTP response code
130    * @throws IOException
131    */
132   public int executeURI(HttpMethod method, Header[] headers, String uri)
133       throws IOException {
134     method.setURI(new URI(uri, true));
135     if (headers != null) {
136       for (Header header: headers) {
137         method.addRequestHeader(header);
138       }
139     }
140     long startTime = System.currentTimeMillis();
141     int code = httpClient.executeMethod(method);
142     long endTime = System.currentTimeMillis();
143     if (LOG.isDebugEnabled()) {
144       LOG.debug(method.getName() + " " + uri + " " + code + " " +
145         method.getStatusText() + " in " + (endTime - startTime) + " ms");
146     }
147     return code;
148   }
149 
150   /**
151    * Execute a transaction method. Will call either <tt>executePathOnly</tt>
152    * or <tt>executeURI</tt> depending on whether a path only is supplied in
153    * 'path', or if a complete URI is passed instead, respectively.
154    * @param cluster the cluster definition
155    * @param method the HTTP method
156    * @param headers HTTP header values to send
157    * @param path the properly urlencoded path or URI
158    * @return the HTTP response code
159    * @throws IOException
160    */
161   public int execute(Cluster cluster, HttpMethod method, Header[] headers,
162       String path) throws IOException {
163     if (path.startsWith("/")) {
164       return executePathOnly(cluster, method, headers, path);
165     }
166     return executeURI(method, headers, path);
167   }
168 
169   /**
170    * @return the cluster definition
171    */
172   public Cluster getCluster() {
173     return cluster;
174   }
175 
176   /**
177    * @param cluster the cluster definition
178    */
179   public void setCluster(Cluster cluster) {
180     this.cluster = cluster;
181   }
182 
183   /**
184    * Send a HEAD request 
185    * @param path the path or URI
186    * @return a Response object with response detail
187    * @throws IOException
188    */
189   public Response head(String path) throws IOException {
190     return head(cluster, path, null);
191   }
192 
193   /**
194    * Send a HEAD request 
195    * @param cluster the cluster definition
196    * @param path the path or URI
197    * @param headers the HTTP headers to include in the request
198    * @return a Response object with response detail
199    * @throws IOException
200    */
201   public Response head(Cluster cluster, String path, Header[] headers) 
202       throws IOException {
203     HeadMethod method = new HeadMethod();
204     try {
205       int code = execute(cluster, method, null, path);
206       headers = method.getResponseHeaders();
207       return new Response(code, headers, null);
208     } finally {
209       method.releaseConnection();
210     }
211   }
212 
213   /**
214    * Send a GET request 
215    * @param path the path or URI
216    * @return a Response object with response detail
217    * @throws IOException
218    */
219   public Response get(String path) throws IOException {
220     return get(cluster, path);
221   }
222 
223   /**
224    * Send a GET request 
225    * @param cluster the cluster definition
226    * @param path the path or URI
227    * @return a Response object with response detail
228    * @throws IOException
229    */
230   public Response get(Cluster cluster, String path) throws IOException {
231     return get(cluster, path, EMPTY_HEADER_ARRAY);
232   }
233 
234   /**
235    * Send a GET request 
236    * @param path the path or URI
237    * @param accept Accept header value
238    * @return a Response object with response detail
239    * @throws IOException
240    */
241   public Response get(String path, String accept) throws IOException {
242     return get(cluster, path, accept);
243   }
244 
245   /**
246    * Send a GET request 
247    * @param cluster the cluster definition
248    * @param path the path or URI
249    * @param accept Accept header value
250    * @return a Response object with response detail
251    * @throws IOException
252    */
253   public Response get(Cluster cluster, String path, String accept)
254       throws IOException {
255     Header[] headers = new Header[1];
256     headers[0] = new Header("Accept", accept);
257     return get(cluster, path, headers);
258   }
259 
260   /**
261    * Send a GET request
262    * @param path the path or URI
263    * @param headers the HTTP headers to include in the request, 
264    * <tt>Accept</tt> must be supplied
265    * @return a Response object with response detail
266    * @throws IOException
267    */
268   public Response get(String path, Header[] headers) throws IOException {
269     return get(cluster, path, headers);
270   }
271 
272   /**
273    * Send a GET request
274    * @param c the cluster definition
275    * @param path the path or URI
276    * @param headers the HTTP headers to include in the request
277    * @return a Response object with response detail
278    * @throws IOException
279    */
280   public Response get(Cluster c, String path, Header[] headers) 
281       throws IOException {
282     GetMethod method = new GetMethod();
283     try {
284       int code = execute(c, method, headers, path);
285       headers = method.getResponseHeaders();
286       byte[] body = method.getResponseBody();
287       return new Response(code, headers, body);
288     } finally {
289       method.releaseConnection();
290     }
291   }
292 
293   /**
294    * Send a PUT request
295    * @param path the path or URI
296    * @param contentType the content MIME type
297    * @param content the content bytes
298    * @return a Response object with response detail
299    * @throws IOException
300    */
301   public Response put(String path, String contentType, byte[] content)
302       throws IOException {
303     return put(cluster, path, contentType, content);
304   }
305 
306   /**
307    * Send a PUT request
308    * @param cluster the cluster definition
309    * @param path the path or URI
310    * @param contentType the content MIME type
311    * @param content the content bytes
312    * @return a Response object with response detail
313    * @throws IOException
314    */
315   public Response put(Cluster cluster, String path, String contentType, 
316       byte[] content) throws IOException {
317     Header[] headers = new Header[1];
318     headers[0] = new Header("Content-Type", contentType);
319     return put(cluster, path, headers, content);
320   }
321 
322   /**
323    * Send a PUT request
324    * @param path the path or URI
325    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
326    * supplied
327    * @param content the content bytes
328    * @return a Response object with response detail
329    * @throws IOException
330    */
331   public Response put(String path, Header[] headers, byte[] content) 
332       throws IOException {
333     return put(cluster, path, headers, content);
334   }
335 
336   /**
337    * Send a PUT request
338    * @param cluster the cluster definition
339    * @param path the path or URI
340    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
341    * supplied
342    * @param content the content bytes
343    * @return a Response object with response detail
344    * @throws IOException
345    */
346   public Response put(Cluster cluster, String path, Header[] headers, 
347       byte[] content) throws IOException {
348     PutMethod method = new PutMethod();
349     try {
350       method.setRequestEntity(new ByteArrayRequestEntity(content));
351       int code = execute(cluster, method, headers, path);
352       headers = method.getResponseHeaders();
353       content = method.getResponseBody();
354       return new Response(code, headers, content);
355     } finally {
356       method.releaseConnection();
357     }
358   }
359 
360   /**
361    * Send a POST request
362    * @param path the path or URI
363    * @param contentType the content MIME type
364    * @param content the content bytes
365    * @return a Response object with response detail
366    * @throws IOException
367    */
368   public Response post(String path, String contentType, byte[] content)
369       throws IOException {
370     return post(cluster, path, contentType, content);
371   }
372 
373   /**
374    * Send a POST request
375    * @param cluster the cluster definition
376    * @param path the path or URI
377    * @param contentType the content MIME type
378    * @param content the content bytes
379    * @return a Response object with response detail
380    * @throws IOException
381    */
382   public Response post(Cluster cluster, String path, String contentType, 
383       byte[] content) throws IOException {
384     Header[] headers = new Header[1];
385     headers[0] = new Header("Content-Type", contentType);
386     return post(cluster, path, headers, content);
387   }
388 
389   /**
390    * Send a POST request
391    * @param path the path or URI
392    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
393    * supplied
394    * @param content the content bytes
395    * @return a Response object with response detail
396    * @throws IOException
397    */
398   public Response post(String path, Header[] headers, byte[] content) 
399       throws IOException {
400     return post(cluster, path, headers, content);
401   }
402 
403   /**
404    * Send a POST request
405    * @param cluster the cluster definition
406    * @param path the path or URI
407    * @param headers the HTTP headers to include, <tt>Content-Type</tt> must be
408    * supplied
409    * @param content the content bytes
410    * @return a Response object with response detail
411    * @throws IOException
412    */
413   public Response post(Cluster cluster, String path, Header[] headers, 
414       byte[] content) throws IOException {
415     PostMethod method = new PostMethod();
416     try {
417       method.setRequestEntity(new ByteArrayRequestEntity(content));
418       int code = execute(cluster, method, headers, path);
419       headers = method.getResponseHeaders();
420       content = method.getResponseBody();
421       return new Response(code, headers, content);
422     } finally {
423       method.releaseConnection();
424     }
425   }
426 
427   /**
428    * Send a DELETE request
429    * @param path the path or URI
430    * @return a Response object with response detail
431    * @throws IOException
432    */
433   public Response delete(String path) throws IOException {
434     return delete(cluster, path);
435   }
436 
437   /**
438    * Send a DELETE request
439    * @param cluster the cluster definition
440    * @param path the path or URI
441    * @return a Response object with response detail
442    * @throws IOException
443    */
444   public Response delete(Cluster cluster, String path) throws IOException {
445     DeleteMethod method = new DeleteMethod();
446     try {
447       int code = execute(cluster, method, null, path);
448       Header[] headers = method.getResponseHeaders();
449       byte[] content = method.getResponseBody();
450       return new Response(code, headers, content);
451     } finally {
452       method.releaseConnection();
453     }
454   }
455 
456 }