/*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.reactivex.netty.protocol.http.client;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.DefaultLastHttpContent;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.reactivex.netty.client.pool.FIFOIdleConnectionsHolder;
import io.reactivex.netty.client.pool.PoolConfig;
import io.reactivex.netty.client.pool.PooledConnection;
import io.reactivex.netty.protocol.http.client.internal.HttpClientResponseImpl;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import rx.observers.TestSubscriber;
import java.nio.channels.ClosedChannelException;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class HttpClientPoolTest {
@Rule
public final PooledHttpClientRule clientRule = new PooledHttpClientRule();
@Test(timeout = 60000)
public void testBasicAcquireRelease() throws Exception {
clientRule.assertIdleConnections(0);
final HttpClientRequest<ByteBuf, ByteBuf> request1 = clientRule.getHttpClient().createGet("/");
TestSubscriber<Void> subscriber = clientRule.sendRequestAndDiscardResponseContent(request1);
clientRule.assertIdleConnections(0); // No idle connections post connect
clientRule.assertRequestHeadersWritten(HttpMethod.GET, "/");
clientRule.feedResponseAndComplete();
subscriber.assertTerminalEvent();
subscriber.assertNoErrors();
clientRule.getLastCreatedChannel().runPendingTasks();
clientRule.assertIdleConnections(1);
}
@Test(timeout = 60000)
public void testBasicAcquireReleaseWithServerClose() throws Exception {
clientRule.assertIdleConnections(0);
final HttpClientRequest<ByteBuf, ByteBuf> request1 = clientRule.getHttpClient().createGet("/");
TestSubscriber<Void> subscriber = clientRule.sendRequestAndDiscardResponseContent(request1);
clientRule.assertIdleConnections(0); // No idle connections post connect
clientRule.assertRequestHeadersWritten(HttpMethod.GET, "/");
clientRule.getLastCreatedChannel().close().await();
subscriber.assertTerminalEvent();
assertThat("On complete sent instead of onError", subscriber.getOnCompletedEvents(), is(empty()));
assertThat("Unexpected error notifications count.", subscriber.getOnErrorEvents(), hasSize(1));
assertThat("Unexpected error notification.", subscriber.getOnErrorEvents().get(0),
is(instanceOf(ClosedChannelException.class)));
clientRule.getLastCreatedChannel().runPendingTasks();
clientRule.assertIdleConnections(0); // Since, channel is closed, it should be discarded.
}
@Test(timeout = 60000)
public void testCloseOnKeepAliveTimeout() throws Exception {
clientRule.assertIdleConnections(0);
final HttpClientRequest<ByteBuf, ByteBuf> request1 = clientRule.getHttpClient().createGet("/");
TestSubscriber<HttpClientResponse<ByteBuf>> responseSub = clientRule.sendRequest(request1);
clientRule.assertIdleConnections(0); // No idle connections post connect
clientRule.assertRequestHeadersWritten(HttpMethod.GET, "/");
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().set(HttpClientResponseImpl.KEEP_ALIVE_HEADER_NAME,
HttpClientResponseImpl.KEEP_ALIVE_TIMEOUT_HEADER_ATTR + "=0");
clientRule.feedResponseAndComplete(response);
HttpClientResponse<ByteBuf> resp = clientRule.discardResponseContent(responseSub);
Channel nettyChannel = resp.unsafeNettyChannel();
clientRule.getLastCreatedChannel().runPendingTasks();
// Close is while release, so this should be post running pending tasks
assertThat("Channel not closed.", nettyChannel.isOpen(), is(false));
clientRule.assertIdleConnections(0); // Since, the channel is closed
}
@Test(timeout = 60000)
public void testReuse() throws Exception {
clientRule.assertIdleConnections(0);
Channel channel1 = clientRule.sendRequestAndGetChannel();
clientRule.getLastCreatedChannel().runPendingTasks();
clientRule.assertIdleConnections(1);
Channel channel2 = clientRule.sendRequestAndGetChannel();
assertThat("Connection was not reused.", channel2, is(channel1));
}
public static class PooledHttpClientRule extends HttpClientRule {
private FIFOIdleConnectionsHolder<ByteBuf, ByteBuf> idleConnHolder;
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
idleConnHolder = new FIFOIdleConnectionsHolder<>();
PoolConfig<ByteBuf, ByteBuf> pConfig = new PoolConfig<>();
pConfig.idleConnectionsHolder(idleConnHolder);
setupPooledConnectionFactory(pConfig); // sets the client et al.
base.evaluate();
}
};
}
public void assertIdleConnections(int expectedCount) {
TestSubscriber<PooledConnection<ByteBuf, ByteBuf>> testSub = new TestSubscriber<>();
idleConnHolder.peek().subscribe(testSub);
testSub.assertTerminalEvent();
testSub.assertNoErrors();
assertThat("Unexpected number of connections in the holder.", testSub.getOnNextEvents(),
hasSize(expectedCount));
}
protected Channel sendRequestAndGetChannel() {
final HttpClientRequest<ByteBuf, ByteBuf> request1 = getHttpClient().createGet("/");
TestSubscriber<HttpClientResponse<ByteBuf>> respSub = sendRequest(request1);
feedResponseHeaders(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK),
getLastCreatedChannelWithFeeder());
respSub.awaitTerminalEvent();
assertIdleConnections(0); // No idle connections post connect
assertRequestHeadersWritten(HttpMethod.GET, "/");
feedResponse(new DefaultLastHttpContent());
final HttpClientResponse<ByteBuf> response = discardResponseContent(respSub);
return response.unsafeNettyChannel();
}
}
}