Package io.reactivex.netty.protocol.http.client

Source Code of io.reactivex.netty.protocol.http.client.HttpClientPoolTest

/*
* Copyright 2014 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.ChannelPipeline;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.timeout.ReadTimeoutException;
import io.reactivex.netty.ChannelCloseListener;
import io.reactivex.netty.RxNetty;
import io.reactivex.netty.client.PoolConfig;
import io.reactivex.netty.client.PoolStats;
import io.reactivex.netty.client.TrackableMetricEventsListener;
import io.reactivex.netty.metrics.HttpClientMetricEventsListener;
import io.reactivex.netty.pipeline.PipelineConfigurator;
import io.reactivex.netty.pipeline.PipelineConfiguratorComposite;
import io.reactivex.netty.pipeline.PipelineConfigurators;
import io.reactivex.netty.protocol.http.server.HttpServer;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
* @author Nitesh Kant
*/
public class HttpClientPoolTest {

    private static HttpServer<ByteBuf, ByteBuf> mockServer;
    private static int port;

    private final ChannelCloseListener channelCloseListener = new ChannelCloseListener();
    private HttpClientImpl<ByteBuf,ByteBuf> client;
    private TrackableMetricEventsListener stateChangeListener;
    private PoolStats stats;

    @BeforeClass
    public static void init() throws Exception {
        mockServer = RxNetty.newHttpServerBuilder(0, new RequestProcessor()).enableWireLogging(LogLevel.ERROR)
                            .build().start();
        port = mockServer.getServerPort();
    }

    @AfterClass
    public static void shutdown() throws InterruptedException {
        mockServer.shutdown();
        mockServer.waitTillShutdown(1, TimeUnit.MINUTES);
    }

    @After
    public void tearDown() throws Exception {
        if (null != client) {
            client.shutdown();
        }
    }

    @Test
    public void testBasicAcquireRelease() throws Exception {

        client = newHttpClient(2, PoolConfig.DEFAULT_CONFIG.getMaxIdleTimeMillis(), null);

        HttpClientResponse<ByteBuf> response = submitAndWaitForCompletion(client, HttpClientRequest.createGet("/"), null
        );

        Assert.assertEquals("Unexpected HTTP response code.", 200, response.getStatus().code());
        Assert.assertEquals("Unexpected Idle connection count.", 1, stats.getIdleCount());
        Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
        Assert.assertEquals("Unexpected total connection count.", 1, stats.getTotalConnectionCount());
    }

    @Test
    public void testBasicAcquireReleaseWithServerClose() throws Exception {

        client = newHttpClient(2, PoolConfig.DEFAULT_CONFIG.getMaxIdleTimeMillis(), null);

        final long[] idleCountOnComplete = {0};
        final long[] inUseCountOnComplete = {0};
        final long[] totalCountOnComplete = {0};

        Action0 onComplete = new Action0() {
            @Override
            public void call() {
                idleCountOnComplete[0] = stats.getIdleCount();
                inUseCountOnComplete[0] = stats.getInUseCount();
                totalCountOnComplete[0] = stats.getTotalConnectionCount();
            }
        };

        HttpClientResponse<ByteBuf> response = submitAndWaitForCompletion(client,
                                                                          HttpClientRequest.createGet("test/closeConnection"),
                                                                          onComplete);

        Assert.assertEquals("Unexpected idle connection count on completion of submit. ", 0, idleCountOnComplete[0]);
        Assert.assertEquals("Unexpected in-use connection count on completion of submit. ", 0, inUseCountOnComplete[0]);
        Assert.assertEquals("Unexpected total connection count on completion of submit. ", 0, totalCountOnComplete[0]);

        Assert.assertEquals("Unexpected HTTP response code.", 200, response.getStatus().code());
        Assert.assertEquals("Unexpected Idle connection count.", 0, stats.getIdleCount());
        Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
        Assert.assertEquals("Unexpected total connection count.", 0, stats.getTotalConnectionCount());
        Assert.assertEquals("Unexpected connection eviction count.", 1, stateChangeListener.getEvictionCount());
    }

    @Test
    public void testReadtimeoutCloseConnection() throws Exception {
        HttpClient.HttpClientConfig conf = new HttpClient.HttpClientConfig.Builder().readTimeout(1, TimeUnit.SECONDS).build();
        client = newHttpClient(1, PoolConfig.DEFAULT_CONFIG.getMaxIdleTimeMillis(), conf);
        try {
            submitAndWaitForCompletion(client, HttpClientRequest.createGet("test/timeout?timeout=60000"), null);
            throw new AssertionError("Expected read timeout error.");
        } catch (ReadTimeoutException e) {
            waitForClose();
            Assert.assertEquals("Unexpected Idle connection count.", 0, stats.getIdleCount());
            Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
            Assert.assertEquals("Unexpected total connection count.", 0, stats.getTotalConnectionCount());
            Assert.assertEquals("Unexpected connection eviction count.", 1, stateChangeListener.getEvictionCount());
        }
    }

    @Test
    public void testCloseOnKeepAliveTimeout() throws Exception {
        client = newHttpClient(2, PoolConfig.DEFAULT_CONFIG.getMaxIdleTimeMillis(), null);

        HttpClientResponse<ByteBuf> response = submitAndWaitForCompletion(client,
                                                                          HttpClientRequest.createGet("test/keepAliveTimeout"),
                                                                          null);

        Assert.assertEquals("Unexpected HTTP response code.", 200, response.getStatus().code());
        Assert.assertEquals("Unexpected Idle connection count.", 1, stats.getIdleCount());
        Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
        Assert.assertEquals("Unexpected total connection count.", 1, stats.getTotalConnectionCount());
        Assert.assertEquals("Unexpected reuse connection count.", 0, stateChangeListener.getReuseCount());

        Thread.sleep(RequestProcessor.KEEP_ALIVE_TIMEOUT_SECONDS * 1000); // Waiting for keep-alive timeout to expire.

        response = submitAndWaitForCompletion(client, HttpClientRequest.createGet("/"), null);

        Assert.assertEquals("Unexpected HTTP response code.", 200, response.getStatus().code());
        Assert.assertEquals("Unexpected Idle connection count.", 1, stats.getIdleCount());
        Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
        Assert.assertEquals("Unexpected total connection count.", 1, stats.getTotalConnectionCount());
        Assert.assertEquals("Unexpected reuse connection count.", 0, stateChangeListener.getReuseCount());
        Assert.assertEquals("Unexpected reuse connection count.", 1, stateChangeListener.getEvictionCount());
    }

    @Test
    public void testReuseWithContent() throws Exception {
        client = newHttpClient(1, 1000000, null);

        List<String> content = submitAndConsumeContent(client, HttpClientRequest.createGet("/"));
        Assert.assertEquals("Unexpected content fragments count.", 1, content.size());
        Assert.assertEquals("Unexpected content fragment.", RequestProcessor.SINGLE_ENTITY_BODY, content.get(0));

        Assert.assertEquals("Unexpected Idle connection count.", 1, stats.getIdleCount());
        Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
        Assert.assertEquals("Unexpected total connection count.", 1, stats.getTotalConnectionCount());
        Assert.assertEquals("Unexpected reuse connection count.", 0, stateChangeListener.getReuseCount());

        content = submitAndConsumeContent(client, HttpClientRequest.createGet("/"));

        Assert.assertEquals("Unexpected content fragments count.", 1, content.size());
        Assert.assertEquals("Unexpected content fragment.", RequestProcessor.SINGLE_ENTITY_BODY, content.get(0));

        Assert.assertEquals("Unexpected Idle connection count.", 1, stats.getIdleCount());
        Assert.assertEquals("Unexpected in use connection count.", 0, stats.getInUseCount());
        Assert.assertEquals("Unexpected total connection count.", 1, stats.getTotalConnectionCount());
        Assert.assertEquals("Unexpected reuse connection count.", 1, stateChangeListener.getReuseCount());
    }

    @Test
    public void testPoolEvictions() throws InterruptedException {
        client = newHttpClient(1, 1000, null);
        final CountDownLatch latch = new CountDownLatch(1);
        HttpClientMetricEventsListener listener = new HttpClientMetricEventsListener() {
            @Override
            protected void onPooledConnectionEviction() {
                latch.countDown();
            }
        };

        client.subscribe(listener);

        submitAndConsumeContent(client, HttpClientRequest.createGet("/"));
        HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/");

        client.submit(request).subscribe();
        latch.await(20, TimeUnit.SECONDS); // Wait less than default idle timeout (30 seconds)
        Assert.assertEquals("Pooled connection not evicted after idle timeout.", 0, latch.getCount());
    }


    private static List<String> submitAndConsumeContent(HttpClientImpl<ByteBuf, ByteBuf> client,
                                                        HttpClientRequest<ByteBuf> request)
            throws InterruptedException {
        final List<String> toReturn = new ArrayList<String>();
        final CountDownLatch completionLatch = new CountDownLatch(1);
        Observable<HttpClientResponse<ByteBuf>> response = client.submit(request);
        response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<String>>() {
            @Override
            public Observable<String> call(HttpClientResponse<ByteBuf> response) {
                if (response.getStatus().code() == 200) {
                    return response.getContent().map(new Func1<ByteBuf, String>() {
                        @Override
                        public String call(ByteBuf byteBuf) {
                            return byteBuf.toString(Charset.defaultCharset());
                        }
                    });
                } else {
                    return Observable.error(new AssertionError("Unexpected response code: " + response.getStatus().code()));
                }
            }
        }).finallyDo(new Action0() {
            @Override
            public void call() {
                completionLatch.countDown();
            }
        }).toBlocking().forEach(new Action1<String>() {
            @Override
            public void call(String s) {
                toReturn.add(s);
            }
        });

        completionLatch.await(1, TimeUnit.MINUTES);
        return toReturn;
    }

    private static HttpClientResponse<ByteBuf> submitAndWaitForCompletion(HttpClientImpl<ByteBuf, ByteBuf> client,
                                                                          HttpClientRequest<ByteBuf> request,
                                                                          final Action0 onComplete)
            throws InterruptedException {
        final CountDownLatch completionLatch = new CountDownLatch(1);
        Observable<HttpClientResponse<ByteBuf>> submit = client.submit(request);
        final AtomicReference<HttpClientResponse<ByteBuf>> toReturnRef =
                new AtomicReference<HttpClientResponse<ByteBuf>>();

        Observable<ByteBuf> contentStream =
                submit.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>>() {
                    @Override
                    public Observable<ByteBuf> call(HttpClientResponse<ByteBuf> response) {
                        toReturnRef.set(response);
                        return response.getContent();
                    }
                }).finallyDo(new Action0() {
                    @Override
                    public void call() {
                        completionLatch.countDown();

                    }
                }).ignoreElements();

        if (null != onComplete) {
            contentStream = contentStream.doOnCompleted(onComplete);
        }

        contentStream.toBlocking().lastOrDefault(null);

        completionLatch.await(1, TimeUnit.MINUTES);

        return toReturnRef.get();
    }

    private void waitForClose() throws InterruptedException {
        if (!channelCloseListener.waitForClose(1, TimeUnit.MINUTES)) {
            throw new AssertionError("Client channel not closed after sufficient wait.");
        }
    }

    private HttpClientImpl<ByteBuf, ByteBuf> newHttpClient(int maxConnections, long idleTimeout,
                                                           HttpClient.HttpClientConfig clientConfig) {
        if (null == clientConfig) {
            clientConfig = HttpClient.HttpClientConfig.Builder.newDefaultConfig();
        }
        PipelineConfigurator<HttpClientResponse<ByteBuf>, HttpClientRequest<ByteBuf>> configurator =
                new PipelineConfiguratorComposite<HttpClientResponse<ByteBuf>, HttpClientRequest<ByteBuf>>(
                        PipelineConfigurators.httpClientConfigurator(),
                        new PipelineConfigurator() {
                            @Override
                            public void configureNewPipeline(ChannelPipeline pipeline) {
                                channelCloseListener.reset();
                                pipeline.addFirst(channelCloseListener);
                            }
                        });

        HttpClientImpl<ByteBuf, ByteBuf> client =
                (HttpClientImpl<ByteBuf, ByteBuf>) new HttpClientBuilder<ByteBuf, ByteBuf>("localhost", port)
                        .withMaxConnections(maxConnections)
                        .withIdleConnectionsTimeoutMillis(idleTimeout)
                        .config(clientConfig)
                        .enableWireLogging(LogLevel.DEBUG)
                        .pipelineConfigurator(configurator).build();
        stateChangeListener = new TrackableMetricEventsListener();
        client.subscribe(stateChangeListener);
        stats = new PoolStats();
        client.subscribe(stats);
        return client;
    }
}
TOP

Related Classes of io.reactivex.netty.protocol.http.client.HttpClientPoolTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.