Expand Up @@ -31,7 +31,7 @@
import java.net.SocketAddress;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
Expand All @@ -48,15 +48,22 @@
@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
public class SessionRequestImpl implements SessionRequest {

enum SessionRequestState {
ACTIVE,
SUCCESSFUL,
TIMEDOUT,
CANCELLED,
FAILED,
}

private final SocketAddress remoteAddress;
private final SocketAddress localAddress;
private final Object attachment;
private final SessionRequestCallback callback;
private final AtomicBoolean completed;
private final AtomicReference<SessionRequestState> state;

private volatile SelectionKey key;

private volatile boolean terminated;
private volatile int connectTimeout;
private volatile IOSession session = null;
private volatile IOException exception = null;
Expand All @@ -72,7 +79,7 @@ public SessionRequestImpl(
this.localAddress = localAddress;
this.attachment = attachment;
this.callback = callback;
this.completed = new AtomicBoolean(false);
this.state = new AtomicReference<SessionRequestState>(SessionRequestState.ACTIVE);
}

@Override
Expand All @@ -92,24 +99,33 @@ public Object getAttachment() {

@Override
public boolean isCompleted() {
return this.completed.get();
return this.state.get().compareTo(SessionRequestState.ACTIVE) != 0;
}

boolean isTerminated() {
return this.terminated;
return this.state.get().compareTo(SessionRequestState.SUCCESSFUL) > 0;
}

protected void setKey(final SelectionKey key) {
this.key = key;
if (this.isCompleted()) {
key.cancel();
final Channel channel = key.channel();
if (channel.isOpen()) {
try {
channel.close();
} catch (final IOException ignore) {}
}
}
}

@Override
public void waitFor() throws InterruptedException {
if (this.completed.get()) {
if (this.isCompleted()) {
return;
}
synchronized (this) {
while (!this.completed.get()) {
while (!this.isCompleted()) {
wait();
}
}
Expand All @@ -131,7 +147,7 @@ public IOException getException() {

public void completed(final IOSession session) {
Args.notNull(session, "Session");
if (this.completed.compareAndSet(false, true)) {
if (this.state.compareAndSet(SessionRequestState.ACTIVE, SessionRequestState.SUCCESSFUL)) {
synchronized (this) {
this.session = session;
if (this.callback != null) {
Expand All @@ -146,8 +162,7 @@ public void failed(final IOException exception) {
if (exception == null) {
return;
}
if (this.completed.compareAndSet(false, true)) {
this.terminated = true;
if (this.state.compareAndSet(SessionRequestState.ACTIVE, SessionRequestState.FAILED)) {
final SelectionKey key = this.key;
if (key != null) {
key.cancel();
Expand All @@ -167,8 +182,7 @@ public void failed(final IOException exception) {
}

public void timeout() {
if (this.completed.compareAndSet(false, true)) {
this.terminated = true;
if (this.state.compareAndSet(SessionRequestState.ACTIVE, SessionRequestState.TIMEDOUT)) {
final SelectionKey key = this.key;
if (key != null) {
key.cancel();
Expand Down Expand Up @@ -205,8 +219,7 @@ public void setConnectTimeout(final int timeout) {

@Override
public void cancel() {
if (this.completed.compareAndSet(false, true)) {
this.terminated = true;
if (this.state.compareAndSet(SessionRequestState.ACTIVE, SessionRequestState.CANCELLED)) {
final SelectionKey key = this.key;
if (key != null) {
key.cancel();
Expand Down
Expand Up @@ -543,6 +543,10 @@ public void validatePendingRequests() {

protected void requestCompleted(final SessionRequest request) {
if (this.isShutDown.get()) {
final IOSession session = request.getSession();
if (session != null) {
session.close();
}
return;
}
@SuppressWarnings("unchecked")
Expand Down
Expand Up @@ -28,6 +28,7 @@
package org.apache.http.nio.protocol;

import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
Expand Down Expand Up @@ -285,39 +286,67 @@ public void exception(
}
state.setTerminated();
closeHandlers(state, cause);
final Cancellable cancellable = state.getCancellable();
if (cancellable != null) {
cancellable.cancel();
}
final Queue<PipelineEntry> pipeline = state.getPipeline();
if (!pipeline.isEmpty()
|| conn.isResponseSubmitted()
|| state.getResponseState().compareTo(MessageState.INIT) > 0) {
// There is not much that we can do if a response
// has already been submitted or pipelining is being used.
shutdownConnection(conn);
} else {
try {
try {
final Cancellable cancellable = state.getCancellable();
if (cancellable != null) {
cancellable.cancel();
}
if (cause instanceof SocketException) {
// Transport layer is likely unreliable.
conn.shutdown();
return;
}
if (cause instanceof SocketTimeoutException) {
// Connection timed out due to inactivity.
conn.close();
return;
}

if (conn.isResponseSubmitted() || state.getResponseState().compareTo(MessageState.INIT) > 0) {
// There is not much that we can do if a response has already been submitted.
conn.close();
return;
}
HttpRequest request = conn.getHttpRequest();
if (request == null) {
final Incoming incoming = state.getIncoming();
final HttpRequest request = incoming != null ? incoming.getRequest() : null;
final HttpContext context = incoming != null ? incoming.getContext() : new BasicHttpContext();
if (incoming != null) {
request = incoming.getRequest();
}
}
if (request == null) {
final Queue<PipelineEntry> pipeline = state.getPipeline();
final PipelineEntry pipelineEntry = pipeline.poll();
if (pipelineEntry != null) {
request = pipelineEntry.getRequest();
}
}
if (request != null) {
conn.resetInput();
final HttpCoreContext context = HttpCoreContext.create();
final HttpAsyncResponseProducer responseProducer = handleException(cause, context);
final HttpResponse response = responseProducer.generateResponse();
final Outgoing outgoing = new Outgoing(request, response, responseProducer, context);
state.setResponseState(MessageState.INIT);
state.setOutgoing(outgoing);
commitFinalResponse(conn, state);
} catch (final Exception ex) {
shutdownConnection(conn);
closeHandlers(state);
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
log(ex);
return;
}
conn.close();
} catch (final Exception ex) {
shutdownConnection(conn);
closeHandlers(state);
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
log(ex);
}
}

protected HttpResponse createHttpResponse(final int status, final HttpContext context) {
return this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_1, status, context);
}

@Override
public void requestReceived(
final NHttpServerConnection conn) throws IOException, HttpException {
Expand Down Expand Up @@ -350,8 +379,7 @@ public void requestReceived(
&& !(conn instanceof SessionBufferStatus && ((SessionBufferStatus) conn).hasBufferedInput())) {

state.setRequestState(MessageState.ACK_EXPECTED);
final HttpResponse ack = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_CONTINUE, context);
final HttpResponse ack = createHttpResponse(HttpStatus.SC_CONTINUE, context);
if (this.expectationVerifier != null) {
conn.suspendInput();
conn.suspendOutput();
Expand Down Expand Up @@ -442,35 +470,31 @@ public void responseReady(
final Object result = pipelineEntry.getResult();
final HttpRequest request = pipelineEntry.getRequest();
final HttpContext context = pipelineEntry.getContext();
final HttpResponse response = createHttpResponse(HttpStatus.SC_OK, context);
final HttpAsyncExchangeImpl httpExchange = new HttpAsyncExchangeImpl(
request, response, state, conn, context);
if (result != null) {
final HttpResponse response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, context);
final HttpAsyncExchangeImpl httpExchange = new HttpAsyncExchangeImpl(
request, response, state, conn, context);
final HttpAsyncRequestHandler<Object> handler = pipelineEntry.getHandler();
conn.suspendOutput();
try {
handler.handle(result, httpExchange, context);
} catch (final RuntimeException ex) {
throw ex;
} catch (final Exception ex) {
pipeline.add(new PipelineEntry(
request,
null,
ex,
handler,
context));
state.setResponseState(MessageState.READY);
responseReady(conn);
if (!httpExchange.isCompleted()) {
httpExchange.submitResponse(handleException(ex, context));
} else {
log(ex);
conn.close();
}
return;
}
} else {
final Exception exception = pipelineEntry.getException();
final HttpAsyncResponseProducer responseProducer = handleException(
exception != null ? exception : new HttpException("Internal error processing request"),
context);
final HttpResponse error = responseProducer.generateResponse();
state.setOutgoing(new Outgoing(request, error, responseProducer, context));
httpExchange.submitResponse(responseProducer);
}
}
if (state.getResponseState() == MessageState.INIT) {
Expand Down Expand Up @@ -529,7 +553,7 @@ public void endOfInput(final NHttpServerConnection conn) throws IOException {
public void timeout(final NHttpServerConnection conn) throws IOException {
final State state = getState(conn);
if (state != null) {
exception(conn, new SocketTimeoutException(
closeHandlers(state, new SocketTimeoutException(
String.format("%,d milliseconds timeout on connection %s", conn.getSocketTimeout(), conn)));
}
if (conn.getStatus() == NHttpConnection.ACTIVE) {
Expand Down Expand Up @@ -623,10 +647,8 @@ protected HttpAsyncResponseProducer handleException(
if (message == null) {
message = ex.toString();
}
final HttpResponse response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_1,
toStatusCode(ex, context), context);
return new ErrorResponseProducer(response,
new NStringEntity(message, ContentType.DEFAULT_TEXT), false);
final HttpResponse response = createHttpResponse(toStatusCode(ex, context), context);
return new ErrorResponseProducer(response, new NStringEntity(message, ContentType.DEFAULT_TEXT), false);
}

protected int toStatusCode(final Exception ex, final HttpContext context) {
Expand All @@ -637,8 +659,6 @@ protected int toStatusCode(final Exception ex, final HttpContext context) {
code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
} else if (ex instanceof ProtocolException) {
code = HttpStatus.SC_BAD_REQUEST;
} else if (ex instanceof SocketTimeoutException) {
code = HttpStatus.SC_GATEWAY_TIMEOUT;
} else {
code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
}
Expand Down
Expand Up @@ -35,6 +35,7 @@
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
Expand Down Expand Up @@ -89,6 +90,7 @@ public class SSLIOSession implements IOSession, SessionBufferStatus, SocketAcces
private final SSLBuffer inPlain;
private final InternalByteChannel channel;
private final SSLSetupHandler handler;
private final AtomicInteger outboundClosedCount;

private int appEventMask;
private SessionBufferStatus appBufferStatus;
Expand Down Expand Up @@ -163,6 +165,7 @@ public SSLIOSession(
// Allocate buffers for application (unencrypted) data
final int appBuffersize = this.sslEngine.getSession().getApplicationBufferSize();
this.inPlain = bufferManagementStrategy.constructBuffer(appBuffersize);
this.outboundClosedCount = new AtomicInteger(0);
}

/**
Expand Down Expand Up @@ -294,7 +297,15 @@ private void doHandshake() throws SSLException {

SSLEngineResult result = null;
while (handshaking) {
switch (this.sslEngine.getHandshakeStatus()) {
HandshakeStatus handshakeStatus = this.sslEngine.getHandshakeStatus();

// Work-around for what appears to be a bug in Conscrypt SSLEngine that does not
// transition into the handshaking state upon #closeOutbound() call but still
// has some handshake data stuck in its internal buffer.
if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && outboundClosedCount.get() > 0) {
handshakeStatus = HandshakeStatus.NEED_WRAP;
}
switch (handshakeStatus) {
case NEED_WRAP:
// Generate outgoing handshake data

Expand Down Expand Up @@ -370,6 +381,7 @@ private void updateEventMask() {
}
if (this.status == CLOSING && !this.outEncrypted.hasData()) {
this.sslEngine.closeOutbound();
this.outboundClosedCount.incrementAndGet();
}
if (this.status == CLOSING && this.sslEngine.isOutboundDone()
&& (this.endOfStream || this.sslEngine.isInboundDone())
Expand Down Expand Up @@ -405,7 +417,9 @@ private void updateEventMask() {
break;
}

if (this.endOfStream && (this.appBufferStatus == null || !this.appBufferStatus.hasBufferedInput())) {
if (this.endOfStream &&
!this.inPlain.hasData() &&
(this.appBufferStatus == null || !this.appBufferStatus.hasBufferedInput())) {
newMask = newMask & ~EventMask.READ;
}

Expand Down Expand Up @@ -624,7 +638,6 @@ public synchronized void close() {
if (this.session.getSocketTimeout() == 0) {
this.session.setSocketTimeout(1000);
}
this.sslEngine.closeOutbound();
try {
updateEventMask();
} catch (final CancelledKeyException ex) {
Expand Down
Expand Up @@ -37,6 +37,7 @@
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
Expand Down Expand Up @@ -67,6 +68,7 @@
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Assert;
Expand All @@ -82,6 +84,7 @@
public class TestHttpAsyncHandlers extends HttpCoreNIOTestBase {

private final static long RESULT_TIMEOUT_SEC = 30;
private final static int REQ_NUM = 25;

@Parameterized.Parameters(name = "{0}")
public static Collection<Object[]> protocols() {
Expand Down Expand Up @@ -144,7 +147,65 @@ public void testHttpGets() throws Exception {
final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", createRequestUri(pattern, count));
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
}

while (!queue.isEmpty()) {
final Future<HttpResponse> future = queue.remove();
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(expectedPattern, EntityUtils.toString(response.getEntity()));
}
}

@Test
public void testHttpGetsCloseConnection() throws Exception {
this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
final HttpHost target = start();

this.client.setMaxPerRoute(3);
this.client.setMaxTotal(3);

final String pattern = RndTestPatternGenerator.generateText();
final int count = RndTestPatternGenerator.generateCount(1000);

final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", createRequestUri(pattern, count));
request.addHeader(HttpHeaders.CONNECTION, "Close");
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
}

while (!queue.isEmpty()) {
final Future<HttpResponse> future = queue.remove();
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(expectedPattern, EntityUtils.toString(response.getEntity()));
}
}

@Test
public void testHttpGetIdentityTransfer() throws Exception {
this.server.setHttpProcessor(new ImmutableHttpProcessor(new ResponseServer("TEST-SERVER/1.1")));
this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
final HttpHost target = start();

this.client.setMaxPerRoute(3);
this.client.setMaxTotal(3);

final String pattern = RndTestPatternGenerator.generateText();
final int count = RndTestPatternGenerator.generateCount(1000);

final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", createRequestUri(pattern, count));
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
Expand All @@ -170,8 +231,35 @@ public void testHttpHeads() throws Exception {
final int count = RndTestPatternGenerator.generateCount(1000);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("HEAD", createRequestUri(pattern, count));
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
}

while (!queue.isEmpty()) {
final Future<HttpResponse> future = queue.remove();
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
}
}

@Test
public void testHttpHeadsCloseConnection() throws Exception {
this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
final HttpHost target = start();

this.client.setMaxPerRoute(3);
this.client.setMaxTotal(3);

final String pattern = RndTestPatternGenerator.generateText();
final int count = RndTestPatternGenerator.generateCount(1000);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("HEAD", createRequestUri(pattern, count));
request.addHeader(HttpHeaders.CONNECTION, "Close");
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
}
Expand All @@ -181,6 +269,7 @@ public void testHttpHeads() throws Exception {
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertNull(response.getEntity());
}
}

Expand All @@ -198,7 +287,7 @@ public void testHttpPostsWithContentLength() throws Exception {
final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(
"POST", createRequestUri(pattern, count));
final NStringEntity entity = new NStringEntity(expectedPattern, ContentType.DEFAULT_TEXT);
Expand Down Expand Up @@ -229,7 +318,7 @@ public void testHttpPostsChunked() throws Exception {
final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(
"POST", createRequestUri(pattern, count));
final NStringEntity entity = new NStringEntity(expectedPattern, ContentType.DEFAULT_TEXT);
Expand Down Expand Up @@ -261,7 +350,7 @@ public void testHttpPostsHTTP10() throws Exception {
final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(
"POST", createRequestUri(pattern, count), HttpVersion.HTTP_1_0);
final NStringEntity entity = new NStringEntity(expectedPattern, ContentType.DEFAULT_TEXT);
Expand Down Expand Up @@ -290,7 +379,7 @@ public void testHttpPostsNoEntity() throws Exception {
final int count = RndTestPatternGenerator.generateCount(1000);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(
"POST", createRequestUri(pattern, count));
request.setEntity(null);
Expand Down Expand Up @@ -388,7 +477,7 @@ public void testHttpPostsWithExpectContinue() throws Exception {
final String expectedPattern = createExpectedString(pattern, count);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest(
"POST", createRequestUri(pattern, count));
final NStringEntity entity = new NStringEntity(expectedPattern, ContentType.DEFAULT_TEXT);
Expand Down Expand Up @@ -525,7 +614,7 @@ public void run() {
final int count = RndTestPatternGenerator.generateCount(1000);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("HEAD", createRequestUri(pattern, count));
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
Expand Down Expand Up @@ -671,7 +760,7 @@ public void testNoServiceHandler() throws Exception {
final int count = RndTestPatternGenerator.generateCount(1000);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", createRequestUri(pattern, count));
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
Expand Down Expand Up @@ -704,7 +793,7 @@ public void handle(
this.client.setMaxTotal(3);

final Queue<Future<HttpResponse>> queue = new ConcurrentLinkedQueue<Future<HttpResponse>>();
for (int i = 0; i < 30; i++) {
for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", "/");
final Future<HttpResponse> future = this.client.execute(target, request);
queue.add(future);
Expand Down
@@ -0,0 +1,284 @@
/*
* ====================================================================
* 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.http.nio.integration;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;

import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
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.protocol.BasicAsyncRequestHandler;
import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.ListenerEndpoint;
import org.apache.http.nio.reactor.ssl.SSLSetupHandler;
import org.apache.http.nio.testserver.HttpClientNio;
import org.apache.http.nio.testserver.HttpServerNio;
import org.apache.http.nio.testserver.ServerConnectionFactory;
import org.apache.http.nio.util.TestingSupport;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.conscrypt.Conscrypt;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
public class TestJSSEProviderIntegration {

private final static long RESULT_TIMEOUT_SEC = 30;
private final static int REQ_NUM = 25;

@Parameterized.Parameters(name = "{0} {1}")
public static Collection<Object[]> protocols() {
return Arrays.asList(new Object[][]{
{"Oracle", null},
{"Conscrypt", "TLSv1.2"},
{"Conscrypt", "TLSv1.3"}
});
}

private final String securityProviderName;
private final String protocolVersion;

private Provider securityProvider;
private HttpServerNio server;
private HttpClientNio client;

public TestJSSEProviderIntegration(final String securityProviderName, final String protocolVersion) {
super();
this.securityProviderName = securityProviderName;
this.protocolVersion = protocolVersion;
}

@BeforeClass
public static void determineJavaVersion() {
Assume.assumeTrue("Java version must be 8 or greater", TestingSupport.determineJRELevel() >= 8);
}

@Rule
public TestRule resourceRules = RuleChain.outerRule(new ExternalResource() {

@Override
protected void before() throws Throwable {
if ("Conscrypt".equalsIgnoreCase(securityProviderName)) {
securityProvider = Conscrypt.newProviderBuilder().provideTrustManager(true).build();
} else {
securityProvider = null;
}
if (securityProvider != null) {
Security.insertProviderAt(securityProvider, 1);
}
}

@Override
protected void after() {
if (securityProvider != null) {
Security.removeProvider(securityProvider.getName());
securityProvider = null;
}
}

}).around(new ExternalResource() {

@Override
protected void before() throws Throwable {
final URL keyStoreURL = TestJSSEProviderIntegration.class.getResource("/test-server.p12");
final String storePassword = "nopassword";
final SSLContext sslContext = SSLContextBuilder.create()
.setKeyStoreType("pkcs12")
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.setSecureRandom(new SecureRandom())
.build();

server = new HttpServerNio();
server.setConnectionFactory(new ServerConnectionFactory(sslContext, new SSLSetupHandler() {

@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
if (protocolVersion != null) {
sslEngine.setEnabledProtocols(new String[]{protocolVersion});
}
}

@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}

}));
server.setTimeout(5000);
}

@Override
protected void after() {
if (server != null) {
try {
server.shutdown();
} catch (final Exception ignore) {
}
}
}

}).around(new ExternalResource() {

@Override
protected void before() throws Throwable {
final URL keyStoreURL = TestJSSEProviderIntegration.class.getResource("/test-client.p12");
final String storePassword = "nopassword";
final SSLContext sslContext = SSLContextBuilder.create()
.setKeyStoreType("pkcs12")
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.setSecureRandom(new SecureRandom())
.build();

client = new HttpClientNio(new BasicNIOConnFactory(sslContext, new SSLSetupHandler() {

@Override
public void initalize(final SSLEngine sslEngine) throws SSLException {
if (protocolVersion != null) {
sslEngine.setEnabledProtocols(new String[]{protocolVersion});
}
}

@Override
public void verify(final IOSession ioSession, final SSLSession sslSession) throws SSLException {
}

}, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
}

@Override
protected void after() {
if (client != null) {
try {
client.shutdown();
} catch (final Exception ignore) {
}
}
}

});

private HttpHost start() throws IOException, InterruptedException {
this.server.start();
this.client.start();

final ListenerEndpoint endpoint = this.server.getListenerEndpoint();
endpoint.waitFor();

final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
return new HttpHost("localhost", address.getPort(), "https");
}

@Test
public void testHttpGets() throws Exception {
this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
final HttpHost target = start();

this.client.setMaxPerRoute(3);
this.client.setMaxTotal(3);

final String pattern = RndTestPatternGenerator.generateText();

for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x1");
final Future<HttpResponse> future = this.client.execute(target, request);
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(pattern, EntityUtils.toString(response.getEntity()));
}
}

@Test
public void testHttpGetsCloseConnection() throws Exception {
this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
final HttpHost target = start();

this.client.setMaxPerRoute(3);
this.client.setMaxTotal(3);

final String pattern = RndTestPatternGenerator.generateText();

for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x1");
request.addHeader(HttpHeaders.CONNECTION, "Close");
final Future<HttpResponse> future = this.client.execute(target, request);
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(pattern, EntityUtils.toString(response.getEntity()));
}
}

@Test
public void testHttpGetIdentityTransfer() throws Exception {
this.server.setHttpProcessor(new ImmutableHttpProcessor(new ResponseServer("TEST-SERVER/1.1")));
this.server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
final HttpHost target = start();

this.client.setMaxPerRoute(3);
this.client.setMaxTotal(3);

final String pattern = RndTestPatternGenerator.generateText();

for (int i = 0; i < REQ_NUM; i++) {
final BasicHttpRequest request = new BasicHttpRequest("GET", pattern + "x1");
final Future<HttpResponse> future = this.client.execute(target, request);
final HttpResponse response = future.get(RESULT_TIMEOUT_SEC, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(pattern, EntityUtils.toString(response.getEntity()));
}
}

}
Expand Up @@ -58,6 +58,7 @@
import org.apache.http.nio.testserver.HttpClientNio;
import org.apache.http.nio.testserver.HttpServerNio;
import org.apache.http.nio.testserver.ServerConnectionFactory;
import org.apache.http.nio.util.TestingSupport;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpRequestHandler;
Expand All @@ -73,6 +74,8 @@ public class TestTLSIntegration {

private final static long RESULT_TIMEOUT_SEC = 30;

private static int JRE_LEVEL = TestingSupport.determineJRELevel();

private HttpServerNio server;

@Rule
Expand Down Expand Up @@ -108,21 +111,39 @@ protected void after() {
};

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();

if (JRE_LEVEL >= 8) {
final URL keyStoreURL = TestTLSIntegration.class.getResource("/test-server.p12");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.setKeyStoreType("pkcs12")
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.build();
} else {
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();
if (JRE_LEVEL >= 8) {
final URL keyStoreURL = TestTLSIntegration.class.getResource("/test-client.p12");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.setKeyStoreType("pkcs12")
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
} else {
final URL keyStoreURL = TestTLSIntegration.class.getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
}
}

@Test
Expand Down Expand Up @@ -165,8 +186,13 @@ public void verify(final IOSession ioSession, final SSLSession sslSession) throw
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"));
if (JRE_LEVEL >= 8) {
Assert.assertThat(sslSession.getPeerPrincipal().getName(),
CoreMatchers.equalTo("CN=Test Server,OU=HttpComponents Project,O=Apache Software Foundation"));
} else {
Assert.assertThat(sslSession.getPeerPrincipal().getName(),
CoreMatchers.equalTo("CN=localhost,OU=Apache HttpComponents,O=Apache Software Foundation"));
}
}

@Test
Expand All @@ -177,7 +203,7 @@ public void testTLSTrustFailure() throws Exception {
server.registerHandler("*", new BasicAsyncRequestHandler(new SimpleRequestHandler()));
server.start();

this.client = new HttpClientNio(new BasicNIOConnFactory(SSLContexts.createDefault(), null, ConnectionConfig.DEFAULT));
client = new HttpClientNio(new BasicNIOConnFactory(SSLContexts.createDefault(), null, ConnectionConfig.DEFAULT));
client.setTimeout(5000);
client.start();

Expand Down
Expand Up @@ -163,6 +163,7 @@ public void testHttpExceptionHandling() throws Exception {
state.setIncoming(incoming);
state.setCancellable(this.cancellable);
this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state);
Mockito.when(this.conn.getHttpRequest()).thenReturn(request);

final HttpException httpex = new HttpException();
this.protocolHandler.exception(this.conn, httpex);
Expand Down Expand Up @@ -210,6 +211,7 @@ public void testExceptionHandlingRuntimeException() throws Exception {
state.setOutgoing(outgoing);
state.setCancellable(this.cancellable);
this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state);
Mockito.when(this.conn.getHttpRequest()).thenReturn(request);

Mockito.doThrow(new RuntimeException()).when(this.httpProcessor).process(
Matchers.any(HttpResponse.class), Matchers.any(HttpContext.class));
Expand Down Expand Up @@ -243,6 +245,7 @@ public void testHttpExceptionHandlingIOException() throws Exception {
state.setOutgoing(outgoing);
state.setCancellable(this.cancellable);
this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state);
Mockito.when(this.conn.getHttpRequest()).thenReturn(request);

Mockito.doThrow(new IOException()).when(this.httpProcessor).process(
Matchers.any(HttpResponse.class), Matchers.any(HttpContext.class));
Expand Down Expand Up @@ -280,7 +283,7 @@ public void testHttpExceptionHandlingResponseSubmitted() throws Exception {

Assert.assertEquals(MessageState.READY, state.getRequestState());
Assert.assertEquals(MessageState.READY, state.getResponseState());
Mockito.verify(this.conn).shutdown();
Mockito.verify(this.conn).close();
Mockito.verify(this.requestConsumer).failed(httpex);
Mockito.verify(this.requestConsumer).close();
Mockito.verify(this.responseProducer).failed(httpex);
Expand Down Expand Up @@ -1329,19 +1332,31 @@ public void testEndOfInputNoTimeout() throws Exception {
@Test
public void testTimeoutActiveConnection() throws Exception {
final State state = new State();
final HttpContext exchangeContext = new BasicHttpContext();
final HttpRequest request = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
final Incoming incoming = new Incoming(
request, this.requestHandler, this.requestConsumer, exchangeContext);
state.setIncoming(incoming);
this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state);
Mockito.when(this.conn.getHttpRequest()).thenReturn(request);
Mockito.when(this.conn.getStatus()).thenReturn(NHttpConnection.ACTIVE, NHttpConnection.CLOSED);

this.protocolHandler.timeout(this.conn);

Mockito.verify(this.conn).close();
Mockito.verify(this.conn, Mockito.atLeastOnce()).close();
Mockito.verify(this.conn, Mockito.never()).setSocketTimeout(Matchers.anyInt());
}

@Test
public void testTimeoutActiveConnectionBufferedData() throws Exception {
final State state = new State();
final HttpContext exchangeContext = new BasicHttpContext();
final HttpRequest request = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
final Incoming incoming = new Incoming(
request, this.requestHandler, this.requestConsumer, exchangeContext);
state.setIncoming(incoming);
this.connContext.setAttribute(HttpAsyncService.HTTP_EXCHANGE_STATE, state);
Mockito.when(this.conn.getHttpRequest()).thenReturn(request);
Mockito.when(this.conn.getStatus()).thenReturn(NHttpConnection.ACTIVE, NHttpConnection.CLOSING);

this.protocolHandler.timeout(this.conn);
Expand Down
Expand Up @@ -32,6 +32,7 @@
import javax.net.ssl.SSLContext;

import org.apache.http.impl.nio.pool.BasicNIOConnFactory;
import org.apache.http.nio.util.TestingSupport;
import org.apache.http.ssl.SSLContextBuilder;
import org.junit.After;

Expand All @@ -41,6 +42,8 @@
*/
public abstract class HttpCoreNIOTestBase {

private static int JRE_LEVEL = TestingSupport.determineJRELevel();

public enum ProtocolScheme { http, https }

private final ProtocolScheme scheme;
Expand All @@ -61,20 +64,39 @@ public ProtocolScheme getScheme() {
}

protected SSLContext createServerSSLContext() throws Exception {
final URL keyStoreURL = getClass().getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.build();
if (JRE_LEVEL >= 8) {
final URL keyStoreURL = getClass().getResource("/test-server.p12");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.setKeyStoreType("pkcs12")
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.build();
} else {
final URL keyStoreURL = getClass().getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.loadKeyMaterial(keyStoreURL, storePassword.toCharArray(), storePassword.toCharArray())
.build();
}
}

protected SSLContext createClientSSLContext() throws Exception {
final URL keyStoreURL = getClass().getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
if (JRE_LEVEL >= 8) {
final URL keyStoreURL = getClass().getResource("/test-client.p12");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.setKeyStoreType("pkcs12")
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
} else {
final URL keyStoreURL = getClass().getResource("/test.keystore");
final String storePassword = "nopassword";
return SSLContextBuilder.create()
.loadTrustMaterial(keyStoreURL, storePassword.toCharArray())
.build();
}
}

protected ServerConnectionFactory createServerConnectionFactory() throws Exception {
Expand Down
@@ -0,0 +1,49 @@
/*
* ====================================================================
* 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.http.nio.util;

public final class TestingSupport {

public static int determineJRELevel() {
final String s = System.getProperty("java.version");
final String[] parts = s.split("\\.");
if (parts.length > 0) {
try {
final int majorVersion = Integer.parseInt(parts[0]);
if (majorVersion > 1) {
return majorVersion;
} else if (majorVersion == 1 && parts.length > 1) {
return Integer.parseInt(parts[1]);
}
} catch (final NumberFormatException ignore) {
}
}
return 7;
}

}
Binary file added httpcore-nio/src/test/resources/ca.p12
Binary file not shown.
Binary file added httpcore-nio/src/test/resources/test-client.p12
Binary file not shown.
Binary file added httpcore-nio/src/test/resources/test-server.p12
Binary file not shown.
31 changes: 0 additions & 31 deletions httpcore-nio/src/test/resources/test-ssl.txt

This file was deleted.

2 changes: 1 addition & 1 deletion httpcore-osgi/pom.xml
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-core</artifactId>
<version>4.4.12-SNAPSHOT</version>
<version>4.4.13</version>
</parent>
<artifactId>httpcore-osgi</artifactId>
<name>Apache HttpCore OSGi bundle</name>
Expand Down
2 changes: 1 addition & 1 deletion httpcore/pom.xml
Expand Up @@ -28,7 +28,7 @@
<parent>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcomponents-core</artifactId>
<version>4.4.12-SNAPSHOT</version>
<version>4.4.13</version>
</parent>
<artifactId>httpcore</artifactId>
<name>Apache HttpCore</name>
Expand Down
Expand Up @@ -70,7 +70,7 @@ public class ProtocolVersion implements Serializable, Cloneable {
*/
public ProtocolVersion(final String protocol, final int major, final int minor) {
this.protocol = Args.notNull(protocol, "Protocol name");
this.major = Args.notNegative(major, "Protocol minor version");
this.major = Args.notNegative(major, "Protocol major version");
this.minor = Args.notNegative(minor, "Protocol minor version");
}

Expand Down
Expand Up @@ -133,7 +133,7 @@ public boolean keepAlive(final HttpResponse response,
if (clhs.length == 1) {
final Header clh = clhs[0];
try {
final int contentLen = Integer.parseInt(clh.getValue());
final long contentLen = Long.parseLong(clh.getValue());
if (contentLen < 0) {
return false;
}
Expand Down
Expand Up @@ -30,8 +30,8 @@

import org.apache.http.HttpClientConnection;
import org.apache.http.HttpHost;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.pool.PoolEntry;

/**
Expand All @@ -52,7 +52,16 @@ public BasicPoolEntry(final String id, final HttpHost route, final HttpClientCon
@Override
public void close() {
try {
this.getConnection().close();
final HttpClientConnection connection = getConnection();
try {
final int socketTimeout = connection.getSocketTimeout();
if (socketTimeout <= 0 || socketTimeout > 1000) {
connection.setSocketTimeout(1000);
}
connection.close();
} catch (final IOException ex) {
connection.shutdown();
}
} catch (final IOException ignore) {
}
}
Expand Down
Expand Up @@ -137,9 +137,11 @@ public void append(final char[] b, final int off, final int len) {
}

for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
if ((b[i1] >= 0x20 && b[i1] <= 0x7E) || // Visible ASCII
(b[i1] >= 0xA0 && b[i1] <= 0xFF)) { // Visible ISO-8859-1
this.buffer[i2] = (byte) b[i1];
final int c = b[i1];
if ((c >= 0x20 && c <= 0x7E) || // Visible ASCII
(c >= 0xA0 && c <= 0xFF) || // Visible ISO-8859-1
c == 0x09) { // TAB
this.buffer[i2] = (byte) c;
} else {
this.buffer[i2] = '?';
}
Expand Down
Expand Up @@ -322,7 +322,7 @@ public void testControlCharFiltering() throws Exception {
final byte[] bytes = asByteArray(chars);

Assert.assertEquals(
"????????????????????????????????"
"?????????\t??????????????????????"
+ " !\"#$%&'()*+,-./0123456789:;<=>?"
+ "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ "`abcdefghijklmnopqrstuvwxyz"
Expand Down
Binary file added httpcore/src/test/resources/ca.p12
Binary file not shown.
Binary file added httpcore/src/test/resources/test-client.p12
Binary file not shown.
Binary file added httpcore/src/test/resources/test-server.p12
Binary file not shown.
39 changes: 21 additions & 18 deletions httpcore/src/test/resources/test-ssl.txt
Expand Up @@ -22,11 +22,14 @@
# information on the Apache Software Foundation, please see
# <http://www.apache.org/>.


*** Use Java 11 or older ***

== generate test key store with a self signed key

---
keytool -genkey \
-keystore test.keystore -storepass nopassword \
-keystore test.p12 -storepass nopassword \
-keyalg RSA -keysize 2048 \
-alias simple-http-server \
-validity 100000 \
Expand All @@ -38,7 +41,7 @@ keytool -genkey \

---
keytool -genkey \
-keystore test-keypasswd.keystore -storepass nopassword \
-keystore test-keypasswd.p12 -storepass nopassword \
-keyalg RSA -keysize 2048 -keypass password \
-alias simple-http-server \
-validity 100000 \
Expand All @@ -50,7 +53,7 @@ keytool -genkey \

---
keytool -genkeypair \
-keystore ca.keystore -storepass nopassword \
-keystore ca.p12 -storepass nopassword \
-keyalg RSA -keysize 2048 -keypass password \
-alias ca \
-validity 100000 \
Expand All @@ -64,7 +67,7 @@ keytool -genkeypair \

---
keytool -export \
-keystore ca.keystore -storepass nopassword -keypass password \
-keystore ca.p12 -storepass nopassword -keypass password \
-alias ca \
-file test-ca.crt \
-rfc
Expand All @@ -74,7 +77,7 @@ keytool -export \

---
keytool -genkeypair \
-keystore test-server.keystore -storepass nopassword \
-keystore test-server.p12 -storepass nopassword \
-keyalg RSA -keysize 2048 \
-alias server \
-validity 100000 \
Expand All @@ -85,7 +88,7 @@ keytool -genkeypair \

---
keytool -certreq \
-keystore test-server.keystore -storepass nopassword \
-keystore test-server.p12 -storepass nopassword \
-alias server \
-file server.csr
---
Expand All @@ -94,7 +97,7 @@ keytool -certreq \

---
keytool -gencert \
-keystore ca.keystore -storepass nopassword -keypass password \
-keystore ca.p12 -storepass nopassword -keypass password \
-alias ca \
-validity 100000 \
-infile server.csr \
Expand All @@ -109,12 +112,12 @@ keytool -gencert \

---
keytool -importcert \
-keystore test-server.keystore -storepass nopassword \
-keystore test-server.p12 -storepass nopassword \
-file test-ca.crt \
-alias caroot
---
keytool -importcert \
-keystore test-server.keystore -storepass nopassword \
-keystore test-server.p12 -storepass nopassword \
-file server.crt \
-alias server
---
Expand All @@ -123,14 +126,14 @@ keytool -importcert \

---
keytool -genkeypair \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-keyalg RSA -keysize 2048 \
-alias client1 \
-validity 100000 \
-dname "CN=Test Client 1, OU=HttpComponents Project, O=Apache Software Foundation"
---
keytool -genkeypair \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-keyalg RSA -keysize 2048 \
-alias client2 \
-validity 100000 \
Expand All @@ -141,12 +144,12 @@ keytool -genkeypair \

---
keytool -certreq \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-alias client1 \
-file client1.csr
---
keytool -certreq \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-alias client2 \
-file client2.csr
---
Expand All @@ -155,7 +158,7 @@ keytool -certreq \

---
keytool -gencert \
-keystore ca.keystore -storepass nopassword -keypass password \
-keystore ca.p12 -storepass nopassword -keypass password \
-alias ca \
-validity 100000 \
-infile client1.csr \
Expand All @@ -165,7 +168,7 @@ keytool -gencert \
-rfc
---
keytool -gencert \
-keystore ca.keystore -storepass nopassword -keypass password \
-keystore ca.p12 -storepass nopassword -keypass password \
-alias ca \
-validity 100000 \
-infile client2.csr \
Expand All @@ -179,17 +182,17 @@ keytool -gencert \

---
keytool -importcert \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-file test-ca.crt \
-alias caroot
---
keytool -importcert \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-file client1.crt \
-alias client1
---
keytool -importcert \
-keystore test-client.keystore -storepass nopassword \
-keystore test-client.p12 -storepass nopassword \
-file client2.crt \
-alias client2
---
Expand Down
11 changes: 9 additions & 2 deletions pom.xml
Expand Up @@ -33,7 +33,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>httpcomponents-core</artifactId>
<name>Apache HttpComponents Core</name>
<version>4.4.12-SNAPSHOT</version>
<version>4.4.13</version>
<description>Apache HttpComponents Core is a library of components for building HTTP enabled services</description>
<url>http://hc.apache.org/httpcomponents-core-ga</url>
<inceptionYear>2005</inceptionYear>
Expand Down Expand Up @@ -61,7 +61,7 @@
<connection>scm:git:https://git-wip-us.apache.org/repos/asf/httpcomponents-core.git</connection>
<developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/httpcomponents-core.git</developerConnection>
<url>https://github.com/apache/httpcomponents-core/tree/${project.scm.tag}</url>
<tag>4.4.12-SNAPSHOT</tag>
<tag>4.4.13</tag>
</scm>

<modules>
Expand All @@ -76,6 +76,7 @@
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<maven.compiler.showDeprecation>false</maven.compiler.showDeprecation>
<conscrypt.version>2.2.1</conscrypt.version>
<junit.version>4.12</junit.version>
<mockito.version>1.10.19</mockito.version>
<commons-logging.version>1.2</commons-logging.version>
Expand All @@ -85,6 +86,11 @@

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.conscrypt</groupId>
<artifactId>conscrypt-openjdk-uber</artifactId>
<version>${conscrypt.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down Expand Up @@ -314,6 +320,7 @@
<exclude>**/.checkstyle</exclude>
<exclude>src/docbkx/resources/**</exclude>
<exclude>src/test/resources/*.truststore</exclude>
<exclude>src/test/resources/*.p12</exclude>
</excludes>
</configuration>
</plugin>
Expand Down