hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject [08/10] httpcomponents-core git commit: Classic and asynchronous authentication filters
Date Wed, 09 Aug 2017 18:15:17 GMT
Classic and asynchronous authentication filters


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/commit/c43586f3
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/tree/c43586f3
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/diff/c43586f3

Branch: refs/heads/master
Commit: c43586f371d91e4193bc4e387c760279707d3f61
Parents: 8b1a84e
Author: Oleg Kalnichevski <olegk@apache.org>
Authored: Mon Aug 7 17:31:15 2017 +0200
Committer: Oleg Kalnichevski <olegk@apache.org>
Committed: Wed Aug 9 15:05:14 2017 +0200

----------------------------------------------------------------------
 .../classic/ClassicAuthenticationTest.java      | 256 +++++++++++++++
 .../testing/nio/Http1AuthenticationTest.java    | 308 +++++++++++++++++++
 .../hc/core5/http/impl/io/HttpService.java      |  11 +-
 .../support/AbstractHttpServerAuthFilter.java   | 105 +++++++
 .../support/AbstractAsyncServerAuthFilter.java  | 129 ++++++++
 5 files changed, 800 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/c43586f3/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java
----------------------------------------------------------------------
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java
b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java
new file mode 100644
index 0000000..93e4fe5
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicAuthenticationTest.java
@@ -0,0 +1,256 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.classic;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
+
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.config.SocketConfig;
+import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
+import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
+import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.StandardFilters;
+import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.io.support.AbstractHttpServerAuthFilter;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.util.Timeout;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassicAuthenticationTest {
+
+    @Parameterized.Parameters(name = "respond immediately on auth failure: {0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][]{
+                { Boolean.FALSE },
+                { Boolean.TRUE }
+        });
+    }
+
+    private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final boolean respondImmediately;
+    private HttpServer server;
+
+    public ClassicAuthenticationTest(final Boolean respondImmediately) {
+        this.respondImmediately = respondImmediately;
+    }
+
+    @Rule
+    public ExternalResource serverResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            log.debug("Starting up test server");
+            server = ServerBootstrap.bootstrap()
+                    .setSocketConfig(
+                            SocketConfig.custom()
+                                    .setSoTimeout(TIMEOUT)
+                                    .build())
+                    .register("*", new EchoHandler())
+                    .replaceFilter(StandardFilters.EXPECT_CONTINUE.name(), new AbstractHttpServerAuthFilter<String>(respondImmediately)
{
+
+                        @Override
+                        protected String parseChallengeResponse(
+                                final String challenge, final HttpContext context) throws
HttpException {
+                            return challenge;
+                        }
+
+                        @Override
+                        protected boolean authenticate(
+                                final String challengeResponse, final HttpContext context)
{
+                            return challengeResponse != null && challengeResponse.equals("let
me pass");
+                        }
+
+                        @Override
+                        protected String generateChallenge(final String challengeResponse,
final HttpContext context) {
+                            return "who goes there?";
+                        }
+
+                        @Override
+                        protected HttpEntity generateResponseContent(final HttpResponse unauthorized)
{
+                            return new StringEntity("You shall not pass!!!");
+                        }
+                    })
+                    .setConnectionFactory(LoggingBHttpServerConnectionFactory.INSTANCE)
+                    .setExceptionListener(LoggingExceptionListener.INSTANCE)
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test server");
+            if (server != null) {
+                try {
+                    server.shutdown(ShutdownType.IMMEDIATE);
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    private HttpRequester requester;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            log.debug("Starting up test client");
+            requester = RequesterBootstrap.bootstrap()
+                    .setSocketConfig(SocketConfig.custom()
+                            .setSoTimeout(TIMEOUT)
+                            .build())
+                    .setMaxTotal(2)
+                    .setDefaultMaxPerRoute(2)
+                    .setConnectionFactory(LoggingBHttpClientConnectionFactory.INSTANCE)
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
+                    .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test client");
+            if (requester != null) {
+                try {
+                    requester.shutdown(ShutdownType.GRACEFUL);
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    @Test
+    public void testGetRequestAuthentication() throws Exception {
+        server.start();
+        final HttpHost target = new HttpHost("localhost", server.getLocalPort());
+        final HttpCoreContext context = HttpCoreContext.create();
+        final ClassicHttpRequest request1 = new BasicClassicHttpRequest("GET", "/stuff");
+        try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT,
context)) {
+            Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED));
+            final String body1 = EntityUtils.toString(response1.getEntity());
+            Assert.assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!"));
+        }
+        final ClassicHttpRequest request2 = new BasicClassicHttpRequest("GET", "/stuff");
+        request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass");
+        try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT,
context)) {
+            Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+            final String body1 = EntityUtils.toString(response2.getEntity());
+            Assert.assertThat(body1, CoreMatchers.equalTo(""));
+        }
+    }
+
+    @Test
+    public void testPostRequestAuthentication() throws Exception {
+        server.start();
+        final HttpHost target = new HttpHost("localhost", server.getLocalPort());
+        final HttpCoreContext context = HttpCoreContext.create();
+        final Random rnd = new Random();
+        final byte[] stuff = new byte[10240];
+        for (int i = 0; i < stuff.length; i++) {
+            stuff[i] = (byte)('a' + rnd.nextInt(10));
+        }
+        final ClassicHttpRequest request1 = new BasicClassicHttpRequest("POST", "/stuff");
+        request1.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN));
+        try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT,
context)) {
+            Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED));
+            final String body1 = EntityUtils.toString(response1.getEntity());
+            Assert.assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!"));
+        }
+        final ClassicHttpRequest request2 = new BasicClassicHttpRequest("POST", "/stuff");
+        request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass");
+        request2.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN));
+        try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT,
context)) {
+            Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+            final String body1 = EntityUtils.toString(response2.getEntity());
+            Assert.assertThat(body1, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII)));
+        }
+    }
+
+    @Test
+    public void testPostRequestAuthenticationNoExpectContinue() throws Exception {
+        server.start();
+        final HttpHost target = new HttpHost("localhost", server.getLocalPort());
+        final HttpCoreContext context = HttpCoreContext.create();
+        final Random rnd = new Random();
+        final byte[] stuff = new byte[10240];
+        for (int i = 0; i < stuff.length; i++) {
+            stuff[i] = (byte)('a' + rnd.nextInt(10));
+        }
+        final ClassicHttpRequest request1 = new BasicClassicHttpRequest("POST", "/stuff");
+        request1.setVersion(HttpVersion.HTTP_1_0);
+        request1.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN));
+        try (final ClassicHttpResponse response1 = requester.execute(target, request1, TIMEOUT,
context)) {
+            Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED));
+            final String body1 = EntityUtils.toString(response1.getEntity());
+            Assert.assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!"));
+        }
+        final ClassicHttpRequest request2 = new BasicClassicHttpRequest("POST", "/stuff");
+        request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass");
+        request2.setVersion(HttpVersion.HTTP_1_0);
+        request2.setEntity(new ByteArrayEntity(stuff, ContentType.TEXT_PLAIN));
+        try (final ClassicHttpResponse response2 = requester.execute(target, request2, TIMEOUT,
context)) {
+            Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+            final String body1 = EntityUtils.toString(response2.getEntity());
+            Assert.assertThat(body1, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII)));
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/c43586f3/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java
----------------------------------------------------------------------
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java
b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java
new file mode 100644
index 0000000..aa5a696
--- /dev/null
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1AuthenticationTest.java
@@ -0,0 +1,308 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.testing.nio;
+
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
+import java.util.concurrent.Future;
+
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Message;
+import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
+import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.impl.bootstrap.StandardFilters;
+import org.apache.hc.core5.http.message.BasicHttpRequest;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.nio.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.BasicResponseConsumer;
+import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
+import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
+import org.apache.hc.core5.http.nio.support.AbstractAsyncServerAuthFilter;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
+import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
+import org.apache.hc.core5.testing.classic.LoggingHttp1StreamListener;
+import org.apache.hc.core5.util.Timeout;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Http1AuthenticationTest {
+
+    @Parameterized.Parameters(name = "respond immediately on auth failure: {0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][]{
+                { Boolean.FALSE },
+                { Boolean.TRUE }
+        });
+    }
+
+    private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final boolean respondImmediately;
+    private HttpAsyncServer server;
+
+    public Http1AuthenticationTest(final Boolean respondImmediately) {
+        this.respondImmediately = respondImmediately;
+    }
+
+    @Rule
+    public ExternalResource serverResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            log.debug("Starting up test server");
+            server = AsyncServerBootstrap.bootstrap()
+                    .setIOReactorConfig(
+                            IOReactorConfig.custom()
+                                    .setSoTimeout(TIMEOUT)
+                                    .build())
+                    .register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+                        @Override
+                        public AsyncServerExchangeHandler get() {
+                            return new EchoHandler(2048);
+                        }
+
+                    })
+                    .replaceFilter(StandardFilters.EXPECT_CONTINUE.name(), new AbstractAsyncServerAuthFilter<String>(respondImmediately)
{
+
+                        @Override
+                        protected String parseChallengeResponse(
+                                final String challenge, final HttpContext context) throws
HttpException {
+                            return challenge;
+                        }
+
+                        @Override
+                        protected boolean authenticate(
+                                final String challengeResponse, final HttpContext context)
{
+                            return challengeResponse != null && challengeResponse.equals("let
me pass");
+                        }
+
+                        @Override
+                        protected String generateChallenge(final String challengeResponse,
final HttpContext context) {
+                            return "who goes there?";
+                        }
+
+                        @Override
+                        protected AsyncEntityProducer generateResponseContent(final HttpResponse
unauthorized) {
+                            return new BasicAsyncEntityProducer("You shall not pass!!!");
+                        }
+                    })
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
+                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test server");
+            if (server != null) {
+                try {
+                    server.shutdown(ShutdownType.IMMEDIATE);
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    private HttpAsyncRequester requester;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            log.debug("Starting up test client");
+            requester = AsyncRequesterBootstrap.bootstrap()
+                    .setIOReactorConfig(IOReactorConfig.custom()
+                            .setSoTimeout(TIMEOUT)
+                            .build())
+                    .setMaxTotal(2)
+                    .setDefaultMaxPerRoute(2)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
+                    .setStreamListener(LoggingHttp1StreamListener.INSTANCE)
+                    .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
+                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .create();
+        }
+
+        @Override
+        protected void after() {
+            log.debug("Shutting down test client");
+            if (requester != null) {
+                try {
+                    requester.shutdown(ShutdownType.GRACEFUL);
+                } catch (final Exception ignore) {
+                }
+            }
+        }
+
+    };
+
+    @Test
+    public void testGetRequestAuthentication() throws Exception {
+        server.start();
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final ListenerEndpoint listener = future.get();
+        final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
+        requester.start();
+
+        final HttpHost target = new HttpHost("localhost", address.getPort());
+
+        final HttpRequest request1 = new BasicHttpRequest("GET", target, "/stuff");
+        final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
+                new BasicRequestProducer(request1, null),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT,
null);
+        final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(),
TIMEOUT.getTimeUnit());
+        Assert.assertThat(message1, CoreMatchers.notNullValue());
+        final HttpResponse response1 = message1.getHead();
+        Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED));
+        final String body1 = message1.getBody();
+        Assert.assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!"));
+
+        final HttpRequest request2 = new BasicHttpRequest("GET", target, "/stuff");
+        request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass");
+        final Future<Message<HttpResponse, String>> resultFuture2 = requester.execute(
+                new BasicRequestProducer(request2, null),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT,
null);
+        final Message<HttpResponse, String> message2 = resultFuture2.get(TIMEOUT.getDuration(),
TIMEOUT.getTimeUnit());
+        Assert.assertThat(message2, CoreMatchers.notNullValue());
+        final HttpResponse response2 = message2.getHead();
+        Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+        final String body2 = message2.getBody();
+        Assert.assertThat(body2, CoreMatchers.equalTo(""));
+    }
+
+    @Test
+    public void testPostRequestAuthentication() throws Exception {
+        server.start();
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final ListenerEndpoint listener = future.get();
+        final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
+        requester.start();
+
+        final HttpHost target = new HttpHost("localhost", address.getPort());
+        final Random rnd = new Random();
+        final byte[] stuff = new byte[10240];
+        for (int i = 0; i < stuff.length; i++) {
+            stuff[i] = (byte)('a' + rnd.nextInt(10));
+        }
+        final HttpRequest request1 = new BasicHttpRequest("POST", target, "/stuff");
+        final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
+                new BasicRequestProducer(request1, new BasicAsyncEntityProducer(stuff, ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT,
null);
+        final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(),
TIMEOUT.getTimeUnit());
+        Assert.assertThat(message1, CoreMatchers.notNullValue());
+        final HttpResponse response1 = message1.getHead();
+        Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED));
+        final String body1 = message1.getBody();
+        Assert.assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!"));
+
+        final HttpRequest request2 = new BasicHttpRequest("POST", target, "/stuff");
+        request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass");
+        final Future<Message<HttpResponse, String>> resultFuture2 = requester.execute(
+                new BasicRequestProducer(request2, new BasicAsyncEntityProducer(stuff, ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT,
null);
+        final Message<HttpResponse, String> message2 = resultFuture2.get(TIMEOUT.getDuration(),
TIMEOUT.getTimeUnit());
+        Assert.assertThat(message2, CoreMatchers.notNullValue());
+        final HttpResponse response2 = message2.getHead();
+        Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+        final String body2 = message2.getBody();
+        Assert.assertThat(body2, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII)));
+    }
+
+    @Test
+    public void testPostRequestAuthenticationNoExpectContinue() throws Exception {
+        server.start();
+        final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
+        final ListenerEndpoint listener = future.get();
+        final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
+        requester.start();
+
+        final HttpHost target = new HttpHost("localhost", address.getPort());
+        final Random rnd = new Random();
+        final byte[] stuff = new byte[10240];
+        for (int i = 0; i < stuff.length; i++) {
+            stuff[i] = (byte)('a' + rnd.nextInt(10));
+        }
+
+        final HttpRequest request1 = new BasicHttpRequest("POST", target, "/stuff");
+        request1.setVersion(HttpVersion.HTTP_1_0);
+        final Future<Message<HttpResponse, String>> resultFuture1 = requester.execute(
+                new BasicRequestProducer(request1, new BasicAsyncEntityProducer(stuff, ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT,
null);
+        final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(),
TIMEOUT.getTimeUnit());
+        Assert.assertThat(message1, CoreMatchers.notNullValue());
+        final HttpResponse response1 = message1.getHead();
+        Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_UNAUTHORIZED));
+        final String body1 = message1.getBody();
+        Assert.assertThat(body1, CoreMatchers.equalTo("You shall not pass!!!"));
+
+        final HttpRequest request2 = new BasicHttpRequest("POST", target, "/stuff");
+        request2.setVersion(HttpVersion.HTTP_1_0);
+        request2.setHeader(HttpHeaders.AUTHORIZATION, "let me pass");
+        final Future<Message<HttpResponse, String>> resultFuture2 = requester.execute(
+                new BasicRequestProducer(request2, new BasicAsyncEntityProducer(stuff, ContentType.TEXT_PLAIN)),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), TIMEOUT,
null);
+        final Message<HttpResponse, String> message2 = resultFuture2.get(TIMEOUT.getDuration(),
TIMEOUT.getTimeUnit());
+        Assert.assertThat(message2, CoreMatchers.notNullValue());
+        final HttpResponse response2 = message2.getHead();
+        Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
+        final String body2 = message2.getBody();
+        Assert.assertThat(body2, CoreMatchers.equalTo(new String(stuff, StandardCharsets.US_ASCII)));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/c43586f3/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
index 3d799b3..fe2715d 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
@@ -28,7 +28,6 @@
 package org.apache.hc.core5.http.impl.io;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.hc.core5.annotation.Contract;
@@ -38,7 +37,6 @@ import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.ConnectionReuseStrategy;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HeaderElements;
-import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpRequestMapper;
@@ -54,6 +52,7 @@ import org.apache.hc.core5.http.impl.Http1StreamListener;
 import org.apache.hc.core5.http.io.HttpRequestHandler;
 import org.apache.hc.core5.http.io.HttpServerConnection;
 import org.apache.hc.core5.http.io.HttpServerRequestHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator;
 import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler;
@@ -223,13 +222,7 @@ public class HttpService {
                             conn.sendResponseEntity(response);
                         }
                         // Make sure the request content is fully consumed
-                        final HttpEntity entity = request.getEntity();
-                        if (entity != null && entity.isStreaming()) {
-                            final InputStream instream = entity.getContent();
-                            if (instream != null) {
-                                instream.close();
-                            }
-                        }
+                        EntityUtils.consume(request.getEntity());
                         final boolean keepAlive = connReuseStrategy.keepAlive(request, response,
context);
                         if (streamListener != null) {
                             streamListener.onExchangeComplete(conn, keepAlive);

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/c43586f3/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java
b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java
new file mode 100644
index 0000000..1024f94
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/AbstractHttpServerAuthFilter.java
@@ -0,0 +1,105 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.http.io.support;
+
+import java.io.IOException;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.HttpFilterChain;
+import org.apache.hc.core5.http.io.HttpFilterHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public abstract class AbstractHttpServerAuthFilter<T> implements HttpFilterHandler
{
+
+    private final boolean respondImmediately;
+
+    protected AbstractHttpServerAuthFilter(final boolean respondImmediately) {
+        this.respondImmediately = respondImmediately;
+    }
+
+    protected abstract T parseChallengeResponse(String challenge, HttpContext context) throws
HttpException;
+
+    protected abstract boolean authenticate(T challengeResponse, HttpContext context);
+
+    protected abstract String generateChallenge(T challengeResponse, HttpContext context);
+
+    protected HttpEntity generateResponseContent(final HttpResponse unauthorized) {
+        return new StringEntity("Unauthorized");
+    }
+
+    @Override
+    public final void handle(
+            final ClassicHttpRequest request,
+            final HttpFilterChain.ResponseTrigger responseTrigger,
+            final HttpContext context,
+            final HttpFilterChain chain) throws HttpException, IOException {
+        final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION);
+        final T challengeResponse = h != null ? parseChallengeResponse(h.getValue(), context)
: null;
+        final boolean authenticated = authenticate(challengeResponse, context);
+        final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
+        final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
+
+        if (authenticated) {
+            if (expectContinue) {
+                responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE));
+            }
+            chain.proceed(request, responseTrigger, context);
+        } else {
+            final ClassicHttpResponse unauthorized = new BasicClassicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
+            unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, generateChallenge(challengeResponse,
context));
+            final HttpEntity responseContent = generateResponseContent(unauthorized);
+            unauthorized.setEntity(responseContent);
+            if (respondImmediately || expectContinue || request.getEntity() == null) {
+                // Respond immediately
+                responseTrigger.submitResponse(unauthorized);
+                // Consume request body later
+                EntityUtils.consume(request.getEntity());
+            } else {
+                // Consume request body first
+                EntityUtils.consume(request.getEntity());
+                // Respond later
+                responseTrigger.submitResponse(unauthorized);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/c43586f3/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java
b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java
new file mode 100644
index 0000000..cb2fb26
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AbstractAsyncServerAuthFilter.java
@@ -0,0 +1,129 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.http.nio.support;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.AsyncFilterChain;
+import org.apache.hc.core5.http.nio.AsyncFilterHandler;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+/**
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public abstract class AbstractAsyncServerAuthFilter<T> implements AsyncFilterHandler
{
+
+    private final boolean respondImmediately;
+
+    protected AbstractAsyncServerAuthFilter(final boolean respondImmediately) {
+        this.respondImmediately = respondImmediately;
+    }
+
+    protected abstract T parseChallengeResponse(String challenge, HttpContext context) throws
HttpException;
+
+    protected abstract boolean authenticate(T challengeResponse, HttpContext context);
+
+    protected abstract String generateChallenge(T challengeResponse, HttpContext context);
+
+    protected AsyncEntityProducer generateResponseContent(final HttpResponse unauthorized)
{
+        return new BasicAsyncEntityProducer("Unauthorized");
+    }
+
+    @Override
+    public AsyncDataConsumer handle(
+            final HttpRequest request,
+            final EntityDetails entityDetails,
+            final HttpContext context,
+            final AsyncFilterChain.ResponseTrigger responseTrigger,
+            final AsyncFilterChain chain) throws HttpException, IOException {
+        final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION);
+        final T challengeResponse = h != null ? parseChallengeResponse(h.getValue(), context)
: null;
+        final boolean authenticated = authenticate(challengeResponse, context);
+        final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
+        final boolean expectContinue = expect != null && "100-continue".equalsIgnoreCase(expect.getValue());
+
+        if (authenticated) {
+            if (expectContinue) {
+                responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE));
+            }
+            return chain.proceed(request, entityDetails, context, responseTrigger);
+        } else {
+            final HttpResponse unauthorized = new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
+            unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, generateChallenge(challengeResponse,
context));
+            final AsyncEntityProducer responseContentProducer = generateResponseContent(unauthorized);
+            if (respondImmediately || expectContinue || entityDetails == null) {
+                responseTrigger.submitResponse(unauthorized, responseContentProducer);
+                return null;
+            } else {
+                return new AsyncDataConsumer() {
+
+                    @Override
+                    public void updateCapacity(final CapacityChannel capacityChannel) throws
IOException {
+                        capacityChannel.update(Integer.MAX_VALUE);
+                    }
+
+                    @Override
+                    public int consume(final ByteBuffer src) throws IOException {
+                        return Integer.MAX_VALUE;
+                    }
+
+                    @Override
+                    public void streamEnd(final List<? extends Header> trailers) throws
HttpException, IOException {
+                        responseTrigger.submitResponse(unauthorized, responseContentProducer);
+                    }
+
+                    @Override
+                    public void releaseResources() {
+                        if (responseContentProducer != null) {
+                            responseContentProducer.releaseResources();
+                        }
+                    }
+
+                };
+            }
+        }
+    }
+
+}


Mime
View raw message