You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2016/11/10 14:27:48 UTC

[2/3] brooklyn-server git commit: Add tests + address PR comments

Add tests + address PR comments


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/5529d79e
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/5529d79e
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/5529d79e

Branch: refs/heads/master
Commit: 5529d79ec6f265fc4890cda3860a1d3d5d29cb63
Parents: 8f41485
Author: Thomas Bouron <th...@cloudsoftcorp.com>
Authored: Tue Nov 8 11:58:17 2016 +0000
Committer: Thomas Bouron <th...@cloudsoftcorp.com>
Committed: Wed Nov 9 11:59:52 2016 +0000

----------------------------------------------------------------------
 .../util/core/http/HttpToolIntegrationTest.java |  33 ++++-
 .../brooklyn/test/framework/TestHttpCall.java   |  24 +++-
 .../test/framework/TestHttpCallImpl.java        |  65 ++-------
 .../test/framework/TestHttpCallTest.java        |  77 ++++++++++
 .../org/apache/brooklyn/util/http/HttpTool.java | 144 +++++++++++--------
 5 files changed, 229 insertions(+), 114 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5529d79e/core/src/test/java/org/apache/brooklyn/util/core/http/HttpToolIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/http/HttpToolIntegrationTest.java b/core/src/test/java/org/apache/brooklyn/util/core/http/HttpToolIntegrationTest.java
index 8a5f4f3..3c4246d 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/http/HttpToolIntegrationTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/http/HttpToolIntegrationTest.java
@@ -22,7 +22,13 @@ import static org.testng.Assert.assertTrue;
 
 import java.net.URI;
 
+import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
 import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpTrace;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -51,7 +57,32 @@ public class HttpToolIntegrationTest {
         if (httpService != null) httpService.shutdown();
         if (httpsService != null) httpsService.shutdown();
     }
-    
+
+    @Test(expectedExceptions = PropagatedRuntimeException.class)
+    public void testHttpRequestBuilderThrowsExIfBodySetForGet() throws Exception {
+        new HttpTool.HttpRequestBuilder<>(HttpGet.class).body("test").build();
+    }
+
+    @Test(expectedExceptions = PropagatedRuntimeException.class)
+    public void testHttpRequestBuilderThrowsExIfBodySetForDelete() throws Exception {
+        new HttpTool.HttpRequestBuilder<>(HttpDelete.class).body("test").build();
+    }
+
+    @Test(expectedExceptions = PropagatedRuntimeException.class)
+    public void testHttpRequestBuilderThrowsExIfBodySetForHead() throws Exception {
+        new HttpTool.HttpRequestBuilder<>(HttpHead.class).body("test").build();
+    }
+
+    @Test(expectedExceptions = PropagatedRuntimeException.class)
+    public void testHttpRequestBuilderThrowsExIfBodySetForOptions() throws Exception {
+        new HttpTool.HttpRequestBuilder<>(HttpOptions.class).body("test").build();
+    }
+
+    @Test(expectedExceptions = PropagatedRuntimeException.class)
+    public void testHttpRequestBuilderThrowsExIfBodySetForTrace() throws Exception {
+        new HttpTool.HttpRequestBuilder<>(HttpTrace.class).body("test").build();
+    }
+
     @Test(groups = {"Integration"})
     public void testHttpGet() throws Exception {
         URI baseUri = new URI(httpService.getUrl());

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5529d79e/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCall.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCall.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCall.java
index 8dd5774..c549fbd 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCall.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCall.java
@@ -24,6 +24,14 @@ import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.methods.HttpTrace;
 
 import com.google.common.reflect.TypeToken;
 
@@ -43,20 +51,30 @@ public interface TestHttpCall extends BaseTest {
             .defaultValue(HttpMethod.GET)
             .build();
 
-    @SetFromFlag
     ConfigKey<Map<String, String>> TARGET_HEADERS = ConfigKeys.builder(new TypeToken<Map<String, String>>() {})
             .name("headers")
             .description("Headers to add to the request")
             .build();
 
-    @SetFromFlag
     ConfigKey<String> TARGET_BODY = ConfigKeys.newStringConfigKey("body", "The request body to send (only for POST and PUT requests)");
 
     ConfigKey<HttpAssertionTarget> ASSERTION_TARGET = ConfigKeys.newConfigKey(HttpAssertionTarget.class, "applyAssertionTo",
         "The HTTP field to apply the assertion to [body,status]", HttpAssertionTarget.body);
 
     enum HttpMethod {
-        GET, POST, PUT, DELETE, HEAD;
+        GET(HttpGet.class),
+        POST(HttpPost.class),
+        PUT(HttpPut.class),
+        DELETE(HttpDelete.class),
+        HEAD(HttpHead.class),
+        OPTIONS(HttpOptions.class),
+        TRACE(HttpTrace.class);
+
+        public final Class<? extends HttpRequestBase> requestClass;
+
+        HttpMethod(Class<? extends HttpRequestBase> requestClass) {
+            this.requestClass = requestClass;
+        }
 
         @Override
         public String toString() {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5529d79e/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
----------------------------------------------------------------------
diff --git a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
index acf9f23..11a3244 100644
--- a/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
+++ b/test-framework/src/main/java/org/apache/brooklyn/test/framework/TestHttpCallImpl.java
@@ -21,7 +21,6 @@ package org.apache.brooklyn.test.framework;
 import static org.apache.brooklyn.test.framework.TestFrameworkAssertions.getAssertions;
 
 import java.net.URI;
-import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -55,17 +54,14 @@ public class TestHttpCallImpl extends TargetableTestComponentImpl implements Tes
      */
     public void start(Collection<? extends Location> locations) {
         String url = null;
-        HttpMethod method = null;
-        Map<String, String> headers = null;
-        String body = null;
-        
+
         ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING);
 
         try {
             url = getRequiredConfig(TARGET_URL);
-            method = getRequiredConfig(TARGET_METHOD);
-            headers = config().get(TARGET_HEADERS);
-            body = config().get(TARGET_BODY);
+            final HttpMethod method = getRequiredConfig(TARGET_METHOD);
+            final Map<String, String> headers = config().get(TARGET_HEADERS);
+            final String body = config().get(TARGET_BODY);
             final List<Map<String, Object>> assertions = getAssertions(this, ASSERTIONS);
             final Duration timeout = getConfig(TIMEOUT);
             final HttpAssertionTarget target = getRequiredConfig(ASSERTION_TARGET);
@@ -95,7 +91,8 @@ public class TestHttpCallImpl extends TargetableTestComponentImpl implements Tes
                     @Override
                     public String get() {
                         try {
-                            return HttpTool.execAndConsume(HttpTool.httpClientBuilder().build(), createHttpMethod(method, url, headers, body)).getContentAsString();
+                            final HttpRequestBase httpMethod = createHttpMethod(method, url, headers, body);
+                            return HttpTool.execAndConsume(HttpTool.httpClientBuilder().build(), httpMethod).getContentAsString();
                         } catch (Exception e) {
                             LOG.info("HTTP call to [{}] failed due to [{}]", url, e.getMessage());
                             throw Exceptions.propagate(e);
@@ -110,11 +107,12 @@ public class TestHttpCallImpl extends TargetableTestComponentImpl implements Tes
                     @Override
                     public Integer get() {
                         try {
-                            final Maybe<HttpResponse> response = HttpTool.execAndConsume(HttpTool.httpClientBuilder().build(), createHttpMethod(method, url, headers, body)).getResponse();
+                            final HttpRequestBase httpMethod = createHttpMethod(method, url, headers, body);
+                            final Maybe<HttpResponse> response = HttpTool.execAndConsume(HttpTool.httpClientBuilder().build(), httpMethod).getResponse();
                             if (response.isPresentAndNonNull()) {
                                 return response.get().getStatusLine().getStatusCode();
                             } else {
-                                throw new Exception("HTTP call did not return any reponse");
+                                throw new Exception("HTTP call did not return any response");
                             }
                         } catch (Exception e) {
                             LOG.info("HTTP call to [{}] failed due to [{}]", url, e.getMessage());
@@ -131,46 +129,11 @@ public class TestHttpCallImpl extends TargetableTestComponentImpl implements Tes
     }
 
     private HttpRequestBase createHttpMethod(HttpMethod method, String url, Map<String, String> headers, String body) throws Exception {
-        switch (method) {
-            case GET:
-                HttpTool.HttpGetBuilder httpGetBuilder = new HttpTool.HttpGetBuilder(new URI(url));
-                if (headers != null) {
-                    httpGetBuilder.headers(headers);
-                }
-                return httpGetBuilder.build();
-            case POST:
-                HttpTool.HttpPostBuilder httpPostBuilder = new HttpTool.HttpPostBuilder(new URI(url));
-                if (headers != null) {
-                    httpPostBuilder.headers(headers);
-                }
-                if (body != null) {
-                    httpPostBuilder.body(body.getBytes(Charset.forName("UTF-8")));
-                }
-                return httpPostBuilder.build();
-            case PUT:
-                HttpTool.HttpPutBuilder httpPutBuilder = new HttpTool.HttpPutBuilder(new URI(url));
-                if (headers != null) {
-                    httpPutBuilder.headers(headers);
-                }
-                if (body != null) {
-                    httpPutBuilder.body(body.getBytes(Charset.forName("UTF-8")));
-                }
-                return httpPutBuilder.build();
-            case DELETE:
-                HttpTool.HttpDeleteBuilder httpDeleteBuilder = new HttpTool.HttpDeleteBuilder(new URI(url));
-                if (headers != null) {
-                    httpDeleteBuilder.headers(headers);
-                }
-                return httpDeleteBuilder.build();
-            case HEAD:
-                final HttpTool.HttpHeadBuilder httpHeadBuilder = new HttpTool.HttpHeadBuilder(new URI(url));
-                if (headers != null) {
-                    httpHeadBuilder.headers(headers);
-                }
-                return httpHeadBuilder.build();
-            default:
-                throw new Exception(method  + " not supported");
-        }
+        return new HttpTool.HttpRequestBuilder<>(method.requestClass)
+                .uri(new URI(url))
+                .body(body)
+                .headers(headers)
+                .build();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5529d79e/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestHttpCallTest.java
----------------------------------------------------------------------
diff --git a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestHttpCallTest.java b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestHttpCallTest.java
index cb824ce..99638a4 100644
--- a/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestHttpCallTest.java
+++ b/test-framework/src/test/java/org/apache/brooklyn/test/framework/TestHttpCallTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.test.framework;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -31,7 +32,11 @@ import org.apache.brooklyn.test.http.TestHttpRequestHandler;
 import org.apache.brooklyn.test.http.TestHttpServer;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.apache.brooklyn.util.time.Duration;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
+import org.apache.http.protocol.HttpContext;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -61,6 +66,10 @@ public class TestHttpCallTest extends BrooklynAppUnitTestSupport {
                 .handler("/body.json", new TestHttpRequestHandler()
                         .response("{\"a\":\"b\",\"c\":\"d\",\"e\":123,\"g\":false}")
                         .code(200 + Identifiers.randomInt(99)))
+                .handler("/foo/bar", new TestHttpTestRequestHandler()
+                        .method("POST")
+                        .response("hello world")
+                        .code(201))
                 .start();
         loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class)
                 .configure("name", testId));
@@ -94,6 +103,11 @@ public class TestHttpCallTest extends BrooklynAppUnitTestSupport {
                 .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/body.json")
                 .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
                 .configure(TestSensor.ASSERTIONS, newAssertion("matches", ".*123.*")));
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.POST)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", "hello world")));
         app.start(ImmutableList.of(loc));
     }
 
@@ -114,6 +128,47 @@ public class TestHttpCallTest extends BrooklynAppUnitTestSupport {
                 .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
                 .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
                 .configure(TestSensor.ASSERTIONS, newAssertion("matches", "2[0-9][0-9]")));
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.POST)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", HttpStatus.SC_CREATED)));
+        app.start(ImmutableList.of(loc));
+    }
+
+    @Test(groups = "Integration")
+    public void testHttpMethodAssertions() {
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.GET)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", HttpStatus.SC_METHOD_NOT_ALLOWED)));
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.POST)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", HttpStatus.SC_CREATED)));
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.PUT)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", HttpStatus.SC_METHOD_NOT_ALLOWED)));
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.DELETE)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", HttpStatus.SC_METHOD_NOT_ALLOWED)));
+        app.createAndManageChild(EntitySpec.create(TestHttpCall.class)
+                .configure(TestHttpCall.TARGET_URL, server.getUrl() + "/foo/bar")
+                .configure(TestHttpCall.TARGET_METHOD, TestHttpCall.HttpMethod.HEAD)
+                .configure(TestHttpCall.TIMEOUT, new Duration(10L, TimeUnit.SECONDS))
+                .configure(TestHttpCall.ASSERTION_TARGET, TestHttpCall.HttpAssertionTarget.status)
+                .configure(TestSensor.ASSERTIONS, newAssertion("isEqualTo", HttpStatus.SC_METHOD_NOT_ALLOWED)));
         app.start(ImmutableList.of(loc));
     }
 
@@ -123,4 +178,26 @@ public class TestHttpCallTest extends BrooklynAppUnitTestSupport {
         return result;
     }
 
+    private class TestHttpTestRequestHandler extends TestHttpRequestHandler {
+        private String method = "GET";
+
+        public TestHttpRequestHandler method(String method) {
+            this.method = method;
+            return this;
+        }
+
+        @Override
+        public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
+            super.handle(request, response, context);
+
+            if (!isValidRequest(request)) {
+                response.setStatusCode(405);
+                response.setEntity(null);
+            }
+        }
+
+        private boolean isValidRequest(HttpRequest request) {
+            return request.getRequestLine().getMethod().equals(this.method.toUpperCase());
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/5529d79e/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
index 354ade9..bff9dfc 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java
@@ -26,6 +26,7 @@ import java.net.HttpURLConnection;
 import java.net.URI;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.charset.Charset;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -43,6 +44,7 @@ import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLSession;
 
+import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.crypto.SslTrustUtils;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.net.URLParamEncoder;
@@ -53,7 +55,6 @@ import org.apache.brooklyn.util.time.Time;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.ConnectionReuseStrategy;
 import org.apache.http.HttpEntity;
-import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
 import org.apache.http.NameValuePair;
 import org.apache.http.auth.AuthScope;
@@ -62,11 +63,11 @@ import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.conn.scheme.Scheme;
@@ -399,94 +400,119 @@ public class HttpTool {
         }
     }
 
-    protected static abstract class HttpRequestBuilder<B extends HttpRequestBuilder<B, R>, R extends HttpRequest> {
-        protected R req;
-        
-        protected HttpRequestBuilder(R req) {
-            this.req = req;
+    public static class HttpRequestBuilder<R extends HttpRequestBase> {
+        private Class<R> requestClass;
+        private Map<String, String> headers;
+        private URI uri;
+        private HttpEntity body;
+
+        public HttpRequestBuilder(Class<R> requestClass) {
+            this.requestClass = requestClass;
+            this.headers = MutableMap.of();
         }
-        @SuppressWarnings("unchecked")
-        protected B self() {
-            return (B) this;
+
+        public HttpRequestBuilder<R> uri(URI uri) {
+            this.uri = uri;
+            return this;
         }
-        public B headers(Map<String,String> headers) {
-            if (headers!=null) {
-                for (Map.Entry<String,String> entry : headers.entrySet()) {
-                    req.addHeader(entry.getKey(), entry.getValue());
-                }
+
+        public HttpRequestBuilder<R> headers(Map<String, String> headers) {
+            if (headers != null) {
+                this.headers.putAll(headers);
             }
-            return self();
+            return this;
         }
-        public B headers(Multimap<String,String> headers) {
-            if (headers!=null) {
+
+        public HttpRequestBuilder<R> headers(Multimap<String, String> headers) {
+            if (headers != null) {
                 for (Map.Entry<String,String> entry : headers.entries()) {
-                    req.addHeader(entry.getKey(), entry.getValue());
+                    this.headers.put(entry.getKey(), entry.getValue());
                 }
             }
-            return self();
+            return this;
         }
-        public R build() {
-            return req;
+
+        public HttpRequestBuilder<R> body(byte[] body) {
+            if (body != null) {
+                this.body = new ByteArrayEntity(body);
+            }
+            return this;
         }
-    }
-    
-    protected static abstract class HttpEntityEnclosingRequestBaseBuilder<B extends HttpEntityEnclosingRequestBaseBuilder<B,R>, R extends HttpEntityEnclosingRequestBase> extends HttpRequestBuilder<B, R> {
-        protected HttpEntityEnclosingRequestBaseBuilder(R req) {
-            super(req);
+
+        public HttpRequestBuilder<R> body(String body) {
+            if (body != null) {
+                this.body(body.getBytes(Charset.forName("UTF-8")));
+            }
+            return this;
         }
-        public B body(byte[] body) {
+
+        public HttpRequestBuilder<R> body(Map<String, String> body) {
             if (body != null) {
-                HttpEntity httpEntity = new ByteArrayEntity(body);
-                req.setEntity(httpEntity);
+                Collection<NameValuePair> httpParams = new ArrayList<NameValuePair>(body.size());
+                for (Entry<String, String> param : body.entrySet()) {
+                    httpParams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
+                }
+                this.body = new UrlEncodedFormEntity(httpParams);
+            }
+            return this;
+        }
+
+        public R build() {
+            try {
+                R request = this.requestClass.newInstance();
+                request.setURI(this.uri);
+                for (Map.Entry<String,String> entry : this.headers.entrySet()) {
+                    request.addHeader(entry.getKey(), entry.getValue());
+                }
+                if (this.body != null) {
+                    if (request instanceof HttpPost) {
+                        ((HttpPost) request).setEntity(this.body);
+                    } else if (request instanceof HttpPut) {
+                        ((HttpPut) request).setEntity(this.body);
+                    } else {
+                        throw new Exception(this.requestClass.getSimpleName() + " does not support a request body");
+                    }
+                }
+                return request;
+            } catch (Exception e) {
+                LOG.warn("Cannot create the HTTP request for uri {}", this.uri);
+                throw Exceptions.propagate(e);
             }
-            return self();
         }
     }
     
-    public static class HttpGetBuilder extends HttpRequestBuilder<HttpGetBuilder, HttpGet> {
+    public static class HttpGetBuilder extends HttpRequestBuilder<HttpGet> {
         public HttpGetBuilder(URI uri) {
-            super(new HttpGet(uri));
+            super(HttpGet.class);
+            this.uri(uri);
         }
     }
     
-    public static class HttpHeadBuilder extends HttpRequestBuilder<HttpHeadBuilder, HttpHead> {
+    public static class HttpHeadBuilder extends HttpRequestBuilder<HttpHead> {
         public HttpHeadBuilder(URI uri) {
-            super(new HttpHead(uri));
+            super(HttpHead.class);
+            this.uri(uri);
         }
     }
     
-    public static class HttpDeleteBuilder extends HttpRequestBuilder<HttpDeleteBuilder, HttpDelete> {
+    public static class HttpDeleteBuilder extends HttpRequestBuilder<HttpDelete> {
         public HttpDeleteBuilder(URI uri) {
-            super(new HttpDelete(uri));
+            super(HttpDelete.class);
+            this.uri(uri);
         }
     }
     
-    public static class HttpPostBuilder extends HttpEntityEnclosingRequestBaseBuilder<HttpPostBuilder, HttpPost> {
+    public static class HttpPostBuilder extends HttpRequestBuilder<HttpPost> {
         public HttpPostBuilder(URI uri) {
-            super(new HttpPost(uri));
-        }
-    }
-
-    public static class HttpFormPostBuilder extends HttpRequestBuilder<HttpFormPostBuilder, HttpPost> {
-        HttpFormPostBuilder(URI uri) {
-            super(new HttpPost(uri));
-        }
-
-        public HttpFormPostBuilder params(Map<String, String> params) {
-            if (params != null) {
-                Collection<NameValuePair> httpParams = new ArrayList<NameValuePair>(params.size());
-                for (Entry<String, String> param : params.entrySet()) {
-                    httpParams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
-                }
-                req.setEntity(new UrlEncodedFormEntity(httpParams));
-            }
-            return self();
+            super(HttpPost.class);
+            this.uri(uri);
         }
     }
 
-    public static class HttpPutBuilder extends HttpEntityEnclosingRequestBaseBuilder<HttpPutBuilder, HttpPut> {
+    public static class HttpPutBuilder extends HttpRequestBuilder<HttpPut> {
         public HttpPutBuilder(URI uri) {
-            super(new HttpPut(uri));
+            super(HttpPut.class);
+            this.uri(uri);
         }
     }
     
@@ -521,7 +547,7 @@ public class HttpTool {
     }
 
     public static HttpToolResponse httpPost(HttpClient httpClient, URI uri, Map<String,String> headers, Map<String, String> params) {
-        HttpPost req = new HttpFormPostBuilder(uri).headers(headers).params(params).build();
+        HttpPost req = new HttpPostBuilder(uri).body(params).headers(headers).build();
         return execAndConsume(httpClient, req);
     }