Java tutorial
/* * 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.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelOption; import io.netty.channel.ConnectTimeoutException; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.logging.LogLevel; import io.netty.handler.timeout.ReadTimeoutException; import io.reactivex.netty.RxNetty; import io.reactivex.netty.channel.ObservableConnection; import io.reactivex.netty.channel.StringTransformer; import io.reactivex.netty.client.RxClient; import io.reactivex.netty.client.RxClient.ClientConfig.Builder; import io.reactivex.netty.pipeline.PipelineConfigurator; import io.reactivex.netty.pipeline.PipelineConfigurators; import io.reactivex.netty.protocol.http.server.HttpServer; import io.reactivex.netty.protocol.http.server.HttpServerBuilder; import io.reactivex.netty.protocol.text.sse.ServerSentEvent; import io.reactivex.netty.server.RxServerThreadFactory; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import rx.Observable; import rx.Observer; import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Func1; import java.net.ConnectException; 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; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class HttpClientTest { private static HttpServer<ByteBuf, ByteBuf> server; private static int port; @BeforeClass public static void init() { HttpServerBuilder<ByteBuf, ByteBuf> builder = new HttpServerBuilder<ByteBuf, ByteBuf>( new ServerBootstrap().group(new NioEventLoopGroup(10, new RxServerThreadFactory())), port, new RequestProcessor()); server = builder.enableWireLogging(LogLevel.ERROR).build(); server.start(); port = server.getServerPort(); // Using ephemeral ports System.out.println("Mock server using ephemeral port; " + port); } @AfterClass public static void shutDown() throws InterruptedException { server.shutdown(); } @Test public void testConnectionClose() throws Exception { HttpClientImpl<ByteBuf, ByteBuf> client = (HttpClientImpl<ByteBuf, ByteBuf>) RxNetty .createHttpClient("localhost", port); Observable<ObservableConnection<HttpClientResponse<ByteBuf>, HttpClientRequest<ByteBuf>>> connectionObservable = client .connect().cache(); final Observable<HttpClientResponse<ByteBuf>> response = client .submit(HttpClientRequest.createGet("test/singleEntity"), connectionObservable); ObservableConnection<HttpClientResponse<ByteBuf>, HttpClientRequest<ByteBuf>> conn = connectionObservable .toBlocking().last(); Assert.assertFalse("Connection already closed.", conn.isCloseIssued()); final CountDownLatch responseCompleteLatch = new CountDownLatch(1); response.finallyDo(new Action0() { @Override public void call() { responseCompleteLatch.countDown(); } }).subscribe(); responseCompleteLatch.await(1, TimeUnit.MINUTES); assertTrue("Connection not closed after response recieved.", conn.isCloseIssued()); } @Test public void testChunkedStreaming() throws Exception { HttpClient<ByteBuf, ServerSentEvent> client = RxNetty.createHttpClient("localhost", port, PipelineConfigurators.<ByteBuf>sseClientConfigurator()); Observable<HttpClientResponse<ServerSentEvent>> response = client .submit(HttpClientRequest.createGet("test/stream")); final List<String> result = new ArrayList<String>(); readResponseContent(response, result); assertEquals(RequestProcessor.smallStreamContent, result); } @Test public void testMultipleChunks() throws Exception { HttpClient<ByteBuf, ServerSentEvent> client = RxNetty.createHttpClient("localhost", port, PipelineConfigurators.<ByteBuf>sseClientConfigurator()); Observable<HttpClientResponse<ServerSentEvent>> response = client .submit(HttpClientRequest.createDelete("test/largeStream")); final List<String> result = new ArrayList<String>(); readResponseContent(response, result); assertEquals(RequestProcessor.largeStreamContent, result); } @Test public void testMultipleChunksWithTransformation() throws Exception { HttpClient<ByteBuf, ServerSentEvent> client = RxNetty.createHttpClient("localhost", port, PipelineConfigurators.<ByteBuf>sseClientConfigurator()); Observable<HttpClientResponse<ServerSentEvent>> response = client .submit(HttpClientRequest.createGet("test/largeStream")); Observable<String> transformed = response .flatMap(new Func1<HttpClientResponse<ServerSentEvent>, Observable<String>>() { @Override public Observable<String> call(HttpClientResponse<ServerSentEvent> httpResponse) { if (httpResponse.getStatus().equals(HttpResponseStatus.OK)) { return httpResponse.getContent().map(new Func1<ServerSentEvent, String>() { @Override public String call(ServerSentEvent sseEvent) { return sseEvent.getEventData(); } }); } return Observable.error(new RuntimeException("Unexpected response")); } }); final List<String> result = new ArrayList<String>(); transformed.toBlocking().forEach(new Action1<String>() { @Override public void call(String t1) { result.add(t1); } }); assertEquals(RequestProcessor.largeStreamContent, result); } @Test public void testSingleEntity() throws Exception { HttpClient<ByteBuf, ByteBuf> client = RxNetty.<ByteBuf, ByteBuf>newHttpClientBuilder("localhost", port) .enableWireLogging(LogLevel.ERROR).build(); Observable<HttpClientResponse<ByteBuf>> response = client .submit(HttpClientRequest.createGet("test/singleEntity")); final List<String> result = new ArrayList<String>(); response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<String>>() { @Override public Observable<String> call(HttpClientResponse<ByteBuf> response) { return response.getContent().map(new Func1<ByteBuf, String>() { @Override public String call(ByteBuf byteBuf) { return byteBuf.toString(Charset.defaultCharset()); } }); } }).toBlocking().forEach(new Action1<String>() { @Override public void call(String t1) { result.add(t1); } }); assertEquals("Response not found.", 1, result.size()); assertEquals("Hello world", result.get(0)); } @Test public void testPost() throws Exception { HttpClient<ByteBuf, ByteBuf> client = RxNetty.createHttpClient("localhost", port); HttpClientRequest<ByteBuf> request = HttpClientRequest.createPost("test/post").withContent("Hello world"); Observable<HttpClientResponse<ByteBuf>> response = client.submit(request); final List<String> result = new ArrayList<String>(); response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<String>>() { @Override public Observable<String> call(HttpClientResponse<ByteBuf> response) { return response.getContent().map(new Func1<ByteBuf, String>() { @Override public String call(ByteBuf byteBuf) { return byteBuf.toString(Charset.defaultCharset()); } }); } }).toBlocking().forEach(new Action1<String>() { @Override public void call(String t1) { result.add(t1); } }); assertEquals(1, result.size()); assertEquals("Hello world", result.get(0)); // resend the same request to make sure it is repeatable response = client.submit(request); result.clear(); response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<String>>() { @Override public Observable<String> call(HttpClientResponse<ByteBuf> response) { return response.getContent().map(new Func1<ByteBuf, String>() { @Override public String call(ByteBuf byteBuf) { return byteBuf.toString(Charset.defaultCharset()); } }); } }).toBlocking().forEach(new Action1<String>() { @Override public void call(String t1) { result.add(t1); } }); assertEquals(1, result.size()); assertEquals("Hello world", result.get(0)); } @Test public void testPostWithRawContentSource() { PipelineConfigurator<HttpClientResponse<ByteBuf>, HttpClientRequest<String>> pipelineConfigurator = PipelineConfigurators .httpClientConfigurator(); HttpClient<String, ByteBuf> client = RxNetty.createHttpClient("localhost", port, pipelineConfigurator); HttpClientRequest<String> request = HttpClientRequest.create(HttpMethod.POST, "test/post"); request.withRawContentSource(Observable.just("Hello world"), StringTransformer.DEFAULT_INSTANCE); Observable<HttpClientResponse<ByteBuf>> response = client.submit(request); String result = response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<String>>() { @Override public Observable<String> call(HttpClientResponse<ByteBuf> response) { return response.getContent().map(new Func1<ByteBuf, String>() { @Override public String call(ByteBuf byteBuf) { return byteBuf.toString(Charset.defaultCharset()); } }); } }).toBlocking().single(); assertEquals("Hello world", result); } @Test public void testNonChunkingStream() throws Exception { HttpClient<ByteBuf, ServerSentEvent> client = RxNetty.createHttpClient("localhost", port, PipelineConfigurators.<ByteBuf>sseClientConfigurator()); Observable<HttpClientResponse<ServerSentEvent>> response = client .submit(HttpClientRequest.createGet("test/nochunk_stream")); final List<String> result = new ArrayList<String>(); response.flatMap(new Func1<HttpClientResponse<ServerSentEvent>, Observable<ServerSentEvent>>() { @Override public Observable<ServerSentEvent> call(HttpClientResponse<ServerSentEvent> httpResponse) { return httpResponse.getContent(); } }).toBlocking().forEach(new Action1<ServerSentEvent>() { @Override public void call(ServerSentEvent event) { result.add(event.getEventData()); } }); assertEquals(RequestProcessor.smallStreamContent, result); } @Test public void testConnectException() throws Exception { HttpClientBuilder<ByteBuf, ByteBuf> clientBuilder = new HttpClientBuilder<ByteBuf, ByteBuf>("localhost", 8182); HttpClient<ByteBuf, ByteBuf> client = clientBuilder.channelOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 100) .build(); Observable<HttpClientResponse<ByteBuf>> response = client.submit(HttpClientRequest.createGet("/")); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> ex = new AtomicReference<Throwable>(); response.subscribe(new Observer<HttpClientResponse<ByteBuf>>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { ex.set(e); latch.countDown(); } @Override public void onNext(HttpClientResponse<ByteBuf> args) { } }); latch.await(); assertNotNull(ex.get()); assertTrue(ex.get() instanceof ConnectException); } @Test public void testConnectException2() throws Exception { HttpClientBuilder<ByteBuf, ByteBuf> clientBuilder = new HttpClientBuilder<ByteBuf, ByteBuf>( "www.google.com", 81); HttpClient<ByteBuf, ByteBuf> client = clientBuilder.channelOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10) .build(); Observable<HttpClientResponse<ByteBuf>> response = client.submit(HttpClientRequest.createGet("/")); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> ex = new AtomicReference<Throwable>(); response.subscribe(new Observer<HttpClientResponse<ByteBuf>>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { ex.set(e); latch.countDown(); } @Override public void onNext(HttpClientResponse<ByteBuf> args) { } }); latch.await(10, TimeUnit.SECONDS); assertTrue(ex.get() instanceof ConnectTimeoutException); } @Test public void testTimeout() throws Exception { int timeoutMillis = 10; RxClient.ClientConfig clientConfig = new Builder(null).readTimeout(timeoutMillis, TimeUnit.MILLISECONDS) .build(); HttpClient<ByteBuf, ByteBuf> client = new HttpClientBuilder<ByteBuf, ByteBuf>("localhost", port) .config(clientConfig).build(); Observable<HttpClientResponse<ByteBuf>> response = client.submit(HttpClientRequest .createGet("test/timeout?timeout=" + timeoutMillis * 2 /*Create bigger wait than timeout*/)); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> exception = new AtomicReference<Throwable>(); response.subscribe(new Observer<HttpClientResponse<ByteBuf>>() { @Override public void onCompleted() { latch.countDown(); } @Override public void onError(Throwable e) { exception.set(e); latch.countDown(); } @Override public void onNext(HttpClientResponse<ByteBuf> response) { latch.countDown(); } }); if (!latch.await(1, TimeUnit.MINUTES)) { fail("Observer is not called without timeout"); } else { assertTrue(exception.get() instanceof ReadTimeoutException); } } @Test public void testNoReadTimeout() throws Exception { RxClient.ClientConfig clientConfig = new Builder(null).readTimeout(2, TimeUnit.SECONDS).build(); HttpClient<ByteBuf, ByteBuf> client = new HttpClientBuilder<ByteBuf, ByteBuf>("localhost", port) .config(clientConfig).build(); Observable<HttpClientResponse<ByteBuf>> response = client .submit(HttpClientRequest.createGet("test/singleEntity")); final AtomicReference<Throwable> exceptionHolder = new AtomicReference<Throwable>(); final int[] status = { 0 }; response.subscribe(new Observer<HttpClientResponse<ByteBuf>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { exceptionHolder.set(e); } @Override public void onNext(HttpClientResponse<ByteBuf> response) { status[0] = response.getStatus().code(); } }); Thread.sleep(3000); if (exceptionHolder.get() != null) { exceptionHolder.get().printStackTrace(); } assertEquals(200, status[0]); assertNull(exceptionHolder.get()); } private static void readResponseContent(Observable<HttpClientResponse<ServerSentEvent>> response, final List<String> result) { response.flatMap(new Func1<HttpClientResponse<ServerSentEvent>, Observable<ServerSentEvent>>() { @Override public Observable<ServerSentEvent> call(HttpClientResponse<ServerSentEvent> sseEventHttpResponse) { return sseEventHttpResponse.getContent(); } }).toBlocking().forEach(new Action1<ServerSentEvent>() { @Override public void call(ServerSentEvent serverSentEvent) { result.add(serverSentEvent.getEventData()); } }); } }