Commits

Jean-Francois Arcand  committed 6f322a8

Repackage to align with other Sonatype product.

  • Participants
  • Parent commits 9567a64

Comments (0)

Files changed (13)

File src/main/java/org/sonatype/spice/jersey/client/ahc/AhcClientHandler.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+package org.sonatype.spice.jersey.client.ahc;
+
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.Cookie;
+import com.ning.http.client.FluentCaseInsensitiveStringsMap;
+import com.ning.http.client.RequestBuilder;
+import com.ning.http.client.Response;
+import com.sun.jersey.api.client.ClientHandler;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientRequest;
+import com.sun.jersey.api.client.ClientResponse;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+import com.sun.jersey.core.header.InBoundHeaders;
+import com.sun.jersey.spi.MessageBodyWorkers;
+
+import javax.ws.rs.core.Context;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A root handler with Sonatype AsyncHttpClient acting as a backend.
+ * <p/>
+ * Client operations are thread safe, the HTTP connection may
+ * be shared between different threads.
+ * <p/>
+ * If a response entity is obtained that is an instance of {@link java.io.Closeable}
+ * then the instance MUST be closed after processing the entity to release
+ * connection-based resources.
+ * <p/>
+ * If a {@link ClientResponse} is obtained and an entity is not read from the
+ * response then {@link ClientResponse#close() } MUST be called after processing
+ * the response to release connection-based resources.
+ * <p/>
+ * The following methods are currently supported: HEAD, GET, POST, PUT, DELETE, TRACE
+ * OPTIONS as well as custom methods.
+ * <p/>
+ *
+ * @author Jeanfrancois Arcand
+ */
+public final class AhcClientHandler implements ClientHandler {
+
+    private final AsyncHttpClient client;
+
+    private final AhcConfig config;
+
+    private final AhcRequestWriter requestWriter = new AhcRequestWriter();
+
+    private List<Cookie> cookies = new ArrayList<Cookie>();
+
+    @Context
+    private MessageBodyWorkers workers;
+
+    /**
+     * Create a new root handler with an {@link AsyncHttpClient}.
+     *
+     * @param client the {@link AsyncHttpClient}.
+     */
+    public AhcClientHandler(AsyncHttpClient client) {
+        this(client, new DefaultAhcConfig());
+    }
+
+    /**
+     * Create a new root handler with an {@link AsyncHttpClient}.
+     *
+     * @param client the {@link AsyncHttpClient}.
+     * @param config the client configuration.
+     */
+    public AhcClientHandler(AsyncHttpClient client, AhcConfig config) {
+        this.client = client;
+        this.config = config;
+    }
+
+    /**
+     * Get the client config.
+     *
+     * @return the client config.
+     */
+    public AhcConfig getConfig() {
+        return config;
+    }
+
+    /**
+     * Get the {@link AsyncHttpClient}.
+     *
+     * @return the {@link AsyncHttpClient}.
+     */
+    public AsyncHttpClient getHttpClient() {
+        return client;
+    }
+
+    /**
+     * Translate the {@link ClientRequest} into a AsyncHttpClient request, and execute it.
+     * @param cr the HTTP request.
+     * @return the {@link ClientResponse}
+     * @throws ClientHandlerException
+     */
+    @Override
+    public ClientResponse handle(final ClientRequest cr)
+            throws ClientHandlerException {
+
+        try {
+            final RequestBuilder requestBuilder = getRequestBuilder(cr);
+            handleCookie(requestBuilder);
+            requestWriter.configureRequest(requestBuilder, cr, allowBody(cr.getMethod()));
+
+            final Response response = client.executeRequest(requestBuilder.build()).get();
+
+            cookies = response.getCookies();
+
+            ClientResponse r = new ClientResponse(response.getStatusCode(),
+                    getInBoundHeaders(response),
+                    response.getResponseBodyAsStream(),
+                    workers);
+            if (!r.hasEntity()) {
+                r.bufferEntity();
+                r.close();
+            }
+            return r;
+        } catch (Exception e) {
+            throw new ClientHandlerException(e);
+        }
+    }
+
+    /**
+     * Check if a body needs to be constructed based on a method's name.
+     * @param method An HTTP method
+     * @return true if s body can be allowed.
+     */
+    private boolean allowBody(String method) {
+        if ( method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("OPTIONS")
+                    && method.equalsIgnoreCase("TRACE")
+                    && method.equalsIgnoreCase("HEAD")) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Return the {@link RequestBuilder} based on a method 
+     * @param cr the HTTP request.
+     * @return {@link RequestBuilder}
+     */
+    private RequestBuilder getRequestBuilder(ClientRequest cr) {
+        final String strMethod = cr.getMethod();
+        final String uri = cr.getURI().toString();
+
+        if (strMethod.equals("GET")) {
+            return new RequestBuilder("GET").setUrl(uri);
+        } else if (strMethod.equals("POST")) {
+            return new RequestBuilder("POST").setUrl(uri);
+        } else if (strMethod.equals("PUT")) {
+            return new RequestBuilder("PUT").setUrl(uri);
+        } else if (strMethod.equals("DELETE")) {
+            return new RequestBuilder("DELETE").setUrl(uri);
+        } else if (strMethod.equals("HEAD")) {
+            return new RequestBuilder("HEAD").setUrl(uri);
+        } else if (strMethod.equals("OPTIONS")) {
+            return new RequestBuilder("OPTIONS").setUrl(uri);
+        } else {
+            return new RequestBuilder(strMethod).setUrl(uri);
+        }
+    }
+
+    private InBoundHeaders getInBoundHeaders(Response response) {
+        InBoundHeaders headers = new InBoundHeaders();
+        FluentCaseInsensitiveStringsMap respHeaders = response.getHeaders();
+        for (Map.Entry<String, List<String>> header : respHeaders) {
+            headers.put(header.getKey(), header.getValue());
+        }
+        return headers;
+    }
+
+    /**
+     * Return the instance of {@link com.sun.jersey.api.client.RequestWriter}. This instance will be injected
+     * within Jersey so it cannot be null.
+     * @return the instance of {@link com.sun.jersey.api.client.RequestWriter}.
+     */
+    public AhcRequestWriter getAhcRequestWriter(){
+        return requestWriter;
+    }
+
+    private void handleCookie(RequestBuilder requestBuilder) {
+        for (Cookie c : cookies) {
+            requestBuilder.addCookie(c);
+        }
+    }
+    
+}

File src/main/java/org/sonatype/spice/jersey/client/ahc/AhcHttpClient.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+package org.sonatype.spice.jersey.client.ahc;
+
+import com.ning.http.client.AsyncHttpClient;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.config.ClientConfig;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
+
+/**
+ * A {@link Client} that utilizes the AsyncHttpClient to send and receive
+ * HTTP request and responses.
+ * <p>
+ * If an {@link AhcClientHandler} is not explicitly passed as a
+ * constructor or method parameter then by default an instance is created with
+ * an {@link AsyncHttpClient} constructed
+ * <p>
+ * <p>
+ * If a response entity is obtained that is an instance of
+ * {@link java.io.Closeable}
+ * then the instance MUST be closed after processing the entity to release
+ * connection-based resources.
+ * <p>
+ * If a {@link com.sun.jersey.api.client.ClientResponse} is obtained and an
+ * entity is not read from the response then
+ * {@link com.sun.jersey.api.client.ClientResponse#close() } MUST be called
+ * after processing the response to release connection-based resources.
+ *
+ * @author Jeanfrancois Arcand
+ */
+public class AhcHttpClient extends Client {
+
+    private AhcClientHandler clientHandler;
+    
+    /**
+     * Create a new client instance.
+     *
+     */
+    public AhcHttpClient() {
+        this(createDefaultClientHander(new DefaultAhcConfig()));
+    }
+
+    /**
+     * Create a new client instance.
+     *
+     * @param root the root client handler for dispatching a request and
+     *        returning a response.
+     */
+    public AhcHttpClient(AhcClientHandler root) {
+        this(root, null);
+    }
+
+    /**
+     * Create a new instance with a client configuration and a
+     * component provider.
+     *
+     * @param root the root client handler for dispatching a request and
+     *        returning a response.
+     * @param config the client configuration.
+     * @param provider the IoC component provider factory.
+     * @deprecated the config parameter is no longer utilized and instead
+     *             the config obtained from the {@link AhcClientHandler#getConfig() }
+     *             is utilized instead.
+     */
+    @Deprecated
+    public AhcHttpClient(AhcClientHandler root, ClientConfig config,
+            IoCComponentProviderFactory provider) {
+        this(root, provider);
+    }
+
+    /**
+     * Create a new instance with a client configuration and a
+     * component provider.
+     *
+     * @param root the root client handler for dispatching a request and
+     *        returning a response.
+     * @param provider the IoC component provider factory.
+     */
+    public AhcHttpClient(AhcClientHandler root,
+            IoCComponentProviderFactory provider) {
+        super(root, root.getConfig(), provider);
+
+        this.clientHandler = root;
+        inject(this.clientHandler.getAhcRequestWriter());
+    }
+
+    /**
+     * Get the AsyncHttpClient client handler.
+     * 
+     * @return the AsyncHttpClient client handler.
+     */
+    public AhcClientHandler getClientHandler() {
+        return clientHandler;
+    }
+
+    /**
+     * Create a default client.
+     *
+     * @return a default client.
+     */
+    public static AhcHttpClient create() {
+        return create(new DefaultAhcConfig());
+    }
+
+    /**
+     * Create a default client with client configuration.
+     *
+     * @param cc the client configuration.
+     * @return a default client.
+     */
+    public static AhcHttpClient create(ClientConfig cc) {
+        return create(cc, null);
+    }
+
+    /**
+     * Create a default client with client configuration and component provider.
+     *
+     * @param cc the client configuration.
+     * @param provider the IoC component provider factory.
+     * @return a default client.
+     */
+    public static AhcHttpClient create(ClientConfig cc, IoCComponentProviderFactory provider) {
+        return new AhcHttpClient(createDefaultClientHander(cc), provider);
+    }
+
+    @Override
+    public void destroy() {
+        clientHandler.getHttpClient().close();    
+    }
+
+    /**
+     * Create a default AsyncHttpClient client handler.
+     *
+     * @return a default AsyncHttpClient client handler.
+     */
+    private static AhcClientHandler createDefaultClientHander(ClientConfig cc) {
+
+        if (AhcConfig.class.isAssignableFrom(cc.getClass()) || DefaultAhcConfig.class.isAssignableFrom(cc.getClass())) {
+            AhcConfig c = AhcConfig.class.cast(cc);
+            return new AhcClientHandler(new AsyncHttpClient(c.getAsyncHttpClientConfigBuilder().build()), c);
+        } else {
+            throw new IllegalStateException("Client Config Type not supported");
+        }
+    }
+
+    @Override
+    public void setFollowRedirects(Boolean redirect) {
+        clientHandler.getConfig().getAsyncHttpClientConfigBuilder().setFollowRedirects(redirect);
+    }
+
+    @Override
+    public void setReadTimeout(Integer interval) {
+        clientHandler.getConfig().getAsyncHttpClientConfigBuilder().setRequestTimeoutInMs(interval);
+    }
+
+    @Override
+    public void setConnectTimeout(Integer interval) {
+        clientHandler.getConfig().getAsyncHttpClientConfigBuilder().setConnectionTimeoutInMs(interval);
+    }
+}

File src/main/java/org/sonatype/spice/jersey/client/ahc/AhcRequestWriter.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+package org.sonatype.spice.jersey.client.ahc;
+
+import com.ning.http.client.PerRequestConfig;
+import com.ning.http.client.Request;
+import com.ning.http.client.RequestBuilder;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientRequest;
+import com.sun.jersey.api.client.CommittingOutputStream;
+import com.sun.jersey.api.client.RequestWriter;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+
+import javax.ws.rs.core.MultivaluedMap;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation of {@link RequestWriter} that also configure the AHC {@link RequestBuilder}
+ *
+ * @author Jeanfrancois Arcand
+ */
+public class AhcRequestWriter extends RequestWriter {
+
+    public void configureRequest(final RequestBuilder requestBuilder, final ClientRequest cr, boolean needsBody) {
+        final Map<String, Object> props = cr.getProperties();
+
+        // Set the read timeout
+        final Integer readTimeout = (Integer) props.get(AhcConfig.PROPERTY_READ_TIMEOUT);
+        if (readTimeout != null) {
+            PerRequestConfig c = new PerRequestConfig();
+            c.setRequestTimeoutInMs(readTimeout);
+            requestBuilder.setPerRequestConfig(c);
+        }
+        configureHeaders(cr.getMetadata(), requestBuilder);
+        if (cr.getEntity() != null && needsBody) {
+            final RequestEntityWriter re = getRequestEntityWriter(cr);
+
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                re.writeRequestEntity(new CommittingOutputStream(baos) {
+                    @Override
+                    protected void commit() throws IOException {
+                        configureHeaders(cr.getMetadata(), requestBuilder);
+                    }
+                });
+            } catch (IOException ex) {
+                throw new ClientHandlerException(ex);
+            }
+
+            final byte[] content = baos.toByteArray();
+            requestBuilder.setBody(new Request.EntityWriter() {
+                @Override
+                public void writeEntity(OutputStream out) throws IOException {
+                    out.write(content);
+                }
+            });
+        }
+    }
+
+    private void configureHeaders(MultivaluedMap<String, Object> metadata, RequestBuilder requestBuilder) {
+        for (Map.Entry<String, List<Object>> e : metadata.entrySet()) {
+            List<Object> vs = e.getValue();
+            for (Object o : vs) {
+                requestBuilder.addHeader(e.getKey(), headerValueToString(o));
+            }
+        }
+    }
+}

File src/main/java/org/sonatype/spice/jersey/client/ahc/config/AhcConfig.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+package org.sonatype.spice.jersey.client.ahc.config;
+
+import com.ning.http.client.AsyncHttpClientConfig;
+import com.sun.jersey.api.client.config.ClientConfig;
+
+public interface AhcConfig extends ClientConfig {
+
+    /**
+     * Get the {@link com.ning.http.client.AsyncHttpClientConfig.Builder} config object. Credentials may be set on the it.
+     * <p>
+     * @return the {@link com.ning.http.client.AsyncHttpClientConfig.Builder}
+     */
+    public AsyncHttpClientConfig.Builder getAsyncHttpClientConfigBuilder();
+}

File src/main/java/org/sonatype/spice/jersey/client/ahc/config/DefaultAhcConfig.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+package org.sonatype.spice.jersey.client.ahc.config;
+
+import com.ning.http.client.AsyncHttpClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+
+public class DefaultAhcConfig extends DefaultClientConfig implements AhcConfig{
+
+    private AsyncHttpClientConfig.Builder config;
+
+    public AsyncHttpClientConfig.Builder getAsyncHttpClientConfigBuilder() {
+
+        if (config == null) {
+            config = new AsyncHttpClientConfig.Builder();
+        }
+
+        return config;
+    }
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/AbstractGrizzlyServerTester.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.grizzly.http.SelectorThread;
+import com.sun.grizzly.tcp.Adapter;
+import com.sun.jersey.api.container.ContainerFactory;
+import com.sun.jersey.api.container.grizzly.GrizzlyServerFactory;
+import com.sun.jersey.api.core.ResourceConfig;
+import junit.framework.TestCase;
+
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public abstract class AbstractGrizzlyServerTester extends TestCase {
+    public static final String CONTEXT = "";
+
+    private SelectorThread selectorThread;
+
+    private int port = getEnvVariable("JERSEY_HTTP_PORT", 9997);
+    
+    private static int getEnvVariable(final String varName, int defaultValue) {
+        if (null == varName) {
+            return defaultValue;
+        }
+        String varValue = System.getenv(varName);
+        if (null != varValue) {
+            try {
+                return Integer.parseInt(varValue);
+            }catch (NumberFormatException e) {
+                // will return default value bellow
+            }
+        }
+        return defaultValue;
+    }
+
+    public AbstractGrizzlyServerTester(String name) {
+        super(name);
+    }
+    
+    public UriBuilder getUri() {
+        return UriBuilder.fromUri("http://localhost").port(port).path(CONTEXT);
+    }
+    
+    public void startServer(Class... resources) {
+        start(ContainerFactory.createContainer(Adapter.class, resources));
+    }
+    
+    public void startServer(ResourceConfig config) {
+        start(ContainerFactory.createContainer(Adapter.class, config));
+    }
+    
+    private void start(Adapter adapter) {
+        if (selectorThread != null && selectorThread.isRunning()){
+            stopServer();
+        }
+
+        System.out.println("Starting GrizzlyServer port number = " + port);
+        
+        URI u = UriBuilder.fromUri("http://localhost").port(port).build();
+        try {
+            selectorThread = GrizzlyServerFactory.create(u, adapter);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("Started GrizzlyServer");
+
+        int timeToSleep = getEnvVariable("JERSEY_HTTP_SLEEP", 0);
+        if (timeToSleep > 0) {
+            System.out.println("Sleeping for " + timeToSleep + " ms");
+            try {
+                // Wait for the server to start
+                Thread.sleep(timeToSleep);
+            } catch (InterruptedException ex) {
+                System.out.println("Sleeping interrupted: " + ex.getLocalizedMessage());
+            }
+        }
+    }
+    
+    public void stopServer() {
+        if (selectorThread.isRunning()) {
+            selectorThread.stopEndpoint();
+        }
+    }
+    
+    @Override
+    public void tearDown() {
+        stopServer();
+    }
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/AuthTest.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.ning.http.client.Realm;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
+import com.sun.jersey.api.container.filter.LoggingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import com.sun.jersey.spi.resource.Singleton;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class AuthTest extends AbstractGrizzlyServerTester {
+
+    public AuthTest(String testName) {
+        super(testName);
+    }
+    
+    @Path("/")
+    public static class PreemptiveAuthResource {
+        @GET
+        public String get(@Context HttpHeaders h) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            assertNotNull(value);
+            return "GET";
+        }
+
+        @POST
+        public String post(@Context HttpHeaders h, String e) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            assertNotNull(value);
+            return e;
+        }
+    }
+        
+    public void testPreemptiveAuth() {
+        ResourceConfig rc = new DefaultResourceConfig(PreemptiveAuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(true).setPrincipal("name").setPassword("password").build());
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().build());
+        assertEquals("GET", r.get(String.class));
+    }
+
+    public void testPreemptiveAuthPost() {
+        ResourceConfig rc = new DefaultResourceConfig(PreemptiveAuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(true).setPrincipal("name").setPassword("password").build());
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().build());
+        assertEquals("POST", r.post(String.class, "POST"));
+    }
+
+    @Path("/test")
+    @Singleton
+    public static class AuthResource {
+        int requestCount = 0;
+        @GET
+        public String get(@Context HttpHeaders h) {
+            requestCount++;
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                assertEquals(1, requestCount);
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            } else {
+                assertTrue(requestCount > 1);
+            }
+
+            return "GET";
+        }
+
+        @GET
+        @Path("filter")
+        public String getFilter(@Context HttpHeaders h) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+
+            return "GET";
+        }
+
+        @POST
+        public String post(@Context HttpHeaders h, String e) {
+            requestCount++;
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                assertEquals(1, requestCount);
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            } else {
+                assertTrue(requestCount > 1);
+            }
+
+            return e;
+        }
+
+        @POST
+        @Path("filter")
+        public String postFilter(@Context HttpHeaders h, String e) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+
+            return e;
+        }
+
+        @DELETE
+        public void delete(@Context HttpHeaders h) {
+            requestCount++;
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                assertEquals(1, requestCount);
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            } else {
+                assertTrue(requestCount > 1);
+            }
+        }
+
+        @DELETE
+        @Path("filter")
+        public void deleteFilter(@Context HttpHeaders h) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+        }
+
+        @DELETE
+        @Path("filter/withEntity")
+        public String deleteFilterWithEntity(@Context HttpHeaders h, String e) {
+            String value = h.getRequestHeaders().getFirst("Authorization");
+            if (value == null) {
+                throw new WebApplicationException(Response.status(401).header("WWW-Authenticate", "Basic realm=\"WallyWorld\"").build());
+            }
+
+            return e;
+        }
+
+
+        
+    }
+
+    public void testAuthGet() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(false).setPrincipal("name").setPassword("password").build());
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+        assertEquals("GET", r.get(String.class));
+    }
+
+    public void testAuthGetWithClientFilter() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+        AhcHttpClient c = AhcHttpClient.create();
+        c.addFilter(new HTTPBasicAuthFilter("name", "password"));
+
+        WebResource r = c.resource(getUri().path("test/filter").build());
+        assertEquals("GET", r.get(String.class));
+    }
+
+    public void testAuthPost() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+//        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+//                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(false).setPrincipal("name").setPassword("password").build());
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+        assertEquals("POST", r.post(String.class, "POST"));
+    }
+
+    public void testAuthPostWithClientFilter() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+        AhcHttpClient c = AhcHttpClient.create();
+        c.addFilter(new HTTPBasicAuthFilter("name", "password"));
+
+        WebResource r = c.resource(getUri().path("test/filter").build());
+        assertEquals("POST", r.post(String.class, "POST"));
+    }
+
+    public void testAuthDelete() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(false).setPrincipal("name").setPassword("password").build());
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+        ClientResponse response = r.delete(ClientResponse.class);
+        assertEquals(response.getStatus(), 204);
+    }
+
+    public void testAuthDeleteWithClientFilter() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+        AhcHttpClient c = AhcHttpClient.create();
+        c.addFilter(new HTTPBasicAuthFilter("name", "password"));
+
+        WebResource r = c.resource(getUri().path("test/filter").build());
+        ClientResponse response = r.delete(ClientResponse.class);
+        assertEquals(204, response.getStatus());
+    }
+
+    public void testAuthDeleteWithEntityUsingClientFilter() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+        AhcHttpClient c = AhcHttpClient.create();
+        c.addFilter(new HTTPBasicAuthFilter("name", "password"));
+
+        WebResource r = c.resource(getUri().path("test/filter/withEntity").build());
+        ClientResponse response = r.delete(ClientResponse.class, "DELETE");
+        assertEquals(200, response.getStatus());
+        assertEquals("DELETE", response.getEntity(String.class));
+    }
+
+    public void testAuthInteractiveGet() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(false).setPrincipal("name").setPassword("password").build());
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+        assertEquals("GET", r.get(String.class));
+    }
+
+    public void testAuthInteractivePost() {
+        ResourceConfig rc = new DefaultResourceConfig(AuthResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getAsyncHttpClientConfigBuilder().setRealm(new Realm.RealmBuilder().setUsePreemptiveAuth(false).setPrincipal("name").setPassword("password").build());
+
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+        assertEquals("POST", r.post(String.class, "POST"));
+    }
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/CookieTest.java

+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License").  You
+ * may not use this file except in compliance with the License.  You can
+ * obtain a copy of the License at
+ * http://glassfish.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt.  See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license."  If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above.  However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.container.filter.LoggingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class CookieTest extends AbstractGrizzlyServerTester {
+    @Path("/")
+    public static class CookieResource {
+        @GET
+        public Response get(@Context HttpHeaders h) {
+            Cookie c = h.getCookies().get("name");
+            String e = (c == null) ? "NO-COOKIE" : c.getValue();
+            return Response.ok(e).
+                    cookie(new NewCookie("name", "value")).build();
+        }
+    }
+        
+    public CookieTest(String testName) {
+        super(testName);
+    }
+    
+    public void testCookie() {
+        ResourceConfig rc = new DefaultResourceConfig(CookieResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().build());
+
+        assertEquals("NO-COOKIE", r.get(String.class));
+        assertEquals("value", r.get(String.class));
+    }
+
+    public void testCookieWithState() {
+        ResourceConfig rc = new DefaultResourceConfig(CookieResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().build());
+
+        assertEquals("NO-COOKIE", r.get(String.class));
+        assertEquals("value", r.get(String.class));
+
+    }
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/GZIPContentEncodingTest.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import java.util.Arrays;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class GZIPContentEncodingTest extends AbstractGrizzlyServerTester {
+
+    @Path("/")
+    public static class Resource {
+        @POST
+        public byte[] post(byte[] content) { return content; }
+    }
+    
+    public GZIPContentEncodingTest(String testName) {
+        super(testName);
+    }
+
+
+    public void testPost() {
+        ResourceConfig rc = new DefaultResourceConfig(Resource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                GZIPContentEncodingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                GZIPContentEncodingFilter.class.getName());
+        startServer(rc);
+
+        AhcHttpClient c = AhcHttpClient.create();
+        c.addFilter(new com.sun.jersey.api.client.filter.GZIPContentEncodingFilter());
+
+        WebResource r = c.resource(getUri().path("/").build());
+        byte[] content = new byte[1024 * 1024];
+        assertTrue(Arrays.equals(content, r.post(byte[].class, content)));
+
+        ClientResponse cr = r.post(ClientResponse.class, content);
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPostChunked() {
+        ResourceConfig rc = new DefaultResourceConfig(Resource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                GZIPContentEncodingFilter.class.getName());
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
+                GZIPContentEncodingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getProperties().put(AhcConfig.PROPERTY_CHUNKED_ENCODING_SIZE, 1024);
+        AhcHttpClient c = AhcHttpClient.create(config);
+        c.addFilter(new com.sun.jersey.api.client.filter.GZIPContentEncodingFilter());
+
+        WebResource r = c.resource(getUri().path("/").build());
+        byte[] content = new byte[1024 * 1024];
+        assertTrue(Arrays.equals(content, r.post(byte[].class, content)));
+
+        ClientResponse cr = r.post(ClientResponse.class, "POST");
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/HttpHeadersTest.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.container.filter.LoggingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class HttpHeadersTest extends AbstractGrizzlyServerTester {
+    @Path("/test")
+    public static class HttpMethodResource {
+        @POST
+        public String post(
+                @HeaderParam("Transfer-Encoding") String transferEncoding,
+                @HeaderParam("X-CLIENT") String xClient,
+                @HeaderParam("X-WRITER") String xWriter,
+                String entity) {
+            assertEquals("client", xClient);
+            if (transferEncoding == null || !transferEncoding.equals("chunked"))
+                assertEquals("writer", xWriter);
+            return entity;
+        }
+    }
+
+    @Provider
+    @Produces("text/plain")
+    public static class HeaderWriter implements MessageBodyWriter<String> {
+
+        public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+            return type == String.class;
+        }
+
+        public long getSize(String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+            return -1;
+        }
+
+        public void writeTo(String t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
+            httpHeaders.add("X-WRITER", "writer");
+            entityStream.write(t.getBytes());
+        }
+    }
+
+    public HttpHeadersTest(String testName) {
+        super(testName);
+    }
+
+    public void testPost() {
+        startServer(HttpMethodResource.class);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getClasses().add(HeaderWriter.class);
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+
+        ClientResponse cr = r.header("X-CLIENT", "client").post(ClientResponse.class, "POST");
+        assertEquals(200, cr.getStatus());
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPostChunked() {
+        ResourceConfig rc = new DefaultResourceConfig(HttpMethodResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getClasses().add(HeaderWriter.class);
+        config.getProperties().put(AhcConfig.PROPERTY_CHUNKED_ENCODING_SIZE, 1024);
+        AhcHttpClient c = AhcHttpClient.create(config);
+
+        WebResource r = c.resource(getUri().path("test").build());
+
+        ClientResponse cr = r.header("X-CLIENT", "client").post(ClientResponse.class, "POST");
+        assertEquals(200, cr.getStatus());
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+
+
+
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/HttpMethodTest.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.container.filter.LoggingFilter;
+import com.sun.jersey.api.core.DefaultResourceConfig;
+import com.sun.jersey.api.core.ResourceConfig;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+import org.sonatype.spice.jersey.client.ahc.config.DefaultAhcConfig;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class HttpMethodTest extends AbstractGrizzlyServerTester {
+    @Target({ElementType.METHOD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @HttpMethod("PATCH")
+    public @interface PATCH {
+    }
+
+    @Path("/test")
+    public static class HttpMethodResource {
+        @GET
+        public String get() {
+            return "GET";
+        }
+
+        @POST
+        public String post(String entity) {
+            return entity;
+        }
+
+        @PUT
+        public String put(String entity) {
+            return entity;
+        }
+
+        @DELETE
+        public String delete() {
+            return "DELETE";
+        }
+
+        @DELETE
+        @Path("withentity")
+        public String delete(String entity) {
+            return entity;
+        }
+
+        @POST
+        @Path("noproduce")
+        public void postNoProduce(String entity) {
+        }
+
+        @POST
+        @Path("noconsumeproduce")
+        public void postNoConsumeProduce() {
+        }
+
+        @PATCH
+        public String patch(String entity) {
+            return entity;
+        }
+    }
+
+    public HttpMethodTest(String testName) {
+        super(testName);
+    }
+
+    protected AhcHttpClient createClient() {
+        return AhcHttpClient.create();
+    }
+
+    protected AhcHttpClient createClient(AhcConfig cc) {
+        return AhcHttpClient.create(cc);
+    }
+
+    public void testHead() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        ClientResponse cr = r.head();
+        assertFalse(cr.hasEntity());
+    }
+
+    public void testOptions() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        ClientResponse cr = r.options(ClientResponse.class);
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testGet() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        assertEquals("GET", r.get(String.class));
+
+        ClientResponse cr = r.get(ClientResponse.class);
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPost() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        assertEquals("POST", r.post(String.class, "POST"));
+
+        ClientResponse cr = r.post(ClientResponse.class, "POST");
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPostChunked() {
+        ResourceConfig rc = new DefaultResourceConfig(HttpMethodResource.class);
+        rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
+                LoggingFilter.class.getName());
+        startServer(rc);
+
+        DefaultAhcConfig config = new DefaultAhcConfig();
+        config.getProperties().put(AhcConfig.PROPERTY_CHUNKED_ENCODING_SIZE, 1024);
+        AhcHttpClient c = createClient(config);
+
+        WebResource r = c.resource(getUri().path("test").build());        
+        assertEquals("POST", r.post(String.class, "POST"));
+
+        ClientResponse cr = r.post(ClientResponse.class, "POST");
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPostVoid() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        // This test will lock up if ClientResponse is not closed by WebResource.
+        // TODO need a better way to detect this.
+        for (int i = 0; i < 100; i++) {
+            r.post("POST");
+        }
+    }
+
+    public void testPostNoProduce() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        assertEquals(204, r.path("noproduce").post(ClientResponse.class, "POST").getStatus());
+
+        ClientResponse cr = r.path("noproduce").post(ClientResponse.class, "POST");
+        assertFalse(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPostNoConsumeProduce() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        assertEquals(204, r.path("noconsumeproduce").post(ClientResponse.class).getStatus());
+
+        ClientResponse cr = r.path("noconsumeproduce").post(ClientResponse.class, "POST");
+        assertFalse(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPut() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        assertEquals("PUT", r.put(String.class, "PUT"));
+
+        ClientResponse cr = r.put(ClientResponse.class, "PUT");
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testDelete() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        assertEquals("DELETE", r.delete(String.class));
+
+        ClientResponse cr = r.delete(ClientResponse.class);
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testDeleteWithEntity() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test/withentity").build());
+        r.addFilter(new com.sun.jersey.api.client.filter.LoggingFilter());
+        assertEquals("DELETE with entity", r.delete(String.class, "DELETE with entity"));
+
+        ClientResponse cr = r.delete(ClientResponse.class, "DELETE with entity");
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testPatch() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+        r.addFilter(new com.sun.jersey.api.client.filter.LoggingFilter());
+        assertEquals("PATCH", r.method("PATCH", String.class, "PATCH"));
+
+        ClientResponse cr = r.method("PATCH", ClientResponse.class, "PATCH");
+        assertTrue(cr.hasEntity());
+        cr.close();
+    }
+
+    public void testAll() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        assertEquals("GET", r.get(String.class));
+
+        assertEquals("POST", r.post(String.class, "POST"));
+
+        assertEquals(204, r.path("noproduce").post(ClientResponse.class, "POST").getStatus());
+
+        assertEquals(204, r.path("noconsumeproduce").post(ClientResponse.class).getStatus());
+
+        assertEquals("PUT", r.post(String.class, "PUT"));
+
+        assertEquals("DELETE", r.delete(String.class));
+    }
+
+
+    @Path("/test")
+    public static class ErrorResource {
+        @POST
+        public Response post(String entity) {
+            return Response.serverError().build();
+        }
+
+        @Path("entity")
+        @POST
+        public Response postWithEntity(String entity) {
+            return Response.serverError().entity("error").build();
+        }
+    }
+
+    public void testPostError() {
+        startServer(ErrorResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        // This test will lock up if ClientResponse is not closed by WebResource.
+        // TODO need a better way to detect this.
+        for (int i = 0; i < 100; i++) {
+            try {
+                r.post("POST");
+            } catch (UniformInterfaceException ex) {
+            }
+        }
+    }
+
+    public void testPostErrorWithEntity() {
+        startServer(ErrorResource.class);
+        WebResource r = createClient().resource(getUri().path("test/entity").build());
+
+        // This test will lock up if ClientResponse is not closed by WebResource.
+        // TODO need a better way to detect this.
+        for (int i = 0; i < 100; i++) {
+            try {
+                r.post("POST");
+            } catch (UniformInterfaceException ex) {
+                String s = ex.getResponse().getEntity(String.class);
+                assertEquals("error", s);
+            }
+        }
+    }
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/HttpMethodWithClientFilterTest.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.jersey.api.client.filter.LoggingFilter;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class HttpMethodWithClientFilterTest extends HttpMethodTest {
+    public HttpMethodWithClientFilterTest(String testName) {
+        super(testName);
+    }
+
+    @Override
+    protected AhcHttpClient createClient() {
+        AhcHttpClient ac = AhcHttpClient.create();
+        ac.addFilter(new LoggingFilter());
+        return ac;
+    }
+
+    @Override
+    protected AhcHttpClient createClient(AhcConfig cc) {
+        AhcHttpClient ac = AhcHttpClient.create(cc);
+        ac.addFilter(new LoggingFilter());
+        return ac;
+    }
+}

File src/test/java/org/sonatype/spice/jersey/client/ahc/tests/tests/NoEntityTest.java

+/*******************************************************************************
+ * Copyright (c) 2010-2011 Sonatype, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Apache License v2.0 which accompanies this distribution.
+ * The Eclipse Public License is available at
+ *   http://www.eclipse.org/legal/epl-v10.html
+ * The Apache License v2.0 is available at
+ *   http://www.apache.org/licenses/LICENSE-2.0.html
+ * You may elect to redistribute this code under either of these licenses.
+ *******************************************************************************/
+
+package org.sonatype.spice.jersey.client.ahc.tests.tests;
+
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import org.sonatype.spice.jersey.client.ahc.AhcHttpClient;
+import org.sonatype.spice.jersey.client.ahc.config.AhcConfig;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+/**
+ *
+ * @author Paul.Sandoz@Sun.Com
+ */
+public class NoEntityTest extends AbstractGrizzlyServerTester {
+    @Path("/test")
+    public static class HttpMethodResource {
+        @GET
+        public Response get() {
+            return Response.status(Status.CONFLICT).build();
+        }
+
+        @POST
+        public void post(String entity) {
+        }
+    }
+
+    public NoEntityTest(String testName) {
+        super(testName);
+    }
+
+    protected AhcHttpClient createClient() {
+        return AhcHttpClient.create();
+    }
+
+    protected AhcHttpClient createClient(AhcConfig cc) {
+        return AhcHttpClient.create(cc);
+    }
+
+    public void testGet() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        for (int i = 0; i < 5; i++) {
+            ClientResponse cr = r.get(ClientResponse.class);
+        }
+    }
+
+    public void testGetWithClose() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        for (int i = 0; i < 5; i++) {
+            ClientResponse cr = r.get(ClientResponse.class);
+            cr.close();
+        }
+    }
+
+    public void testPost() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        for (int i = 0; i < 5; i++) {
+            ClientResponse cr = r.post(ClientResponse.class);
+        }
+    }
+
+    public void testPostWithClose() {
+        startServer(HttpMethodResource.class);
+        WebResource r = createClient().resource(getUri().path("test").build());
+
+        for (int i = 0; i < 5; i++) {