Skip to content
Commits on Source (7)
Release 4.4.12
-------------------
This is a maintenance release that corrects a number of defects discovered since release 4.4.11.
Changelog
-------------------
* HTTPASYNC-152: Minimize possibility of a race condition when I/O reactor session request gets
cancelled or times out immediately after its creation.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Execute Socket[Channel]#connect under doPrivileged.
Contributed by Simon Willnauer <simonw at apache.org>
* Bug fix: Ensure consistency of internal buffers in case of I/O or SSL exception (back-ported
from master).
Contributed by Oleg Kalnichevski <olegk at apache.org>
* Rethrow RuntimeExceptions thrown by SSLSetupHandler#initalize as SSLException
Contributed by Oleg Kalnichevski <olegk at apache.org>
* HTTPCORE-578: Incorrect serialization of HeaderGroup.
Contributed by Gary Gregory <ggregory at apache.org>
* HTTPCORE-370, HTTPCORE-574: Discard non-blocking session request immediately upon timeout.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* HTTPCLIENT-1978: Filter characters before byte conversion.
Contributed by Ryan Schmitt <rschmitt at apache.org>
* HTTPCORE-573 FileContentDecoder doesn't always enforce the maximum number of bytes to transfer.
Contributed by Julien Coloos <julien.coloos at gmail.com>
* HTTPCORE-567: Fixed race condition that may cause a connection leak when the connection lease
request is cancelled from another thread.
Contributed by Oleg Kalnichevski <olegk at apache.org>
Release 4.4.11
-------------------
......
httpcomponents-core (4.4.12-1) unstable; urgency=medium
* New upstream release
* Removed the unused ${java:Depends} variable in debian/control
* Standards-Version updated to 4.4.1
* Use a secure URI in debian/watch
-- Emmanuel Bourg <ebourg@apache.org> Sat, 05 Oct 2019 10:05:40 +0200
httpcomponents-core (4.4.11-1) unstable; urgency=medium
* New upstream release
......
......@@ -10,14 +10,14 @@ Build-Depends:
libmaven-antrun-plugin-java,
libmaven-bundle-plugin-java,
maven-debian-helper
Standards-Version: 4.3.0
Standards-Version: 4.4.1
Vcs-Git: https://salsa.debian.org/java-team/httpcomponents-core.git
Vcs-Browser: https://salsa.debian.org/java-team/httpcomponents-core
Homepage: http://hc.apache.org/httpcomponents-core-ga/index.html
Package: libhttpcore-java
Architecture: all
Depends: ${java:Depends}, ${misc:Depends}
Depends: ${misc:Depends}
Provides: libhttpcore-nio-java
Description: set of low level HTTP transport components for Java
HttpCore is a set of low level HTTP transport components that can be used
......
version=3
http://www.apache.org/dist/httpcomponents/httpcore/source/httpcomponents-core-(\d+[^a-zA-Z]*)-src\.tar\.gz
https://www.apache.org/dist/httpcomponents/httpcore/source/httpcomponents-core-(\d+[^a-zA-Z]*)-src\.tar\.gz
......@@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-core</artifactId>
<version>4.4.11</version>
<version>4.4.12</version>
</parent>
<artifactId>httpcore-ab</artifactId>
<name>Apache HttpCore Benchmarking Tool</name>
......@@ -88,7 +88,6 @@
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${hc.project-info.version}</version>
<inherited>false</inherited>
<reportSets>
<reportSet>
......
......@@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-core</artifactId>
<version>4.4.11</version>
<version>4.4.12</version>
</parent>
<artifactId>httpcore-nio</artifactId>
<name>Apache HttpCore NIO</name>
......@@ -120,7 +120,6 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${hc.javadoc.version}</version>
<configuration>
<!-- reduce console output. Can override with -Dquiet=false -->
<quiet>true</quiet>
......@@ -141,7 +140,6 @@
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${hc.project-info.version}</version>
<inherited>false</inherited>
<reportSets>
<reportSet>
......@@ -156,12 +154,10 @@
<plugin>
<artifactId>maven-jxr-plugin</artifactId>
<version>${hc.jxr.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>${hc.surefire-report.version}</version>
</plugin>
</plugins>
......
......@@ -93,8 +93,9 @@ public class IdentityDecoder extends AbstractContentDecoder
long bytesRead;
if (this.buffer.hasData()) {
final int maxLen = this.buffer.length();
dst.position(position);
bytesRead = this.buffer.read(dst);
bytesRead = this.buffer.read(dst, count < maxLen ? (int)count : maxLen);
} else {
if (this.channel.isOpen()) {
if (position > dst.size()) {
......
......@@ -117,7 +117,7 @@ public class LengthDelimitedDecoder extends AbstractContentDecoder
if (this.buffer.hasData()) {
final int maxLen = Math.min(chunk, this.buffer.length());
dst.position(position);
bytesRead = this.buffer.read(dst, maxLen);
bytesRead = this.buffer.read(dst, count < maxLen ? (int)count : maxLen);
} else {
if (this.channel.isOpen()) {
if (position > dst.size()) {
......
......@@ -419,14 +419,23 @@ public abstract class AbstractIOReactor implements IOReactor {
}
try {
this.sessions.add(session);
key.attach(session);
final SessionRequestImpl sessionRequest = entry.getSessionRequest();
if (sessionRequest != null) {
if (!sessionRequest.isTerminated()) {
sessionRequest.completed(session);
}
key.attach(session);
if (!sessionRequest.isTerminated()) {
sessionCreated(key, session);
}
if (sessionRequest.isTerminated()) {
throw new CancelledKeyException();
}
} else {
sessionCreated(key, session);
}
} catch (final CancelledKeyException ex) {
queueClosedSession(session);
session.close();
key.attach(null);
}
}
......@@ -489,7 +498,12 @@ public abstract class AbstractIOReactor implements IOReactor {
final int timeout = session.getSocketTimeout();
if (timeout > 0) {
if (session.getLastAccessTime() + timeout < now) {
try {
sessionTimedOut(session);
} catch (final CancelledKeyException ex) {
session.close();
key.attach(null);
}
}
}
}
......
......@@ -169,8 +169,7 @@ public class BaseIOReactor extends AbstractIOReactor {
this.bufferingSessions.add(session);
}
} catch (final CancelledKeyException ex) {
queueClosedSession(session);
key.attach(null);
throw ex;
} catch (final RuntimeException ex) {
handleRuntimeException(ex);
}
......@@ -187,8 +186,7 @@ public class BaseIOReactor extends AbstractIOReactor {
try {
this.eventDispatch.outputReady(session);
} catch (final CancelledKeyException ex) {
queueClosedSession(session);
key.attach(null);
throw ex;
} catch (final RuntimeException ex) {
handleRuntimeException(ex);
}
......@@ -230,7 +228,7 @@ public class BaseIOReactor extends AbstractIOReactor {
}
} catch (final CancelledKeyException ex) {
it.remove();
queueClosedSession(session);
session.close();
} catch (final RuntimeException ex) {
handleRuntimeException(ex);
}
......@@ -247,7 +245,7 @@ public class BaseIOReactor extends AbstractIOReactor {
try {
this.eventDispatch.connected(session);
} catch (final CancelledKeyException ex) {
queueClosedSession(session);
throw ex;
} catch (final RuntimeException ex) {
handleRuntimeException(ex);
}
......@@ -262,7 +260,7 @@ public class BaseIOReactor extends AbstractIOReactor {
try {
this.eventDispatch.timeout(session);
} catch (final CancelledKeyException ex) {
queueClosedSession(session);
throw ex;
} catch (final RuntimeException ex) {
handleRuntimeException(ex);
}
......
......@@ -35,6 +35,9 @@ import java.net.UnknownHostException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
......@@ -207,7 +210,11 @@ public class DefaultConnectingIOReactor extends AbstractMultiworkerIOReactor
final int timeout = sessionRequest.getConnectTimeout();
if (timeout > 0) {
if (handle.getRequestTime() + timeout < now) {
try {
sessionRequest.timeout();
} finally {
key.attach(null);
}
}
}
}
......@@ -270,7 +277,25 @@ public class DefaultConnectingIOReactor extends AbstractMultiworkerIOReactor
sock.setReuseAddress(this.config.isSoReuseAddress());
sock.bind(request.getLocalAddress());
}
final boolean connected = socketChannel.connect(request.getRemoteAddress());
final SocketAddress targetAddress = request.getRemoteAddress();
// Run this under a doPrivileged to support lib users that run under a SecurityManager this allows granting connect
// permissions only to this library
final boolean connected;
try {
connected = AccessController.doPrivileged(
new PrivilegedExceptionAction<Boolean>() {
@Override
public Boolean run() throws IOException {
return socketChannel.connect(targetAddress);
};
});
} catch (final PrivilegedActionException e) {
Asserts.check(e.getCause() instanceof IOException,
"method contract violation only checked exceptions are wrapped: " + e.getCause());
// only checked exceptions are wrapped - error and RTExceptions are rethrown by doPrivileged
throw (IOException) e.getCause();
}
if (connected) {
final ChannelEntry entry = new ChannelEntry(socketChannel, request);
addChannel(entry);
......
......@@ -31,9 +31,10 @@ import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.SessionRequest;
import org.apache.http.nio.reactor.SessionRequestCallback;
......@@ -47,14 +48,15 @@ import org.apache.http.util.Args;
@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
public class SessionRequestImpl implements SessionRequest {
private volatile boolean completed;
private volatile SelectionKey key;
private final SocketAddress remoteAddress;
private final SocketAddress localAddress;
private final Object attachment;
private final SessionRequestCallback callback;
private final AtomicBoolean completed;
private volatile SelectionKey key;
private volatile boolean terminated;
private volatile int connectTimeout;
private volatile IOSession session = null;
private volatile IOException exception = null;
......@@ -70,7 +72,7 @@ public class SessionRequestImpl implements SessionRequest {
this.localAddress = localAddress;
this.attachment = attachment;
this.callback = callback;
this.connectTimeout = 0;
this.completed = new AtomicBoolean(false);
}
@Override
......@@ -90,7 +92,11 @@ public class SessionRequestImpl implements SessionRequest {
@Override
public boolean isCompleted() {
return this.completed;
return this.completed.get();
}
boolean isTerminated() {
return this.terminated;
}
protected void setKey(final SelectionKey key) {
......@@ -99,11 +105,11 @@ public class SessionRequestImpl implements SessionRequest {
@Override
public void waitFor() throws InterruptedException {
if (this.completed) {
if (this.completed.get()) {
return;
}
synchronized (this) {
while (!this.completed) {
while (!this.completed.get()) {
wait();
}
}
......@@ -125,10 +131,7 @@ public class SessionRequestImpl implements SessionRequest {
public void completed(final IOSession session) {
Args.notNull(session, "Session");
if (this.completed) {
return;
}
this.completed = true;
if (this.completed.compareAndSet(false, true)) {
synchronized (this) {
this.session = session;
if (this.callback != null) {
......@@ -137,15 +140,14 @@ public class SessionRequestImpl implements SessionRequest {
notifyAll();
}
}
}
public void failed(final IOException exception) {
if (exception == null) {
return;
}
if (this.completed) {
return;
}
this.completed = true;
if (this.completed.compareAndSet(false, true)) {
this.terminated = true;
final SelectionKey key = this.key;
if (key != null) {
key.cancel();
......@@ -162,12 +164,11 @@ public class SessionRequestImpl implements SessionRequest {
notifyAll();
}
}
}
public void timeout() {
if (this.completed) {
return;
}
this.completed = true;
if (this.completed.compareAndSet(false, true)) {
this.terminated = true;
final SelectionKey key = this.key;
if (key != null) {
key.cancel();
......@@ -184,6 +185,7 @@ public class SessionRequestImpl implements SessionRequest {
}
}
}
}
@Override
public int getConnectTimeout() {
......@@ -203,10 +205,8 @@ public class SessionRequestImpl implements SessionRequest {
@Override
public void cancel() {
if (this.completed) {
return;
}
this.completed = true;
if (this.completed.compareAndSet(false, true)) {
this.terminated = true;
final SelectionKey key = this.key;
if (key != null) {
key.cancel();
......@@ -224,5 +224,6 @@ public class SessionRequestImpl implements SessionRequest {
notifyAll();
}
}
}
}
......@@ -229,7 +229,11 @@ public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAcces
break;
}
if (this.handler != null) {
try {
this.handler.initalize(this.sslEngine);
} catch (final RuntimeException ex) {
throw convert(ex);
}
}
this.initialized = true;
this.sslEngine.beginHandshake();
......@@ -313,9 +317,11 @@ public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAcces
// Perform operations
inEncryptedBuf.flip();
try {
result = doUnwrap(inEncryptedBuf, inPlainBuf);
} finally {
inEncryptedBuf.compact();
}
try {
if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
......@@ -426,10 +432,14 @@ public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAcces
// Acquire buffer
final ByteBuffer outEncryptedBuf = this.outEncrypted.acquire();
final int bytesWritten;
// Perform operation
outEncryptedBuf.flip();
final int bytesWritten = this.session.channel().write(outEncryptedBuf);
try {
bytesWritten = this.session.channel().write(outEncryptedBuf);
} finally {
outEncryptedBuf.compact();
}
// Release if empty
if (outEncryptedBuf.position() == 0) {
......@@ -466,10 +476,14 @@ public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAcces
final ByteBuffer inEncryptedBuf = this.inEncrypted.acquire();
final ByteBuffer inPlainBuf = this.inPlain.acquire();
final SSLEngineResult result;
// Perform operations
inEncryptedBuf.flip();
final SSLEngineResult result = doUnwrap(inEncryptedBuf, inPlainBuf);
try {
result = doUnwrap(inEncryptedBuf, inPlainBuf);
} finally {
inEncryptedBuf.compact();
}
try {
if (!inEncryptedBuf.hasRemaining() && result.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) {
......
......@@ -256,6 +256,74 @@ public class TestIdentityDecoder {
CodecTestUtils.readFromFile(this.tmpfile));
}
@Test
public void testDecodingFileWithLimit() throws Exception {
final ReadableByteChannel channel = new ReadableByteChannelMock(
new String[] {"stuff; more stuff; ", "a lot more stuff!"}, Consts.ASCII);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
final IdentityDecoder decoder = new IdentityDecoder(
channel, inbuf, metrics);
final int i = inbuf.fill(channel);
Assert.assertEquals(19, i);
createTempFile();
final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
try {
final FileChannel fchannel = testfile.getChannel();
long pos = 0;
// transferred from buffer
long bytesRead = decoder.transfer(fchannel, pos, 1);
Assert.assertEquals(1, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(0, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 2);
Assert.assertEquals(2, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(0, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 17);
Assert.assertEquals(16, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(0, metrics.getBytesTransferred());
pos += bytesRead;
// transferred from channel
bytesRead = decoder.transfer(fchannel, pos, 1);
Assert.assertEquals(1, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(1, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 2);
Assert.assertEquals(2, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(3, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 15);
Assert.assertEquals(14, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(17, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 1);
Assert.assertEquals(-1, bytesRead);
Assert.assertTrue(decoder.isCompleted());
Assert.assertEquals(17, metrics.getBytesTransferred());
} finally {
testfile.close();
}
Assert.assertEquals("stuff; more stuff; a lot more stuff!",
CodecTestUtils.readFromFile(this.tmpfile));
}
@Test
public void testWriteBeyondFileSize() throws Exception {
final ReadableByteChannel channel = new ReadableByteChannelMock(
......
......@@ -364,6 +364,74 @@ public class TestLengthDelimitedDecoder {
CodecTestUtils.readFromFile(this.tmpfile));
}
@Test
public void testDecodingFileWithLimit() throws Exception {
final ReadableByteChannel channel = new ReadableByteChannelMock(
new String[] {"stuff; more stuff; ", "a lot more stuff!!!"}, Consts.ASCII);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 256, Consts.ASCII);
final HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
final LengthDelimitedDecoder decoder = new LengthDelimitedDecoder(
channel, inbuf, metrics, 36);
final int i = inbuf.fill(channel);
Assert.assertEquals(19, i);
createTempFile();
final RandomAccessFile testfile = new RandomAccessFile(this.tmpfile, "rw");
try {
final FileChannel fchannel = testfile.getChannel();
long pos = 0;
// transferred from buffer
long bytesRead = decoder.transfer(fchannel, pos, 1);
Assert.assertEquals(1, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(0, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 2);
Assert.assertEquals(2, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(0, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 17);
Assert.assertEquals(16, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(0, metrics.getBytesTransferred());
pos += bytesRead;
// transferred from channel
bytesRead = decoder.transfer(fchannel, pos, 1);
Assert.assertEquals(1, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(1, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 2);
Assert.assertEquals(2, bytesRead);
Assert.assertFalse(decoder.isCompleted());
Assert.assertEquals(3, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 15);
Assert.assertEquals(14, bytesRead);
Assert.assertTrue(decoder.isCompleted());
Assert.assertEquals(17, metrics.getBytesTransferred());
pos += bytesRead;
bytesRead = decoder.transfer(fchannel, pos, 1);
Assert.assertEquals(-1, bytesRead);
Assert.assertTrue(decoder.isCompleted());
Assert.assertEquals(17, metrics.getBytesTransferred());
} finally {
testfile.close();
}
Assert.assertEquals("stuff; more stuff; a lot more stuff!",
CodecTestUtils.readFromFile(this.tmpfile));
}
@Test
public void testWriteBeyondFileSize() throws Exception {
final ReadableByteChannel channel = new ReadableByteChannelMock(
......
......@@ -31,18 +31,22 @@ import java.io.IOException;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.impl.nio.pool.BasicNIOConnFactory;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.nio.NHttpConnection;
......@@ -58,30 +62,285 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.ssl.SSLContextBuilder;
import org.junit.After;
import org.apache.http.ssl.SSLContexts;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
public class TestCustomSSL {
public class TestTLSIntegration {
private final static long RESULT_TIMEOUT_SEC = 30;
protected HttpServerNio server;
protected HttpClientNio client;
private HttpServerNio server;
@After
public void shutDownClient() throws Exception {
if (this.client != null) {
this.client.shutdown();
this.client = null;
@Rule
public ExternalResource serverResource = new ExternalResource() {
@Override
protected void after() {
if (server != null) {
try {
server.shutdown();
} catch (final Exception ignore) {
}
}
}
};
private HttpClientNio client;
@Rule
public ExternalResource clientResource = new ExternalResource() {
@Override
protected void after() {
if (client != null) {
try {
client.shutdown();
} catch (final Exception ignore) {
}
}
}
};
private static SSLContext createServerSSLContext() throws Exception {
final URL keyStoreURL = TestTLSIntegration.class.getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.build();
}
private static SSLContext createClientSSLContext() throws Exception {
final URL keyStoreURL = TestTLSIntegration.class.getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
}
@Test
public void testTLSSuccess() throws Exception {
server = new HttpServerNio();
server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), null));
server.setTimeout(5000);
server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
server.start();
final AtomicReference<SSLSession> sslSessionRef = new AtomicReference<SSLSession>(null);
this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), new SSLSetupHandler() {
@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
}
@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
sslSessionRef.set(sslSession);
}
}, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
client.start();
final ListenerEndpoint endpoint = server.getListenerEndpoint();
endpoint.waitFor();
final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
final Future<HttpResponse> future = client.execute(target, request);
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertThat(response, CoreMatchers.notNullValue());
Assert.assertThat(response.getStatusLine().getStatusCode(), CoreMatchers.equalTo(200));
final SSLSession sslSession = sslSessionRef.getAndSet(null);
Assert.assertThat(sslSession.getPeerPrincipal().getName(),
CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
}
@Test
public void testTLSTrustFailure() throws Exception {
server = new HttpServerNio();
server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), null));
server.setTimeout(5000);
server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
server.start();
this.client = new HttpClientNio(new BasicNIOConnFactory(SSLContexts.createDefault(), null, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
client.start();
final ListenerEndpoint endpoint = server.getListenerEndpoint();
endpoint.waitFor();
final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
final Future<HttpResponse> future = client.execute(target, request);
try {
future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.fail("ExecutionException expected");
} catch (final ExecutionException ex) {
final Throwable cause = ex.getCause();
Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(SSLHandshakeException.class));
}
}
@Test
public void testTLSClientAuthFailure() throws Exception {
server = new HttpServerNio();
server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), new SSLSetupHandler() {
@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
sslEngine.setNeedClientAuth(true);
}
@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}
}));
server.setTimeout(5000);
server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
server.start();
this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), null, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
client.start();
final ListenerEndpoint endpoint = server.getListenerEndpoint();
endpoint.waitFor();
final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
final Future<HttpResponse> future = client.execute(target, request);
try {
future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.fail("ExecutionException expected");
} catch (final ExecutionException ex) {
final Throwable cause = ex.getCause();
Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
}
}
@Test
public void testTLSProtocolMismatch() throws Exception {
server = new HttpServerNio();
server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), new SSLSetupHandler() {
@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
sslEngine.setEnabledProtocols(new String[]{"TLSv1.2"});
}
@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}
}));
server.setTimeout(5000);
server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
server.start();
this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), new SSLSetupHandler() {
@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
sslEngine.setEnabledProtocols(new String[]{"SSLv3"});
}
@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}
}, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
client.start();
final ListenerEndpoint endpoint = server.getListenerEndpoint();
endpoint.waitFor();
final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
final Future<HttpResponse> future = client.execute(target, request);
try {
future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.fail("ExecutionException expected");
} catch (final ExecutionException ex) {
final Throwable cause = ex.getCause();
Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
}
}
@Test
public void testTLSCipherMismatch() throws Exception {
server = new HttpServerNio();
server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), new SSLSetupHandler() {
@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
sslEngine.setEnabledCipherSuites(new String[]{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"});
}
@After
public void shutDownServer() throws Exception {
if (this.server != null) {
this.server.shutdown();
this.server = null;
@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}
}));
server.setTimeout(5000);
server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
server.start();
this.client = new HttpClientNio(new BasicNIOConnFactory(createClientSSLContext(), new SSLSetupHandler() {
@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
sslEngine.setEnabledCipherSuites(new String[]{"SSL_RSA_EXPORT_WITH_RC4_40_MD5"});
}
@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}
}, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
client.start();
final ListenerEndpoint endpoint = server.getListenerEndpoint();
endpoint.waitFor();
final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
final HttpHost target = new HttpHost("localhost", address.getPort(), "https");
final BasicHttpRequest request = new BasicHttpRequest("GET", "BLAHx200");
final Future<HttpResponse> future = client.execute(target, request);
try {
future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.fail("ExecutionException expected");
} catch (final ExecutionException ex) {
final Throwable cause = ex.getCause();
Assert.assertThat(cause, CoreMatchers.<Throwable>instanceOf(IOException.class));
}
}
......@@ -119,26 +378,14 @@ public class TestCustomSSL {
};
final URL keyStoreURL = getClass().getResource("/test.keystore");
final String storePassword = "nopassword";
final SSLContext serverSSLContext = SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.build();
this.server = new HttpServerNio();
this.server.setConnectionFactory(new ServerConnectionFactory(serverSSLContext, sslSetupHandler));
this.server.setConnectionFactory(new ServerConnectionFactory(createServerSSLContext(), sslSetupHandler));
this.server.setTimeout(5000);
final SSLContext clientSSLContext = SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
this.client = new HttpClientNio(new BasicNIOConnFactory(new ClientConnectionFactory(clientSSLContext), null));
this.client.setTimeout(5000);
this.server.registerHandler("*", new BasicAsyncRequestHandler(requestHandler));
this.server.start();
this.client = new HttpClientNio(new BasicNIOConnFactory(new ClientConnectionFactory(createClientSSLContext()), null));
this.client.setTimeout(5000);
this.client.start();
final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
......
......@@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-core</artifactId>
<version>4.4.11</version>
<version>4.4.12</version>
</parent>
<artifactId>httpcore-osgi</artifactId>
<name>Apache HttpCore OSGi bundle</name>
......@@ -127,7 +127,6 @@
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${hc.project-info.version}</version>
<inherited>false</inherited>
<reportSets>
<reportSet>
......
......@@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-core</artifactId>
<version>4.4.11</version>
<version>4.4.12</version>
</parent>
<artifactId>httpcore</artifactId>
<name>Apache HttpCore</name>
......@@ -112,7 +112,6 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${hc.javadoc.version}</version>
<configuration>
<!-- reduce console output. Can override with -Dquiet=false -->
<quiet>true</quiet>
......@@ -132,7 +131,6 @@
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${hc.project-info.version}</version>
<inherited>false</inherited>
<reportSets>
<reportSet>
......@@ -147,12 +145,10 @@
<plugin>
<artifactId>maven-jxr-plugin</artifactId>
<version>${hc.jxr.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>${hc.surefire-report.version}</version>
</plugin>
</plugins>
......
......@@ -129,7 +129,7 @@ public class SocketConfig implements Cloneable {
* Determines the default value of the {@link java.net.SocketOptions#TCP_NODELAY} parameter
* for newly created sockets.
* <p>
* Default: {@code false}
* Default: {@code true}
* </p>
*
* @return the default value of the {@link java.net.SocketOptions#TCP_NODELAY} parameter.
......
......@@ -77,6 +77,8 @@ public final class ContentType implements Serializable {
"application/json", Consts.UTF_8);
public static final ContentType APPLICATION_OCTET_STREAM = create(
"application/octet-stream", (Charset) null);
public static final ContentType APPLICATION_SOAP_XML = create(
"application/soap+xml", Consts.UTF_8);
public static final ContentType APPLICATION_SVG_XML = create(
"application/svg+xml", Consts.ISO_8859_1);
public static final ContentType APPLICATION_XHTML_XML = create(
......