brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aleds...@apache.org
Subject [3/5] brooklyn-server git commit: Adds HttpFeed.preemptiveBasicAuth
Date Fri, 04 Aug 2017 12:10:00 GMT
Adds HttpFeed.preemptiveBasicAuth


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

Branch: refs/heads/master
Commit: 6fb5166aad7f9731c0d912b4ad31578f1ff02554
Parents: 9e53af9
Author: Aled Sage <aled.sage@gmail.com>
Authored: Thu Aug 3 17:38:41 2017 +0100
Committer: Aled Sage <aled.sage@gmail.com>
Committed: Fri Aug 4 13:09:24 2017 +0100

----------------------------------------------------------------------
 .../core/sensor/http/HttpRequestSensor.java     | 12 ++-
 .../org/apache/brooklyn/feed/http/HttpFeed.java | 34 +++++++-
 .../core/sensor/http/HttpRequestSensorTest.java | 40 ++++++++-
 .../feed/http/HttpFeedIntegrationTest.java      | 10 +++
 .../apache/brooklyn/feed/http/HttpFeedTest.java | 89 +++++++++++++++++++-
 .../test/http/RecordingHttpRequestHandler.java  | 78 +++++++++++++++++
 6 files changed, 251 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6fb5166a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
index 22e24f1..107d9a4 100644
--- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java
@@ -56,8 +56,13 @@ public final class HttpRequestSensor<T> extends AddSensor<T>
{
     public static final ConfigKey<String> JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath",
"JSON path to select in HTTP response; default $", "$");
     public static final ConfigKey<String> USERNAME = ConfigKeys.newStringConfigKey("username",
"Username for HTTP request, if required");
     public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password",
"Password for HTTP request, if required");
-    public static final ConfigKey<Map<String, String>> HEADERS = new MapConfigKey(String.class,
"headers");
-
+    public static final ConfigKey<Map<String, String>> HEADERS = new MapConfigKey<>(String.class,
"headers");
+    
+    public static final ConfigKey<Boolean> PREEMPTIVE_BASIC_AUTH = ConfigKeys.newBooleanConfigKey(
+            "preemptiveBasicAuth",
+            "Whether to pre-emptively including a basic-auth header of the username:password
(rather than waiting for a challenge)",
+            Boolean.FALSE);
+    
     public HttpRequestSensor(final ConfigBag params) {
         super(params);
     }
@@ -85,7 +90,7 @@ public final class HttpRequestSensor<T> extends AddSensor<T>
{
         final String username = EntityInitializers.resolve(allConfig, USERNAME);
         final String password = EntityInitializers.resolve(allConfig, PASSWORD);
         final Map<String, String> headers = EntityInitializers.resolve(allConfig, HEADERS);
-
+        final Boolean preemptiveBasicAuth = EntityInitializers.resolve(allConfig, PREEMPTIVE_BASIC_AUTH);
         
         HttpPollConfig<T> pollConfig = new HttpPollConfig<T>(sensor)
                 .checkSuccess(HttpValueFunctions.responseCodeEquals(200))
@@ -96,6 +101,7 @@ public final class HttpRequestSensor<T> extends AddSensor<T>
{
         HttpFeed.Builder httpRequestBuilder = HttpFeed.builder().entity(entity)
                 .baseUri(uri)
                 .credentialsIfNotNull(username, password)
+                .preemptiveBasicAuth(Boolean.TRUE.equals(preemptiveBasicAuth))
                 .poll(pollConfig);
 
         if (headers != null) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6fb5166a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java b/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
index 36d19c2..37fb32f 100644
--- a/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
+++ b/core/src/main/java/org/apache/brooklyn/feed/http/HttpFeed.java
@@ -24,6 +24,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -47,11 +48,11 @@ import org.apache.brooklyn.core.location.Machines;
 import org.apache.brooklyn.util.executor.HttpExecutorFactory;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.http.HttpToolResponse;
-import org.apache.brooklyn.util.http.executor.UsernamePassword;
 import org.apache.brooklyn.util.http.executor.HttpConfig;
 import org.apache.brooklyn.util.http.executor.HttpExecutor;
 import org.apache.brooklyn.util.http.executor.HttpRequest;
 import org.apache.brooklyn.util.http.executor.HttpResponse;
+import org.apache.brooklyn.util.http.executor.UsernamePassword;
 import org.apache.brooklyn.util.http.executor.apacheclient.HttpExecutorImpl;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.time.Duration;
@@ -71,6 +72,7 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.SetMultimap;
 import com.google.common.collect.Sets;
+import com.google.common.io.BaseEncoding;
 import com.google.common.io.ByteStreams;
 import com.google.common.reflect.TypeToken;
 
@@ -144,6 +146,7 @@ public class HttpFeed extends AbstractFeed {
         private Credentials credentials;
         private String uniqueTag;
         private HttpExecutor httpExecutor;
+        private Boolean preemptiveBasicAuth;
         private volatile boolean built;
 
         public Builder entity(Entity val) {
@@ -220,6 +223,10 @@ public class HttpFeed extends AbstractFeed {
             }
             return this;
         }
+        public Builder preemptiveBasicAuth(Boolean val) {
+            this.preemptiveBasicAuth = val;
+            return this;
+        }
         public Builder uniqueTag(String uniqueTag) {
             this.uniqueTag = uniqueTag;
             return this;
@@ -228,6 +235,25 @@ public class HttpFeed extends AbstractFeed {
             this.httpExecutor = val;
             return this;
         }
+        public Map<String, String> buildBaseHeaders() {
+            if (Boolean.TRUE.equals(preemptiveBasicAuth)) {
+                Credentials creds = credentials;
+                if (creds == null) {
+                    throw new IllegalArgumentException("Must not enable preemptiveBasicAuth
when there are no credentials, in feed for "+baseUri);
+                }
+                String username = checkNotNull(creds.getUserPrincipal().getName(), "username");
+                String password = creds.getPassword();
+                String toencode = username + (password == null ? "" : ":"+password);
+                String headerVal = "Basic " + BaseEncoding.base64().encode((toencode).getBytes(StandardCharsets.UTF_8));
+                
+                return ImmutableMap.<String,String>builder()
+                        .put("Authorization", headerVal)
+                        .putAll(checkNotNull(headers, "headers"))
+                        .build();
+            } else {
+                return ImmutableMap.copyOf(checkNotNull(headers, "headers"));
+            }
+        }
         public HttpFeed build() {
             built = true;
             HttpFeed result = new HttpFeed(this);
@@ -297,8 +323,8 @@ public class HttpFeed extends AbstractFeed {
     }
     
     protected HttpFeed(Builder builder) {
-        setConfig(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp);
-        Map<String,String> baseHeaders = ImmutableMap.copyOf(checkNotNull(builder.headers,
"headers"));
+        config().set(ONLY_IF_SERVICE_UP, builder.onlyIfServiceUp);
+        Map<String,String> baseHeaders = builder.buildBaseHeaders();
 
         HttpExecutor httpExecutor;
         if (builder.httpExecutor != null) {
@@ -344,7 +370,7 @@ public class HttpFeed extends AbstractFeed {
 
             polls.put(new HttpPollIdentifier(httpExecutor, method, baseUriProvider, headers,
body, credentials, connectionTimeout, socketTimeout), configCopy);
         }
-        setConfig(POLLS, polls);
+        config().set(POLLS, polls);
         initUniqueTag(builder.uniqueTag, polls.values());
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6fb5166a/core/src/test/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensorTest.java
b/core/src/test/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensorTest.java
index 34b2d79..876b190 100644
--- a/core/src/test/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensorTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensorTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.brooklyn.core.sensor.http;
 
+import static org.testng.Assert.assertEquals;
+
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
@@ -28,15 +30,20 @@ import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.feed.http.HttpFeedTest;
+import org.apache.brooklyn.test.http.RecordingHttpRequestHandler;
 import org.apache.brooklyn.test.http.TestHttpRequestHandler;
 import org.apache.brooklyn.test.http.TestHttpServer;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.time.Duration;
+import org.apache.http.HttpRequest;
+import org.apache.http.protocol.HttpRequestHandler;
 import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public class HttpRequestSensorTest {
     final static AttributeSensor<String> SENSOR_STRING = Sensors.newStringSensor("aString");
@@ -47,11 +54,14 @@ public class HttpRequestSensorTest {
 
     private TestHttpServer server;
     private String serverUrl;
+    private RecordingHttpRequestHandler recordingHandler;
 
-    @BeforeClass(alwaysRun=true)
+    @BeforeMethod(alwaysRun=true)
     public void setUp() throws Exception {
+        HttpRequestHandler handler = new TestHttpRequestHandler().header("Content-Type",
"application/json").response("{\"myKey\":\"myValue\"}");
+        recordingHandler = new RecordingHttpRequestHandler(handler);
         server = new TestHttpServer()
-            .handler("/myKey/myValue", new TestHttpRequestHandler().header("Content-Type",
"application/json").response("{\"myKey\":\"myValue\"}"))
+            .handler("/myKey/myValue", recordingHandler)
             .start();
         serverUrl = server.getUrl();
 
@@ -67,8 +77,8 @@ public class HttpRequestSensorTest {
         server.stop();
     }
 
-    @SuppressWarnings("deprecation")
     @Test
+    @SuppressWarnings("deprecation")
     public void testHttpSensor() throws Exception {
         HttpRequestSensor<Integer> sensor = new HttpRequestSensor<Integer>(ConfigBag.newInstance()
                 .configure(HttpRequestSensor.SENSOR_PERIOD, Duration.millis(100))
@@ -82,4 +92,26 @@ public class HttpRequestSensorTest {
         EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_STRING, "myValue");
     }
 
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testPreemptiveBasicAuth() throws Exception {
+        HttpRequestSensor<Integer> sensor = new HttpRequestSensor<Integer>(ConfigBag.newInstance()
+                .configure(HttpRequestSensor.PREEMPTIVE_BASIC_AUTH, true)
+                .configure(HttpRequestSensor.USERNAME, "myuser")
+                .configure(HttpRequestSensor.PASSWORD, "mypass")
+                .configure(HttpRequestSensor.SENSOR_PERIOD, Duration.minutes(1))
+                .configure(HttpRequestSensor.SENSOR_NAME, SENSOR_STRING.getName())
+                .configure(HttpRequestSensor.SENSOR_TYPE, TARGET_TYPE)
+                .configure(HttpRequestSensor.JSON_PATH, "$.myKey")
+                .configure(HttpRequestSensor.SENSOR_URI, serverUrl + "/myKey/myValue"));
+        sensor.apply((org.apache.brooklyn.api.entity.EntityLocal)entity);
+        entity.sensors().set(Attributes.SERVICE_UP, true);
+        
+        EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_STRING, "myValue");
+        
+        HttpRequest req = Iterables.getFirst(recordingHandler.getRequests(), null);
+        String headerVal = req.getFirstHeader("Authorization").getValue();
+        String expectedVal = HttpFeedTest.getBasicAuthHeaderVal("myuser", "mypass");
+        assertEquals(headerVal, expectedVal);
+    }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6fb5166a/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedIntegrationTest.java
b/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedIntegrationTest.java
index 305fc75..e0fe384 100644
--- a/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedIntegrationTest.java
+++ b/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedIntegrationTest.java
@@ -99,6 +99,15 @@ public class HttpFeedIntegrationTest extends BrooklynAppUnitTestSupport
{
 
     @Test(groups = {"Integration"})
     public void testPollsAndParsesHttpGetResponseWithBasicAuthentication() throws Exception
{
+        runPollsAndParsesHttpGetResponseWithBasicAuthentication(false);
+    }
+    
+    @Test(groups = {"Integration"})
+    public void testPollsAndParsesHttpGetResponseWithPreemptiveBasicAuthentication() throws
Exception {
+        runPollsAndParsesHttpGetResponseWithBasicAuthentication(true);
+    }
+    
+    protected void runPollsAndParsesHttpGetResponseWithBasicAuthentication(boolean preemptiveBasicAuth)
throws Exception {
         final String username = "brooklyn";
         final String password = "hunter2";
         httpService = new HttpService(PortRanges.fromString("9000+"))
@@ -111,6 +120,7 @@ public class HttpFeedIntegrationTest extends BrooklynAppUnitTestSupport
{
                 .entity(entity)
                 .baseUri(baseUrl)
                 .credentials(username, password)
+                .preemptiveBasicAuth(preemptiveBasicAuth)
                 .poll(new HttpPollConfig<Integer>(SENSOR_INT)
                         .period(100)
                         .onSuccess(HttpValueFunctions.responseCode()))

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6fb5166a/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedTest.java b/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedTest.java
index f2c8431..08abbe6 100644
--- a/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedTest.java
+++ b/core/src/test/java/org/apache/brooklyn/feed/http/HttpFeedTest.java
@@ -22,6 +22,7 @@ import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.concurrent.Callable;
 
@@ -30,6 +31,7 @@ import org.apache.brooklyn.api.entity.EntitySpec;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityAsserts;
 import org.apache.brooklyn.core.entity.EntityFunctions;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.entity.EntityInternal.FeedSupport;
@@ -42,8 +44,8 @@ import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.http.BetterMockWebServer;
-import org.apache.brooklyn.util.http.HttpToolResponse;
 import org.apache.brooklyn.util.guava.Functionals;
+import org.apache.brooklyn.util.http.HttpToolResponse;
 import org.apache.brooklyn.util.net.Networking;
 import org.apache.brooklyn.util.time.Duration;
 import org.slf4j.Logger;
@@ -59,7 +61,9 @@ import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import com.google.common.io.BaseEncoding;
 import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.RecordedRequest;
 import com.google.mockwebserver.SocketPolicy;
 
 public class HttpFeedTest extends BrooklynAppUnitTestSupport {
@@ -360,7 +364,90 @@ public class HttpFeedTest extends BrooklynAppUnitTestSupport {
         
         server.shutdown();
     }
+    
+    @Test
+    public void testPreemptiveBasicAuth() throws Exception {
+        final String username = "brooklyn";
+        final String password = "hunter2";
+
+        feed = HttpFeed.builder()
+                .entity(entity)
+                .baseUrl(server.getUrl("/"))
+                .credentials(username, password)
+                .preemptiveBasicAuth(true)
+                .poll(new HttpPollConfig<Integer>(SENSOR_INT)
+                        .period(100)
+                        .onSuccess(HttpValueFunctions.responseCode())
+                        .onException(Functions.constant(-1)))
+                .build();
+
+        EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_INT, 200);
+        RecordedRequest req = server.takeRequest();
+        String headerVal = req.getHeader("Authorization");
+        String expectedVal = getBasicAuthHeaderVal(username, password);
+        assertEquals(headerVal, expectedVal);
+    }
+
+    @Test
+    public void testPreemptiveBasicAuthFailsIfNoCredentials() throws Exception {
+        try {
+            feed = HttpFeed.builder()
+                    .entity(entity)
+                    .baseUrl(new URL("http://shouldNeverBeCalled.org"))
+                    .preemptiveBasicAuth(true)
+                    .poll(new HttpPollConfig<Integer>(SENSOR_INT)
+                            .period(100)
+                            .onSuccess(HttpValueFunctions.responseCode())
+                            .onException(Functions.constant(-1)))
+                    .build();
+            Asserts.shouldHaveFailedPreviously();
+        } catch (IllegalArgumentException e) {
+            Asserts.expectedFailureContains(e, "Must not enable preemptiveBasicAuth when
there are no credentials");
+        }
+    }
 
+    // Expected behaviour of o.a.http.client is that it first sends the request without credentials,
+    // and then when given a challenge for basic-auth it re-sends the request with the basic-auth
header.
+    @Test
+    public void testNonPreemptiveBasicAuth() throws Exception {
+        final String username = "brooklyn";
+        final String password = "hunter2";
+        
+        if (server != null) server.shutdown();
+        server = BetterMockWebServer.newInstanceLocalhost();
+        server.enqueue(new MockResponse()
+                .setResponseCode(401)
+                .addHeader("WWW-Authenticate", "Basic"));
+        server.enqueue(new MockResponse()
+                .setResponseCode(200)
+                .setBody("Hello World"));
+        server.play();
+
+        feed = HttpFeed.builder()
+                .entity(entity)
+                .baseUrl(server.getUrl("/"))
+                .credentials(username, password)
+                .poll(new HttpPollConfig<Integer>(SENSOR_INT)
+                        .period(Duration.ONE_MINUTE) // so only dealing with first request
+                        .onSuccess(HttpValueFunctions.responseCode())
+                        .onException(Functions.constant(-1)))
+                .build();
+
+        EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_INT, 200);
+        RecordedRequest req = server.takeRequest();
+        assertEquals(req.getHeader("Authorization"), null);
+        
+        RecordedRequest req2 = server.takeRequest();
+        String headerVal = req2.getHeader("Authorization");
+        String expected = getBasicAuthHeaderVal(username, password);
+        assertEquals(headerVal, expected);
+    }
+
+    public static String getBasicAuthHeaderVal(String username, String password) {
+        String toencode = username + (password == null ? "" : ":"+password);
+        return "Basic " + BaseEncoding.base64().encode((toencode).getBytes(StandardCharsets.UTF_8));
+    }
+    
     private void newMultiFeed(URL baseUrl) {
         feed = HttpFeed.builder()
                 .entity(entity)

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6fb5166a/utils/common/src/main/java/org/apache/brooklyn/test/http/RecordingHttpRequestHandler.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/http/RecordingHttpRequestHandler.java
b/utils/common/src/main/java/org/apache/brooklyn/test/http/RecordingHttpRequestHandler.java
new file mode 100644
index 0000000..5b9ced4
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/test/http/RecordingHttpRequestHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.test.http;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.brooklyn.test.Asserts;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.testng.Assert;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class RecordingHttpRequestHandler implements HttpRequestHandler {
+    private final HttpRequestHandler delegate;
+
+    private final List<HttpRequest> requests = Lists.newCopyOnWriteArrayList();
+    
+    public RecordingHttpRequestHandler(HttpRequestHandler delegate) {
+        this.delegate = checkNotNull(delegate, "delegate");
+    }
+
+    @Override
+    public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws
HttpException, IOException {
+        requests.add(request);
+        delegate.handle(request, response, context);
+    }
+
+    public void assertHasRequest(Predicate<? super HttpRequest> filter) {
+        for (HttpRequest req : requests) {
+            if (filter.apply(req)) {
+                return;
+            }
+        }
+        Assert.fail("No request matching filter "+ filter);
+    }
+
+    public void assertHasRequestEventually(Predicate<? super HttpRequest> filter) {
+        Asserts.succeedsEventually(new Runnable() {
+            @Override
+            public void run() {
+                assertHasRequest(filter);
+            }});
+    }
+    
+    public List<HttpRequest> getRequests(Predicate<? super HttpRequest> filter)
{
+        return ImmutableList.copyOf(Iterables.filter(requests, filter));
+    }
+
+    public List<HttpRequest> getRequests() {
+        return ImmutableList.copyOf(requests);
+    }
+}


Mime
View raw message