reactor.ipc.netty.http.client.HttpClientTest.java Source code

Java tutorial

Introduction

Here is the source code for reactor.ipc.netty.http.client.HttpClientTest.java

Source

/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * 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 reactor.ipc.netty.http.client;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.CertificateException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLException;

import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import reactor.core.publisher.DirectProcessor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.FutureMono;
import reactor.ipc.netty.NettyContext;
import reactor.ipc.netty.NettyPipeline;
import reactor.ipc.netty.channel.AbortedException;
import reactor.ipc.netty.http.server.HttpServer;
import reactor.ipc.netty.options.ClientProxyOptions;
import reactor.ipc.netty.options.ClientProxyOptions.Proxy;
import reactor.ipc.netty.resources.PoolResources;
import reactor.ipc.netty.tcp.TcpServer;
import reactor.test.StepVerifier;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * @author Stephane Maldini
 * @since 0.6
 */
public class HttpClientTest {

    @Test
    public void abort() throws Exception {
        NettyContext x = TcpServer.create("localhost", 0).newHandler((in, out) -> in.receive().take(1)
                .thenMany(Flux.defer(() -> out.context(c -> c.addHandlerFirst(new HttpResponseEncoder()))
                        .sendObject(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.ACCEPTED))
                        .then(Mono.delay(Duration.ofSeconds(2)).then()))))
                .block(Duration.ofSeconds(30));

        PoolResources pool = PoolResources.fixed("test", 1);

        int res = HttpClient.create(opts -> opts.host("localhost").port(x.address().getPort()).poolResources(pool))
                .get("/").flatMap(r -> Mono.just(r.status().code())).log().block(Duration.ofSeconds(30));

        HttpClient.create(opts -> opts.host("localhost").port(x.address().getPort()).poolResources(pool)).get("/")
                .log().block(Duration.ofSeconds(30));

        HttpClient.create(opts -> opts.host("localhost").port(x.address().getPort()).poolResources(pool)).get("/")
                .log().block(Duration.ofSeconds(30));

    }

    DefaultFullHttpResponse response() {
        DefaultFullHttpResponse r = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.ACCEPTED);
        r.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
        return r;
    }

    @Test
    @Ignore
    public void pipelined() throws Exception {
        NettyContext x = TcpServer.create("localhost", 0)
                .newHandler((in, out) -> out.context(c -> c.addHandlerFirst(new HttpResponseEncoder()))
                        .sendObject(Flux.just(response(), response())).neverComplete())
                .block(Duration.ofSeconds(30));

        PoolResources pool = PoolResources.fixed("test", 1);

        int res = HttpClient.create(opts -> opts.host("localhost").port(x.address().getPort()).poolResources(pool))
                .get("/").flatMap(r -> Mono.just(r.status().code())).log().block(Duration.ofSeconds(30));

        try {
            HttpClient.create(opts -> opts.host("localhost").port(x.address().getPort()).poolResources(pool))
                    .get("/").log().block(Duration.ofSeconds(30));
        } catch (AbortedException ae) {
            return;
        }

        Assert.fail("Not aborted");
    }

    @Test
    public void backpressured() throws Exception {

        NettyContext c = HttpServer.create(0)
                .newRouter(
                        routes -> routes.directory("/test", Paths.get(getClass().getResource("/public").getFile())))
                .block(Duration.ofSeconds(30));

        Mono<HttpClientResponse> remote = HttpClient.create(c.address().getPort()).get("/test/test.css");

        Mono<String> page = remote.flatMapMany(r -> r.receive().asString().limitRate(1)).reduce(String::concat);

        Mono<String> cancelledPage = remote.flatMapMany(r -> r.receive().asString().take(5).limitRate(1))
                .reduce(String::concat);

        page.block(Duration.ofSeconds(30));
        cancelledPage.block(Duration.ofSeconds(30));
        page.block(Duration.ofSeconds(30));
        c.dispose();
    }

    @Test
    public void serverInfiniteClientClose() throws Exception {

        CountDownLatch latch = new CountDownLatch(1);
        NettyContext c = HttpServer.create(0).newHandler((req, resp) -> {
            req.context().onClose(latch::countDown);

            return Flux.interval(Duration.ofSeconds(1)).flatMap(d -> {
                req.context().channel().config().setAutoRead(true);

                return resp.sendObject(Unpooled.EMPTY_BUFFER).then()
                        .doOnSuccess(x -> req.context().channel().config().setAutoRead(false));
            });
        }).block(Duration.ofSeconds(30));

        Mono<HttpClientResponse> remote = HttpClient.create(c.address().getPort()).get("/");

        HttpClientResponse r = remote.block();
        r.dispose();
        while (r.channel().isActive()) {
        }
        latch.await();
        c.dispose();
    }

    @Test
    @Ignore
    public void proxy() throws Exception {
        Mono<HttpClientResponse> remote = HttpClient
                .create(o -> o.proxyOptions(
                        ClientProxyOptions.builder().type(Proxy.HTTP).host("127.0.0.1").port(8888).build()))
                .get("https://projectreactor.io", c -> c.followRedirect().sendHeaders());

        Mono<String> page = remote.flatMapMany(r -> r.receive().retain().asString().limitRate(1))
                .reduce(String::concat);

        page.block(Duration.ofSeconds(30));
    }

    //@Test
    public void postUpload() throws Exception {
        InputStream f = getClass().getResourceAsStream("/public/index.html");
        //Path f = Paths.get("/Users/smaldini/Downloads/IMG_6702.mp4");
        int res = HttpClient.create("google.com").put("/post",
                c -> c.sendForm(form -> form.multipart(true).file("test", f).attr("att1", "attr2").file("test2", f))
                        .log().then())
                .flatMap(r -> Mono.just(r.status().code())).block(Duration.ofSeconds(30));
        res = HttpClient.create("google.com").get("/search", c -> c.followRedirect().sendHeaders())
                .flatMap(r -> Mono.just(r.status().code())).log().block(Duration.ofSeconds(30));

        if (res != 200) {
            throw new IllegalStateException("test status failed with " + res);
        }
    }

    @Test
    public void simpleTest404() {
        int res = HttpClient.create("google.com").get("/unsupportedURI", c -> c.followRedirect().sendHeaders())
                .flatMap(r -> Mono.just(r.status().code())).log()
                .onErrorResume(HttpClientException.class, e -> Mono.just(e.status().code()))
                .block(Duration.ofSeconds(30));

        if (res != 404) {
            throw new IllegalStateException("test status failed with " + res);
        }
    }

    @Test
    public void disableChunkForced() throws Exception {
        HttpClientResponse r = HttpClient.create("google.com")
                .get("/unsupportedURI",
                        c -> c.chunkedTransfer(false).failOnClientError(false).sendString(Flux.just("hello")))
                .block(Duration.ofSeconds(30));

        FutureMono.from(r.context().channel().closeFuture()).block(Duration.ofSeconds(5));

        Assert.assertTrue(r.status() == HttpResponseStatus.NOT_FOUND);
    }

    @Test
    public void disableChunkForced2() throws Exception {
        HttpClientResponse r = HttpClient.create("google.com")
                .get("/unsupportedURI", c -> c.chunkedTransfer(false).failOnClientError(false).keepAlive(false))
                .block(Duration.ofSeconds(30));

        FutureMono.from(r.context().channel().closeFuture()).block(Duration.ofSeconds(5));

        Assert.assertTrue(r.status() == HttpResponseStatus.NOT_FOUND);
    }

    @Test
    public void disableChunkImplicit() throws Exception {
        PoolResources p = PoolResources.fixed("test", 1);

        HttpClientResponse r = HttpClient.create(opts -> opts.poolResources(p))
                .get("http://google.com/unsupportedURI", c -> c.failOnClientError(false).sendHeaders())
                .block(Duration.ofSeconds(30));

        HttpClientResponse r2 = HttpClient.create(opts -> opts.poolResources(p))
                .get("http://google.com/unsupportedURI", c -> c.failOnClientError(false).sendHeaders())
                .block(Duration.ofSeconds(30));
        Assert.assertTrue(r.context().channel() == r2.context().channel());

        Assert.assertTrue(r.status() == HttpResponseStatus.NOT_FOUND);
    }

    @Test
    public void disableChunkImplicitDefault() throws Exception {
        HttpClientResponse r = HttpClient.create("google.com")
                .get("/unsupportedURI", c -> c.chunkedTransfer(false).failOnClientError(false))
                .block(Duration.ofSeconds(30));

        FutureMono.from(r.context().channel().closeFuture()).block(Duration.ofSeconds(5));

        Assert.assertTrue(r.status() == HttpResponseStatus.NOT_FOUND);
    }

    @Test
    public void contentHeader() throws Exception {
        PoolResources fixed = PoolResources.fixed("test", 1);
        HttpClientResponse r = HttpClient.create(opts -> opts.poolResources(fixed))
                .get("http://google.com",
                        c -> c.header("content-length", "1").failOnClientError(false).sendString(Mono.just(" ")))
                .block(Duration.ofSeconds(30));

        HttpClient.create(opts -> opts.poolResources(fixed))
                .get("http://google.com",
                        c -> c.header("content-length", "1").failOnClientError(false).sendString(Mono.just(" ")))
                .block(Duration.ofSeconds(30));

        Assert.assertTrue(r.status() == HttpResponseStatus.BAD_REQUEST);
    }

    @Test
    public void simpleTestHttps() {

        StepVerifier
                .create(HttpClient.create().get("https://developer.chrome.com")
                        .flatMap(r -> Mono.just(r.status().code())))
                .expectNextMatches(status -> status >= 200 && status < 400).expectComplete().verify();

        StepVerifier
                .create(HttpClient.create().get("https://developer.chrome.com")
                        .flatMap(r -> Mono.just(r.status().code())))
                .expectNextMatches(status -> status >= 200 && status < 400).expectComplete().verify();
    }

    @Test
    public void prematureCancel() throws Exception {
        DirectProcessor<Void> signal = DirectProcessor.create();
        NettyContext x = TcpServer.create("localhost", 0).newHandler((in, out) -> {
            signal.onComplete();
            return out.context(c -> c.addHandlerFirst(new HttpResponseEncoder()))
                    .sendObject(Mono.delay(Duration.ofSeconds(2)).map(
                            t -> new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.PROCESSING)))
                    .neverComplete();
        }).block(Duration.ofSeconds(30));

        StepVerifier.create(
                HttpClient.create(x.address().getHostName(), x.address().getPort()).get("/").timeout(signal))
                .verifyError(TimeoutException.class);
        //      Thread.sleep(1000000);
    }

    @Test
    public void gzip() {
        //verify gzip is negotiated (when no decoder)
        StepVerifier
                .create(HttpClient.create().get("http://www.httpwatch.com",
                        req -> req.addHeader("Accept-Encoding", "gzip").addHeader("Accept-Encoding", "deflate"))
                        .flatMap(r -> r.receive().asString().elementAt(0).map(s -> s.substring(0, 100))
                                .and(Mono.just(r.responseHeaders().get("Content-Encoding", "")))))
                .expectNextMatches(tuple -> !tuple.getT1().contains("<html>") && !tuple.getT1().contains("<head>")
                        && "gzip".equals(tuple.getT2()))
                .expectComplete().verify();

        //verify decoder does its job and removes the header
        StepVerifier.create(HttpClient.create().get("http://www.httpwatch.com", req -> {
            req.context().addHandlerFirst("gzipDecompressor", new HttpContentDecompressor());
            return req.addHeader("Accept-Encoding", "gzip").addHeader("Accept-Encoding", "deflate");
        }).flatMap(r -> r.receive().asString().elementAt(0).map(s -> s.substring(0, 100))
                .and(Mono.just(r.responseHeaders().get("Content-Encoding", "")))))
                .expectNextMatches(tuple -> tuple.getT1().contains("<html>") && tuple.getT1().contains("<head>")
                        && "".equals(tuple.getT2()))
                .expectComplete().verify();
    }

    @Test
    public void testUserAgent() {
        NettyContext c = HttpServer.create(0).newHandler((req, resp) -> {
            Assert.assertTrue("" + req.requestHeaders().get(HttpHeaderNames.USER_AGENT),
                    req.requestHeaders().contains(HttpHeaderNames.USER_AGENT)
                            && req.requestHeaders().get(HttpHeaderNames.USER_AGENT).equals(HttpClient.USER_AGENT));

            return resp;
        }).block();

        HttpClient.create(c.address().getPort()).get("/").block();

        c.dispose();
    }

    @Test
    public void toStringShowsOptions() {
        HttpClient client = HttpClient.create(opt -> opt.host("foo").port(123).compression(true));

        assertThat(client.toString()).isEqualTo("HttpClient: connecting to foo:123 with gzip");
    }

    @Test
    public void gettingOptionsDuplicates() {
        HttpClient client = HttpClient.create(opt -> opt.host("foo").port(123).compression(true));
        assertThat(client.options()).isNotSameAs(client.options).isNotSameAs(client.options());
    }

    @Test
    public void sshExchangeRelativeGet() throws CertificateException, SSLException {
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        SslContext sslServer = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        SslContext sslClient = SslContextBuilder.forClient()
                //make the client to trust the self signed certificate
                .trustManager(ssc.cert()).build();

        NettyContext context = HttpServer.create(opt -> opt.sslContext(sslServer))
                .newHandler((req, resp) -> resp.sendString(Flux.just("hello ", req.uri()))).block();

        HttpClientResponse response = HttpClient
                .create(opt -> opt.port(context.address().getPort()).sslContext(sslClient)).get("/foo")
                .block(Duration.ofMillis(200));
        context.dispose();
        context.onClose().block();

        String responseString = response.receive().aggregate().asString(CharsetUtil.UTF_8).block();
        assertThat(responseString).isEqualTo("hello /foo");
    }

    @Test
    public void sshExchangeAbsoluteGet() throws CertificateException, SSLException {
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        SslContext sslServer = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        SslContext sslClient = SslContextBuilder.forClient().trustManager(ssc.cert()).build();

        NettyContext context = HttpServer.create(opt -> opt.sslContext(sslServer))
                .newHandler((req, resp) -> resp.sendString(Flux.just("hello ", req.uri()))).block();

        HttpClientResponse response = HttpClient
                .create(opt -> opt.port(context.address().getPort()).sslContext(sslClient))
                .get("https://localhost:" + context.address().getPort() + "/foo").block(Duration.ofMillis(200));
        context.dispose();
        context.onClose().block();

        String responseString = response.receive().aggregate().asString(CharsetUtil.UTF_8).block();
        assertThat(responseString).isEqualTo("hello /foo");
    }

    @Test
    public void secureSendFile() throws CertificateException, SSLException, InterruptedException {
        Path largeFile = Paths.get(getClass().getResource("/largeFile.txt").getFile());
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        SslContext sslServer = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        SslContext sslClient = SslContextBuilder.forClient().trustManager(ssc.cert()).build();
        AtomicReference<String> uploaded = new AtomicReference<>();

        NettyContext context = HttpServer.create(opt -> opt.sslContext(sslServer))
                .newRouter(
                        r -> r.post("/upload",
                                (req, resp) -> req.receive().aggregate().asString().doOnNext(uploaded::set)
                                        .then(resp.status(201).sendString(Mono.just("Received File")).then())))
                .block();

        HttpClientResponse response = HttpClient
                .create(opt -> opt.port(context.address().getPort()).sslContext(sslClient))
                .post("/upload", r -> r.sendFile(largeFile)).block(Duration.ofSeconds(120));

        context.dispose();
        context.onClose().block();

        String responseBody = response.receive().aggregate().asString().block();
        assertThat(response.status().code()).isEqualTo(201);
        assertThat(responseBody).isEqualTo("Received File");

        assertThat(uploaded.get())
                .startsWith(
                        "This is an UTF-8 file that is larger than 1024 bytes.\n" + "It contains accents like .")
                .contains("1024 mark here -><- 1024 mark here").endsWith("End of File");
    }

    @Test
    public void chunkedSendFile() throws InterruptedException, IOException {
        Path largeFile = Paths.get(getClass().getResource("/largeFile.txt").getFile());
        AtomicReference<String> uploaded = new AtomicReference<>();

        NettyContext context = HttpServer.create(opt -> opt.host("localhost"))
                .newRouter(
                        r -> r.post("/upload",
                                (req, resp) -> req.receive().aggregate().asString().doOnNext(uploaded::set)
                                        .then(resp.status(201).sendString(Mono.just("Received File")).then())))
                .block();

        HttpClientResponse response = HttpClient.create(opt -> opt.port(context.address().getPort()))
                .post("/upload", r -> r.sendFile(largeFile)).block(Duration.ofSeconds(120));

        context.dispose();
        context.onClose().block();

        String responseBody = response.receive().aggregate().asString().block();
        assertThat(response.status().code()).isEqualTo(201);
        assertThat(responseBody).isEqualTo("Received File");

        assertThat(uploaded.get())
                .startsWith(
                        "This is an UTF-8 file that is larger than 1024 bytes.\n" + "It contains accents like .")
                .contains("1024 mark here -><- 1024 mark here").endsWith("End of File");
    }
}