com.netflix.iep.http.RxHttpTest.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.iep.http.RxHttpTest.java

Source

/*
 * Copyright 2015 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 com.netflix.iep.http;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.config.ConfigurationManager;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.timeout.ReadTimeoutException;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import org.apache.commons.configuration.Configuration;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Actions;
import rx.functions.Func1;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicReference;

@RunWith(JUnit4.class)
public class RxHttpTest {

    private static final String client = "test";
    private static final int retries = 2;

    private static HttpServer server;
    private static int port;

    private static AtomicInteger statusCode = new AtomicInteger(200);
    private static AtomicIntegerArray statusCounts = new AtomicIntegerArray(600);

    private static AtomicInteger redirects = new AtomicInteger(0);

    private static final Configuration archaius = ConfigurationManager.getConfigInstance();
    private static final RxHttp rxHttp = new RxHttp(archaius, null);

    private static void set(String k, String v) {
        ConfigurationManager.getConfigInstance().setProperty(k, v);
    }

    private static void ignore(InputStream input) throws IOException {
        try (InputStream in = input) {
            byte[] buf = new byte[1024];
            while (in.read(buf) > 0)
                ;
        }
    }

    @BeforeClass
    public static void startServer() throws Exception {
        rxHttp.start();

        server = HttpServer.create(new InetSocketAddress(0), 100);
        server.setExecutor(Executors.newFixedThreadPool(10, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "HttpServer");
            }
        }));
        port = server.getAddress().getPort();

        server.createContext("/empty", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                ignore(exchange.getRequestBody());
                int port = exchange.getRemoteAddress().getPort();
                exchange.getResponseHeaders().add("X-Test-Port", "" + port);
                statusCounts.incrementAndGet(statusCode.get());
                exchange.sendResponseHeaders(statusCode.get(), -1L);
                exchange.close();
            }
        });

        server.createContext("/echo", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                Headers headers = exchange.getRequestHeaders();
                int contentLength = Integer.parseInt(headers.getFirst("Content-Length"));
                String contentEnc = headers.getFirst("Content-Encoding");
                if (contentEnc != null) {
                    exchange.getResponseHeaders().add("Content-Encoding", contentEnc);
                }

                int code = statusCode.get();
                if (contentLength > 512 && !"gzip".equals(contentEnc)) {
                    code = 400;
                }

                statusCounts.incrementAndGet(code);
                exchange.sendResponseHeaders(code, contentLength);
                try (InputStream input = exchange.getRequestBody();
                        OutputStream output = exchange.getResponseBody()) {
                    byte[] buf = new byte[1024];
                    int length;
                    while ((length = input.read(buf)) > 0) {
                        output.write(buf, 0, length);
                    }
                }
                exchange.close();
            }
        });

        server.createContext("/relativeRedirect", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                ignore(exchange.getRequestBody());
                if (redirects.get() <= 0) {
                    statusCounts.incrementAndGet(statusCode.get());
                    exchange.getResponseHeaders().add("Location", "/empty");
                    exchange.sendResponseHeaders(statusCode.get(), -1L);
                    exchange.close();
                } else {
                    redirects.decrementAndGet();
                    statusCounts.incrementAndGet(302);
                    exchange.getResponseHeaders().add("Location", "/relativeRedirect");
                    exchange.sendResponseHeaders(302, -1L);
                    exchange.close();
                }
            }
        });

        server.createContext("/absoluteRedirect", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                String host = "http://" + exchange.getRequestHeaders().getFirst("Host");
                ignore(exchange.getRequestBody());
                if (redirects.get() <= 0) {
                    statusCounts.incrementAndGet(302);
                    exchange.getResponseHeaders().add("Location", host + "/empty");
                    exchange.sendResponseHeaders(302, -1L);
                    exchange.close();
                } else {
                    redirects.decrementAndGet();
                    statusCounts.incrementAndGet(302);
                    exchange.getResponseHeaders().add("Location", host + "/absoluteRedirect");
                    exchange.sendResponseHeaders(302, -1L);
                    exchange.close();
                }
            }
        });

        server.createContext("/notModified", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                ignore(exchange.getRequestBody());
                statusCounts.incrementAndGet(304);
                exchange.sendResponseHeaders(304, -1L);
                exchange.close();
            }
        });

        server.createContext("/redirectNoLocation", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                ignore(exchange.getRequestBody());
                statusCounts.incrementAndGet(302);
                exchange.sendResponseHeaders(302, -1L);
                exchange.close();
            }
        });

        server.createContext("/readTimeout", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                ignore(exchange.getRequestBody());
                statusCounts.incrementAndGet(statusCode.get()); // So we can track retries
                Object lock = new Object();
                try {
                    synchronized (lock) {
                        lock.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        server.start();

        set(client + ".niws.client.MaxAutoRetriesNextServer", "" + retries);
        set(client + ".niws.client.RetryDelay", "100");
        set(client + ".niws.client.ReadTimeout", "1000");
    }

    @AfterClass
    public static void stopServer() {
        rxHttp.stop();
        server.stop(0);
    }

    @Before
    public void initProps() {
        set(client + ".niws.client.MaxAutoRetriesNextServer", "" + retries);
        set(client + ".niws.client.RetryDelay", "100");
        set(client + ".niws.client.ConnectTimeout", "1000");
        //set(client + ".niws.client.ReadTimeout", "30000");
    }

    private URI uri(String path) {
        return URI.create("niws://test/http://localhost:" + port + path);
    }

    private AtomicIntegerArray copy(AtomicIntegerArray src) {
        AtomicIntegerArray dst = new AtomicIntegerArray(src.length());
        for (int i = 0; i < src.length(); ++i) {
            dst.set(i, src.get(i));
        }
        return dst;
    }

    private void assertEquals(AtomicIntegerArray expected, AtomicIntegerArray actual) {
        for (int i = 0; i < expected.length(); ++i) {
            final String prefix = "count(" + i + ")=";
            Assert.assertEquals(prefix + expected.get(i), prefix + actual.get(i));
        }
    }

    private void codeTest(int code, int attempts) throws Exception {
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, attempts);

        rxHttp.get(uri("/empty")).toBlocking().toFuture().get();

        assertEquals(expected, statusCounts);
    }

    @Test
    public void ok() throws Exception {
        codeTest(200, 1);
    }

    @Test
    public void clientError() throws Exception {
        codeTest(400, 1);
    }

    @Test
    public void notFound() throws Exception {
        codeTest(404, 1);
    }

    @Test
    public void serverError() throws Exception {
        codeTest(500, retries + 1);
    }

    @Test
    public void server502() throws Exception {
        codeTest(502, retries + 1);
    }

    @Test
    public void throttle429() throws Exception {
        codeTest(429, retries + 1);
    }

    @Test
    public void throttle503() throws Exception {
        codeTest(503, retries + 1);
    }

    @Test
    public void throttle503NoDelay() throws Exception {
        set(client + ".niws.client.RetryDelay", "0");
        codeTest(503, retries + 1);
    }

    @Test
    public void relativeRedirect() throws Exception {
        int code = 200;
        statusCode.set(code);
        redirects.set(2);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(302, 2);
        expected.addAndGet(code, 1);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get(uri("/relativeRedirect")).subscribe(Actions.empty(), new Action1<Throwable>() {
            @Override
            public void call(Throwable t) {
                latch.countDown();
                throwable.set(t);
            }
        }, new Action0() {
            @Override
            public void call() {
                latch.countDown();
            }
        });

        latch.await();
        assertEquals(expected, statusCounts);
    }

    @Test
    public void relativeRedirectAndRetries() throws Exception {
        set(client + ".niws.client.MaxAutoRetriesNextServer", "0");
        relativeRedirect();
    }

    @Test
    public void absoluteRedirect() throws Exception {
        int code = 200;
        statusCode.set(code);
        redirects.set(2);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.incrementAndGet(code);
        expected.addAndGet(302, 3);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get(uri("/absoluteRedirect")).subscribe(Actions.empty(), new Action1<Throwable>() {
            @Override
            public void call(Throwable t) {
                throwable.set(t);
                latch.countDown();
            }
        }, new Action0() {
            @Override
            public void call() {
                latch.countDown();
            }
        });

        latch.await();
        Assert.assertNull(throwable.get());
        assertEquals(expected, statusCounts);
    }

    @Test
    public void absoluteRedirectAndRetries() throws Exception {
        set(client + ".niws.client.MaxAutoRetriesNextServer", "0");
        absoluteRedirect();
    }

    @Test
    public void notModified() throws Exception {
        int code = 304;
        statusCode.set(code);
        redirects.set(2);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get(uri("/notModified")).subscribe(Actions.empty(), new Action1<Throwable>() {
            @Override
            public void call(Throwable t) {
                latch.countDown();
                throwable.set(t);
            }
        }, new Action0() {
            @Override
            public void call() {
                latch.countDown();
            }
        });

        latch.await();
        assertEquals(expected, statusCounts);
        Assert.assertNull(throwable.get());
    }

    @Test
    public void redirectResponseMissingLocation() throws Exception {
        int code = 302;
        statusCode.set(code);
        redirects.set(2);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get(uri("/redirectNoLocation")).subscribe(Actions.empty(), new Action1<Throwable>() {
            @Override
            public void call(Throwable t) {
                latch.countDown();
                throwable.set(t);
            }
        }, new Action0() {
            @Override
            public void call() {
                latch.countDown();
            }
        });

        latch.await();
        assertEquals(expected, statusCounts);
        Assert.assertNotNull(throwable.get());
    }

    @Test
    public void readTimeout() throws Exception {
        //set(client + ".niws.client.ReadTimeout", "100");
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 3);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get(uri("/readTimeout")).subscribe(Actions.empty(), new Action1<Throwable>() {
            @Override
            public void call(Throwable t) {
                throwable.set(t);
                latch.countDown();
            }
        }, new Action0() {
            @Override
            public void call() {
                latch.countDown();
            }
        });

        latch.await();
        Assert.assertTrue(throwable.get() instanceof ReadTimeoutException);
        assertEquals(expected, statusCounts);
    }

    @Test
    public void readTimeoutDoesntRetry() throws Exception {
        //set(client + ".niws.client.ReadTimeout", "100");
        set(client + ".niws.client.RetryReadTimeouts", "false");
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get(uri("/readTimeout")).subscribe(Actions.empty(), new Action1<Throwable>() {
            @Override
            public void call(Throwable t) {
                throwable.set(t);
                latch.countDown();
            }
        }, new Action0() {
            @Override
            public void call() {
                latch.countDown();
            }
        });

        latch.await();
        Assert.assertTrue(throwable.get() instanceof ReadTimeoutException);
        assertEquals(expected, statusCounts);
    }

    @Test
    public void connectTimeout() throws Exception {
        // Pick a free port with no server running
        ServerSocket ss = new ServerSocket(0);
        int serverPort = ss.getLocalPort();
        ss.close();

        set(client + ".niws.client.ConnectTimeout", "100");
        int code = 200;
        statusCode.set(code);

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<Throwable> throwable = new AtomicReference<>();
        rxHttp.get("niws://test/http://localhost:" + serverPort + "/empty").subscribe(Actions.empty(),
                new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        throwable.set(t);
                        latch.countDown();
                    }
                }, new Action0() {
                    @Override
                    public void call() {
                        latch.countDown();
                    }
                });

        latch.await();
        Assert.assertTrue(throwable.get() instanceof ConnectException);
    }

    @Test
    public void simplePost() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);

        final StringBuilder builder = new StringBuilder();
        rxHttp.post(uri("/echo"), "text/plain", "foo bar".getBytes())
                .flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>>() {
                    @Override
                    public Observable<ByteBuf> call(HttpClientResponse<ByteBuf> res) {
                        Assert.assertEquals(200, res.getStatus().code());
                        return res.getContent();
                    }
                }).toBlocking().forEach(new Action1<ByteBuf>() {
                    @Override
                    public void call(ByteBuf byteBuf) {
                        builder.append(byteBuf.toString(Charset.defaultCharset()));
                    }
                });

        Assert.assertEquals("foo bar", builder.toString());

        assertEquals(expected, statusCounts);
    }

    @Test
    public void gzipPost() throws Exception {
        //set(client + ".niws.client.ReadTimeout", "1000");
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);

        StringBuilder content = new StringBuilder();
        for (int i = 0; i < 500; ++i) {
            content.append(i).append(", ");
        }
        String body = content.toString();

        final StringBuilder builder = new StringBuilder();
        rxHttp.post(uri("/echo"), "text/plain", body.getBytes())
                .flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>>() {
                    @Override
                    public Observable<ByteBuf> call(HttpClientResponse<ByteBuf> res) {
                        Assert.assertEquals(200, res.getStatus().code());
                        return res.getContent();
                    }
                }).toBlocking().forEach(new Action1<ByteBuf>() {
                    @Override
                    public void call(ByteBuf byteBuf) {
                        builder.append(byteBuf.toString(Charset.defaultCharset()));
                    }
                });

        Assert.assertEquals(body, builder.toString());

        assertEquals(expected, statusCounts);
    }

    @Test
    public void postJsonString() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);
        rxHttp.postJson(uri("/empty"), "{}").toBlocking().toFuture().get();
        assertEquals(expected, statusCounts);
    }

    @Test
    public void postForm() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);

        final StringBuilder builder = new StringBuilder();
        rxHttp.postForm(uri("/echo?foo=bar&name=John+Doe&pct=%2042%25"))
                .flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>>() {
                    @Override
                    public Observable<ByteBuf> call(HttpClientResponse<ByteBuf> res) {
                        Assert.assertEquals(200, res.getStatus().code());
                        return res.getContent();
                    }
                }).toBlocking().forEach(new Action1<ByteBuf>() {
                    @Override
                    public void call(ByteBuf byteBuf) {
                        builder.append(byteBuf.toString(Charset.defaultCharset()));
                    }
                });

        assertEquals(expected, statusCounts);
        Assert.assertEquals("foo=bar&name=John+Doe&pct=%2042%25", builder.toString());
    }

    @Test
    public void putJsonString() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);
        rxHttp.putJson(uri("/empty"), "{}").toBlocking().toFuture().get();
        assertEquals(expected, statusCounts);
    }

    @Test
    public void delete() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);
        rxHttp.delete(uri("/empty").toString()).toBlocking().toFuture().get();
        assertEquals(expected, statusCounts);
    }

    @Test
    public void head() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);
        rxHttp.submit(HttpClientRequest.<ByteBuf>create(HttpMethod.HEAD, uri("/empty").toString())).toBlocking()
                .toFuture().get();
        assertEquals(expected, statusCounts);
    }

    @Test
    public void postWithCustomHeader() throws Exception {
        int code = 200;
        statusCode.set(code);
        AtomicIntegerArray expected = copy(statusCounts);
        expected.addAndGet(code, 1);
        rxHttp.submit(HttpClientRequest.createPost(uri("/empty").toString()).withHeader("k", "v"), "{}")
                .toBlocking().toFuture().get();
        assertEquals(expected, statusCounts);
    }

    @Test
    public void portOverrideSetting() throws Exception {
        set("port-override.niws.client.Port", "2");
        URI origUri = URI.create("niws://port-override/foo");
        URI relUri = URI.create("/foo");
        ClientConfig cfg = new ClientConfig(archaius, "port-override", "vip", origUri, relUri);
        InstanceInfo info = InstanceInfo.Builder.newBuilder().setAppName("foo").setPort(1).build();
        Server server = EurekaServerRegistry.toServer(cfg, info);
        Assert.assertEquals(server.port(), 2);
    }

    @Test
    public void portDefaultSetting() throws Exception {
        URI origUri = URI.create("niws://port-default/foo");
        URI relUri = URI.create("/foo");
        ClientConfig cfg = new ClientConfig(archaius, "port-default", "vip", origUri, relUri);
        InstanceInfo info = InstanceInfo.Builder.newBuilder().setAppName("foo").setPort(1).build();
        Server server = EurekaServerRegistry.toServer(cfg, info);
        Assert.assertEquals(server.port(), 1);
    }

    private int getPortForReq() throws Exception {
        HttpClientResponse<ByteBuf> r = rxHttp.get(uri("/empty")).toBlocking().toFuture().get();
        return Integer.parseInt(r.getHeaders().get("X-Test-Port"));
    }

    @Test
    public void connectionReuse() throws Exception {
        Map<Integer, Integer> counts = new HashMap<>();
        for (int i = 0; i < 10; ++i) {
            int p = getPortForReq();
            Integer c = counts.get(p);
            if (c == null) {
                counts.put(p, 1);
            } else {
                counts.put(p, c + 1);
            }
        }
        Assert.assertTrue("connections not getting reused", counts.size() < 4);
    }
}