/*
*
* 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 com.netflix.ribbon.transport.netty.http;
import static com.netflix.ribbon.testutils.TestUtils.waitUntilTrueOrTimeout;
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;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import io.reactivex.netty.contexts.ContextsContainer;
import io.reactivex.netty.contexts.ContextsContainerImpl;
import io.reactivex.netty.contexts.MapBackedKeySupplier;
import io.reactivex.netty.contexts.RxContexts;
import io.reactivex.netty.protocol.http.client.HttpClient.HttpClientConfig;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import io.reactivex.netty.protocol.text.sse.ServerSentEvent;
import io.reactivex.netty.servo.http.HttpClientListener;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import com.google.common.collect.Lists;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import com.netflix.client.ClientException;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.IClientConfigKey;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.DummyPing;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerStats;
import com.netflix.ribbon.test.resources.EmbeddedResources;
import com.netflix.ribbon.test.resources.EmbeddedResources.Person;
import com.netflix.ribbon.transport.netty.RibbonTransport;
import com.netflix.serialization.JacksonCodec;
import com.netflix.serialization.SerializationUtils;
import com.netflix.serialization.TypeDef;
import com.sun.jersey.api.container.httpserver.HttpServerFactory;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.net.httpserver.HttpServer;
public class NettyClientTest {
private static HttpServer server = null;
private static String SERVICE_URI;
private static int port;
private static final String host = "localhost";
static Observable<ServerSentEvent> transformSSE(Observable<HttpClientResponse<ServerSentEvent>> response) {
return response.flatMap(new Func1<HttpClientResponse<ServerSentEvent>, Observable<ServerSentEvent>>() {
@Override
public Observable<ServerSentEvent> call(HttpClientResponse<ServerSentEvent> t1) {
return t1.getContent();
}
});
}
@BeforeClass
public static void init() throws Exception {
PackagesResourceConfig resourceConfig = new PackagesResourceConfig("com.netflix.ribbon.test.resources");
port = (new Random()).nextInt(1000) + 4000;
SERVICE_URI = "http://localhost:" + port + "/";
ExecutorService service = Executors.newFixedThreadPool(20);
try{
server = HttpServerFactory.create(SERVICE_URI, resourceConfig);
server.setExecutor(service);
server.start();
} catch(Exception e) {
e.printStackTrace();
fail("Unable to start server");
}
// LogManager.getRootLogger().setLevel(Level.DEBUG);
}
private static Observable<Person> getPersonObservable(Observable<HttpClientResponse<ByteBuf>> response) {
return response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>>() {
@Override
public Observable<ByteBuf> call(HttpClientResponse<ByteBuf> t1) {
return t1.getContent();
}
}).map(new Func1<ByteBuf, Person>() {
@Override
public Person call(ByteBuf t1) {
try {
return JacksonCodec.<Person>getInstance().deserialize(new ByteBufInputStream(t1), TypeDef.fromClass(Person.class));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
});
}
@Test
public void testObservable() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/person");
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient();
Observable<HttpClientResponse<ByteBuf>> response = observableClient.submit(request);
Person person = getPersonObservable(response).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
final HttpClientListener listener = observableClient.getListener();
assertEquals(1, listener.getPoolAcquires());
assertEquals(1, listener.getConnectionCount());
waitUntilTrueOrTimeout(1000, new Func0<Boolean>() {
@Override
public Boolean call() {
return listener.getPoolReleases() == 1;
}
});
}
@Test
public void testSubmitToAbsoluteURI() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/person");
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient();
// final List<Person> result = Lists.newArrayList();
Observable<HttpClientResponse<ByteBuf>> response = observableClient.submit(request);
Person person = getPersonObservable(response).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
// need to sleep to wait until connection is released
final HttpClientListener listener = observableClient.getListener();
assertEquals(1, listener.getConnectionCount());
assertEquals(1, listener.getPoolAcquires());
waitUntilTrueOrTimeout(1000, new Func0<Boolean>() {
@Override
public Boolean call() {
return listener.getPoolReleases() == 1;
}
});
}
@Test
public void testPoolReuse() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/person");
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient(
IClientConfig.Builder.newBuilder().withDefaultValues()
.withMaxAutoRetries(1)
.withMaxAutoRetriesNextServer(1).build());
Observable<HttpClientResponse<ByteBuf>> response = observableClient.submit(request);
Person person = getPersonObservable(response).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
response = observableClient.submit(request);
person = getPersonObservable(response).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
final HttpClientListener listener = observableClient.getListener();
assertEquals(2, listener.getPoolAcquires());
waitUntilTrueOrTimeout(1000, new Func0<Boolean>() {
@Override
public Boolean call() {
return listener.getPoolReleases() == 2;
}
});
assertEquals(1, listener.getConnectionCount());
assertEquals(1, listener.getPoolReuse());
}
@Test
public void testPostWithObservable() throws Exception {
Person myPerson = new Person("netty", 5);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createPost(SERVICE_URI + "testAsync/person")
.withHeader("Content-type", "application/json")
.withContent(SerializationUtils.serializeToBytes(JacksonCodec.getInstance(), myPerson, null));
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient(
DefaultClientConfigImpl.getClientConfigWithDefaultValues().set(CommonClientConfigKey.ReadTimeout, 10000));
Observable<HttpClientResponse<ByteBuf>> response = observableClient.submit(new Server(host, port), request);
Person person = getPersonObservable(response).toBlocking().single();
assertEquals(myPerson, person);
}
@Test
public void testPostWithByteBuf() throws Exception {
Person myPerson = new Person("netty", 5);
ObjectMapper mapper = new ObjectMapper();
byte[] raw = mapper.writeValueAsBytes(myPerson);
ByteBuf buffer = Unpooled.copiedBuffer(raw);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createPost(SERVICE_URI + "testAsync/person")
.withHeader("Content-type", "application/json")
.withHeader("Content-length", String.valueOf(raw.length))
.withContent(buffer);
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient(
DefaultClientConfigImpl.getClientConfigWithDefaultValues().set(CommonClientConfigKey.ReadTimeout, 10000));
Observable<HttpClientResponse<ByteBuf>> response = observableClient.submit(request);
Person person = getPersonObservable(response).toBlocking().single();
assertEquals(myPerson, person);
}
@Test
public void testConnectTimeout() throws Exception {
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient(
DefaultClientConfigImpl.getClientConfigWithDefaultValues().withProperty(CommonClientConfigKey.ConnectTimeout, "1"));
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("http://www.google.com:81/");
Observable<HttpClientResponse<ByteBuf>> observable = observableClient.submit(new Server("www.google.com", 81), request);
ObserverWithLatch<HttpClientResponse<ByteBuf>> observer = new ObserverWithLatch<HttpClientResponse<ByteBuf>>();
observable.subscribe(observer);
observer.await();
assertNotNull(observer.error);
assertTrue(observer.error instanceof io.netty.channel.ConnectTimeoutException);
}
@Test
public void testReadTimeout() throws Exception {
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient(
DefaultClientConfigImpl.getClientConfigWithDefaultValues().withProperty(CommonClientConfigKey.ReadTimeout, "100"));
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/readTimeout");
Observable<HttpClientResponse<ByteBuf>> observable = observableClient.submit(request);
ObserverWithLatch<HttpClientResponse<ByteBuf>> observer = new ObserverWithLatch<HttpClientResponse<ByteBuf>>();
observable.subscribe(observer);
observer.await();
assertTrue(observer.error instanceof io.netty.handler.timeout.ReadTimeoutException);
}
@Test
public void testObservableWithMultipleServers() throws Exception {
IClientConfig config = DefaultClientConfigImpl
.getClientConfigWithDefaultValues()
.withProperty(CommonClientConfigKey.ConnectTimeout, "1000");
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/person");
Server badServer = new Server("localhost:12345");
Server goodServer = new Server("localhost:" + port);
List<Server> servers = Lists.newArrayList(badServer, badServer, badServer, goodServer);
BaseLoadBalancer lb = LoadBalancerBuilder.<Server>newBuilder()
.withRule(new AvailabilityFilteringRule())
.withPing(new DummyPing())
.buildFixedServerListLoadBalancer(servers);
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config,
new NettyHttpLoadBalancerErrorHandler(1, 3, true));
Person person = getPersonObservable(lbObservables.submit(request)).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
ServerStats stats = lbObservables.getServerStats(badServer);
// two requests to bad server because retry same server is set to 1
assertEquals(4, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(4, stats.getSuccessiveConnectionFailureCount());
stats = lbObservables.getServerStats(goodServer);
assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(0, stats.getSuccessiveConnectionFailureCount());
person = getPersonObservable(lbObservables.submit(request)).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
HttpClientListener listener = lbObservables.getListener();
assertEquals(1, listener.getPoolReuse());
}
@Test
public void testObservableWithMultipleServersWithOverrideRxConfig() throws Exception {
IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues().withProperty(CommonClientConfigKey.ConnectTimeout, "1000");
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/person");
Server badServer = new Server("localhost:12345");
Server goodServer = new Server("localhost:" + port);
List<Server> servers = Lists.newArrayList(badServer, badServer, badServer, goodServer);
BaseLoadBalancer lb = LoadBalancerBuilder.<Server>newBuilder()
.withRule(new AvailabilityFilteringRule())
.withPing(new DummyPing())
.buildFixedServerListLoadBalancer(servers);
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config,
new NettyHttpLoadBalancerErrorHandler(1, 3, true));
HttpClientConfig rxconfig = HttpClientConfig.Builder.newDefaultConfig();
Person person = getPersonObservable(lbObservables.submit(request, rxconfig)).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
ServerStats stats = lbObservables.getServerStats(badServer);
// two requests to bad server because retry same server is set to 1
assertEquals(4, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(4, stats.getSuccessiveConnectionFailureCount());
stats = lbObservables.getServerStats(goodServer);
assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(0, stats.getSuccessiveConnectionFailureCount());
final HttpClientListener listener = lbObservables.getListener();
assertEquals(1, listener.getConnectionCount());
waitUntilTrueOrTimeout(1000, new Func0<Boolean>() {
@Override
public Boolean call() {
return listener.getPoolReleases() == 1;
}
});
}
@Test
public void testObservableWithRetrySameServer() throws Exception {
IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues().withProperty(CommonClientConfigKey.ConnectTimeout, "1000");
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/person");
Server badServer = new Server("localhost:12345");
Server goodServer = new Server("localhost:" + port);
List<Server> servers = Lists.newArrayList(badServer, badServer, goodServer);
BaseLoadBalancer lb = LoadBalancerBuilder.<Server>newBuilder()
.withRule(new AvailabilityFilteringRule())
.withPing(new DummyPing())
.buildFixedServerListLoadBalancer(servers);
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config,
new NettyHttpLoadBalancerErrorHandler(1, 0, true));
Observable<Person> observableWithRetries = getPersonObservable(lbObservables.submit(request));
ObserverWithLatch<Person> observer = new ObserverWithLatch<Person>();
observableWithRetries.subscribe(observer);
observer.await();
assertNull(observer.obj);
assertTrue(observer.error instanceof ClientException);
ServerStats stats = lbObservables.getServerStats(badServer);
// two requests to bad server because retry same server is set to 1
assertEquals(2, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
stats = lbObservables.getServerStats(goodServer);
assertEquals(0, stats.getTotalRequestsCount());
}
@Test
public void testLoadBalancingObservablesWithReadTimeout() throws Exception {
NettyHttpLoadBalancerErrorHandler errorHandler = new NettyHttpLoadBalancerErrorHandler(1, 3, true);
MockWebServer server = new MockWebServer();
String content = "{\"name\": \"ribbon\", \"age\": 2}";
server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-type", "application/json")
.setBody(content));
server.play();
IClientConfig config = DefaultClientConfigImpl
.getClientConfigWithDefaultValues()
.set(CommonClientConfigKey.ReadTimeout, 100);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/readTimeout");
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config, errorHandler);
Server goodServer = new Server("localhost:" + server.getPort());
Server badServer = new Server("localhost:" + port);
lb.setServersList(Lists.newArrayList(goodServer, badServer, badServer, goodServer));
Observable<Person> observableWithRetries = getPersonObservable(lbObservables.submit(request));
ObserverWithLatch<Person> observer = new ObserverWithLatch<Person>();
observableWithRetries.subscribe(observer);
observer.await();
if (observer.error != null) {
observer.error.printStackTrace();
}
assertEquals("ribbon", observer.obj.name);
assertEquals(2, observer.obj.age);
ServerStats stats = lbObservables.getServerStats(badServer);
server.shutdown();
final HttpClientListener listener = lbObservables.getListener();
waitUntilTrueOrTimeout(1000, new Func0<Boolean>() {
@Override
public Boolean call() {
return listener.getPoolReleases() == 5;
}
});
assertEquals(0, listener.getPoolReuse());
// two requests to bad server because retry same server is set to 1
assertEquals(4, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(4, stats.getSuccessiveConnectionFailureCount());
stats = lbObservables.getServerStats(goodServer);
assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(0, stats.getSuccessiveConnectionFailureCount());
}
@Test
public void testLoadBalancingWithTwoServers() throws Exception {
MockWebServer server = new MockWebServer();
String content = "{\"name\": \"ribbon\", \"age\": 2}";
server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-type", "application/json")
.setBody(content));
server.play();
IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues();
HttpClientRequest<ByteBuf> request = HttpClientRequest.createPost("/testAsync/person")
.withContent(SerializationUtils.serializeToBytes(JacksonCodec.getInstance(), EmbeddedResources.defaultPerson, null))
.withHeader("Content-type", "application/json");
NettyHttpLoadBalancerErrorHandler errorHandler = new NettyHttpLoadBalancerErrorHandler(1, 3, true);
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config, errorHandler);
HttpClientListener externalListener = HttpClientListener.newHttpListener("external");
lbObservables.subscribe(externalListener);
Server server1 = new Server("localhost:" + server.getPort());
Server server2 = new Server("localhost:" + port);
lb.setServersList(Lists.newArrayList(server1, server2));
RetryHandler handler = new RequestSpecificRetryHandler(true, true, errorHandler, null) {
@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
return true;
}
};
Observable<Person> observableWithRetries = getPersonObservable(lbObservables.submit(request, handler, null));
ObserverWithLatch<Person> observer = new ObserverWithLatch<Person>();
observableWithRetries.subscribe(observer);
observer.await();
if (observer.error != null) {
observer.error.printStackTrace();
}
assertEquals("ribbon", observer.obj.name);
assertEquals(EmbeddedResources.defaultPerson.age, observer.obj.age);
observer = new ObserverWithLatch<Person>();
observableWithRetries = getPersonObservable(lbObservables.submit(request, handler, null));
observableWithRetries.subscribe(observer);
observer.await();
if (observer.error != null) {
observer.error.printStackTrace();
}
assertEquals("ribbon", observer.obj.name);
assertEquals(2, observer.obj.age);
ServerStats stats = lbObservables.getServerStats(server1);
server.shutdown();
// assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
stats = lbObservables.getServerStats(server2);
// two requests to bad server because retry same server is set to 1
assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(0, stats.getSuccessiveConnectionFailureCount());
final HttpClientListener listener = lbObservables.getListener();
assertEquals(2, listener.getPoolAcquires());
waitUntilTrueOrTimeout(1000, new Func0<Boolean>() {
@Override
public Boolean call() {
return listener.getPoolReleases() == 2;
}
});
assertEquals(2, listener.getConnectionCount());
assertEquals(0, listener.getPoolReuse());
assertEquals(2, externalListener.getPoolAcquires());
}
@Test
public void testLoadBalancingPostWithReadTimeout() throws Exception {
MockWebServer server = new MockWebServer();
String content = "{\"name\": \"ribbon\", \"age\": 2}";
server.enqueue(new MockResponse()
.setResponseCode(200)
.setHeader("Content-type", "application/json")
.setBody(content));
server.play();
IClientConfig config = DefaultClientConfigImpl
.getClientConfigWithDefaultValues()
.set(CommonClientConfigKey.ReadTimeout, 100);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createPost("/testAsync/postTimeout")
.withContent(SerializationUtils.serializeToBytes(JacksonCodec.getInstance(), EmbeddedResources.defaultPerson, null))
.withHeader("Content-type", "application/json");
NettyHttpLoadBalancerErrorHandler errorHandler = new NettyHttpLoadBalancerErrorHandler(1, 3, true);
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config, errorHandler);
Server goodServer = new Server("localhost:" + server.getPort());
Server badServer = new Server("localhost:" + port);
List<Server> servers = Lists.newArrayList(badServer, badServer, badServer, goodServer);
lb.setServersList(servers);
RetryHandler handler = new RequestSpecificRetryHandler(true, true, errorHandler, null) {
@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
return true;
}
};
Observable<Person> observableWithRetries = getPersonObservable(lbObservables.submit(request, handler, null));
ObserverWithLatch<Person> observer = new ObserverWithLatch<Person>();
observableWithRetries.subscribe(observer);
observer.await();
if (observer.error != null) {
observer.error.printStackTrace();
}
assertEquals("ribbon", observer.obj.name);
assertEquals(2, observer.obj.age);
ServerStats stats = lbObservables.getServerStats(badServer);
server.shutdown();
assertEquals(4, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(4, stats.getSuccessiveConnectionFailureCount());
stats = lbObservables.getServerStats(goodServer);
// two requests to bad server because retry same server is set to 1
assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(0, stats.getSuccessiveConnectionFailureCount());
}
@Test
public void testLoadBalancingPostWithNoRetrySameServer() throws Exception {
MockWebServer server = new MockWebServer();
String content = "{\"name\": \"ribbon\", \"age\": 2}";
server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-type", "application/json")
.setBody(content));
server.play();
IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues()
.set(CommonClientConfigKey.ReadTimeout, 100);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createPost("/testAsync/postTimeout")
.withContent(SerializationUtils.serializeToBytes(JacksonCodec.getInstance(), EmbeddedResources.defaultPerson, null))
.withHeader("Content-type", "application/json");
NettyHttpLoadBalancerErrorHandler errorHandler = new NettyHttpLoadBalancerErrorHandler(0, 3, true);
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config, errorHandler);
Server goodServer = new Server("localhost:" + server.getPort());
Server badServer = new Server("localhost:" + port);
List<Server> servers = Lists.newArrayList(badServer, badServer, badServer, goodServer);
lb.setServersList(servers);
RetryHandler handler = new RequestSpecificRetryHandler(true, true, errorHandler, null) {
@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
return true;
}
};
Observable<Person> observableWithRetries = getPersonObservable(lbObservables.submit(request, handler, null));
ObserverWithLatch<Person> observer = new ObserverWithLatch<Person>();
observableWithRetries.subscribe(observer);
observer.await();
if (observer.error != null) {
observer.error.printStackTrace();
}
server.shutdown();
assertEquals("ribbon", observer.obj.name);
assertEquals(2, observer.obj.age);
ServerStats stats = lbObservables.getServerStats(badServer);
assertEquals(2, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(2, stats.getSuccessiveConnectionFailureCount());
stats = lbObservables.getServerStats(goodServer);
assertEquals(1, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(0, stats.getSuccessiveConnectionFailureCount());
}
@Test
public void testObservableWithMultipleServersFailed() throws Exception {
IClientConfig config = IClientConfig.Builder.newBuilder()
.withDefaultValues()
.withRetryOnAllOperations(true)
.withMaxAutoRetries(1)
.withMaxAutoRetriesNextServer(3)
.withConnectTimeout(100)
.build();
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/person");
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config);
Server badServer = new Server("localhost:12345");
Server badServer1 = new Server("localhost:12346");
Server badServer2 = new Server("localhost:12347");
List<Server> servers = Lists.newArrayList(badServer, badServer1, badServer2);
lb.setServersList(servers);
Observable<Person> observableWithRetries = getPersonObservable(lbObservables.submit(request));
ObserverWithLatch<Person> observer = new ObserverWithLatch<Person>();
observableWithRetries.subscribe(observer);
observer.await();
assertNull(observer.obj);
observer.error.printStackTrace();
assertTrue(observer.error instanceof ClientException);
ServerStats stats = lbObservables.getServerStats(badServer);
// two requests to bad server because retry same server is set to 1
assertEquals(2, stats.getTotalRequestsCount());
assertEquals(0, stats.getActiveRequestsCount());
assertEquals(2, stats.getSuccessiveConnectionFailureCount());
}
private static List<Person> getPersonListFromResponse(Observable<HttpClientResponse<ServerSentEvent>> response) {
return getPersonList(transformSSE(response));
}
private static List<Person> getPersonList(Observable<ServerSentEvent> events) {
List<Person> result = Lists.newArrayList();
Iterator<Person> iterator = events.map(new Func1<ServerSentEvent, Person>() {
@Override
public Person call(ServerSentEvent t1) {
String content = t1.getEventData();
try {
return SerializationUtils.deserializeFromString(JacksonCodec.<Person>getInstance(), content, TypeDef.fromClass(Person.class));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}).toBlocking().getIterator();
while (iterator.hasNext()) {
result.add(iterator.next());
}
return result;
}
@Test
public void testStream() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/personStream");
LoadBalancingHttpClient<ByteBuf, ServerSentEvent> observableClient = (LoadBalancingHttpClient<ByteBuf, ServerSentEvent>) RibbonTransport.newSSEClient();
List<Person> result = getPersonListFromResponse(observableClient.submit(new Server(host, port), request));
assertEquals(EmbeddedResources.entityStream, result);
}
@Test
public void testStreamWithLoadBalancer() throws Exception {
// NettyHttpLoadBalancerErrorHandler errorHandler = new NettyHttpLoadBalancerErrorHandler(1, 3, true);
// IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues().withProperty(CommonClientConfigKey.ConnectTimeout, "1000");
IClientConfig config = IClientConfig.Builder.newBuilder().withRetryOnAllOperations(true)
.withMaxAutoRetries(1)
.withMaxAutoRetriesNextServer(3)
.build();
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ServerSentEvent> lbObservables = (LoadBalancingHttpClient<ByteBuf, ServerSentEvent>) RibbonTransport.newSSEClient(lb, config);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/personStream");
List<Person> result = Lists.newArrayList();
Server goodServer = new Server("localhost:" + port);
Server badServer = new Server("localhost:12245");
List<Server> servers = Lists.newArrayList(badServer, badServer, badServer, goodServer);
lb.setServersList(servers);
result = getPersonListFromResponse(lbObservables.submit(request, null, null));
assertEquals(EmbeddedResources.entityStream, result);
}
@Test
public void testQuery() throws Exception {
Person myPerson = new Person("hello_world", 4);
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/personQuery?name=" + myPerson.name + "&age=" + myPerson.age);
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient();
Person person = getPersonObservable(observableClient.submit(new Server(host, port), request)).toBlocking().single();
assertEquals(myPerson, person);
}
@Test
public void testUnexpectedResponse() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/throttle");
LoadBalancingHttpClient<ByteBuf, ByteBuf> client = RibbonTransport.newHttpClient();
Observable<HttpClientResponse<ByteBuf>> responseObservable = client.submit(new Server(host, port), request);
final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
final CountDownLatch latch = new CountDownLatch(1);
responseObservable.subscribe(new Action1<HttpClientResponse<ByteBuf>>() {
@Override
public void call(HttpClientResponse<ByteBuf> t1) {
latch.countDown();
}
}, new Action1<Throwable>(){
@Override
public void call(Throwable t1) {
error.set(t1);
latch.countDown();
}
});
latch.await();
assertTrue(error.get() instanceof ClientException);
ClientException ce = (ClientException) error.get();
assertTrue(ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED);
}
@Test
public void testLoadBalancerThrottle() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet("/testAsync/throttle");
IClientConfig config = DefaultClientConfigImpl
.getClientConfigWithDefaultValues()
.set(IClientConfigKey.Keys.MaxAutoRetriesNextServer, 1)
.set(IClientConfigKey.Keys.OkToRetryOnAllOperations, true);
BaseLoadBalancer lb = new BaseLoadBalancer(new DummyPing(), new AvailabilityFilteringRule());
LoadBalancingHttpClient<ByteBuf, ByteBuf> lbObservables = RibbonTransport.newHttpClient(lb, config);
Server server = new Server(host, port);
lb.setServersList(Lists.newArrayList(server, server, server));
Observable<HttpClientResponse<ByteBuf>> response = lbObservables.submit(request);
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
response.subscribe(new Action1<HttpClientResponse<ByteBuf>>() {
@Override
public void call(HttpClientResponse<ByteBuf> t1) {
System.err.println("Get response: " + t1.getStatus().code());
latch.countDown();
}
}, new Action1<Throwable>(){
@Override
public void call(Throwable t1) {
error.set(t1);
latch.countDown();
}
}, new Action0() {
@Override
public void call() {
Thread.dumpStack();
latch.countDown();
}
});
latch.await();
assertTrue(error.get() instanceof ClientException);
ClientException ce = (ClientException) error.get();
assertTrue(ce.toString(), ce.getErrorType() == ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED);
assertEquals(2, lbObservables.getServerStats(server).getSuccessiveConnectionFailureCount());
}
@Test
public void testContext() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/context");
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient = RibbonTransport.newHttpClient();
String requestId = "xyz";
ContextsContainerImpl contextsContainer = new ContextsContainerImpl(new MapBackedKeySupplier());
contextsContainer.addContext("Context1", "value1");
RxContexts.DEFAULT_CORRELATOR.onNewServerRequest(requestId, contextsContainer);
Observable<HttpClientResponse<ByteBuf>> response = observableClient.submit(new Server(host, port), request);
final AtomicReference<ContextsContainer> responseContext = new AtomicReference<ContextsContainer>();
String requestIdSent = response.flatMap(new Func1<HttpClientResponse<ByteBuf>, Observable<ByteBuf>>() {
@Override
public Observable<ByteBuf> call(HttpClientResponse<ByteBuf> t1) {
return t1.getContent();
}
}).map(new Func1<ByteBuf, String>() {
@Override
public String call(ByteBuf t1) {
String requestId = RxContexts.DEFAULT_CORRELATOR.getRequestIdForClientRequest();
responseContext.set(RxContexts.DEFAULT_CORRELATOR.getContextForClientRequest(requestId));
return t1.toString(Charset.defaultCharset());
}
}).toBlocking().single();
assertEquals(requestId, requestIdSent);
assertEquals("value1", responseContext.get().getContext("Context1"));
}
@Test
@Ignore
public void testRedirect() throws Exception {
HttpClientRequest<ByteBuf> request = HttpClientRequest.createGet(SERVICE_URI + "testAsync/redirect?port=" + port);
LoadBalancingHttpClient<ByteBuf, ByteBuf> observableClient =
RibbonTransport.newHttpClient(
IClientConfig.Builder.newBuilder().withDefaultValues()
.withFollowRedirects(true)
.build());
Person person = getPersonObservable(observableClient.submit(new Server(host, port), request)).toBlocking().single();
assertEquals(EmbeddedResources.defaultPerson, person);
}
}