/* * 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; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.netflix.hystrix.HystrixInvokableInfo; import com.netflix.hystrix.exception.HystrixBadRequestException; import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; import com.netflix.ribbon.http.HttpRequestTemplate; import com.netflix.ribbon.http.HttpResourceGroup; import com.netflix.ribbon.hystrix.FallbackHandler; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.reactivex.netty.protocol.http.client.HttpClientResponse; import org.apache.log4j.Level; import org.apache.log4j.LogManager; 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.Func1; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import static org.junit.Assert.*; public class RibbonTest { private static String toStringBlocking(RibbonRequest<ByteBuf> request) { return request.toObservable().map(new Func1<ByteBuf, String>() { @Override public String call(ByteBuf t1) { return t1.toString(Charset.defaultCharset()); } }).toBlocking().single(); } @BeforeClass public static void init() { LogManager.getRootLogger().setLevel(Level.DEBUG); } @Test public void testCommand() throws IOException, InterruptedException, ExecutionException { MockWebServer server = new MockWebServer(); String content = "Hello world"; MockResponse response = new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content); server.enqueue(response); server.enqueue(response); server.enqueue(response); server.play(); HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withMaxAutoRetriesNextServer(3) .withReadTimeout(300000) .withConfigurationBasedServerList("localhost:12345, localhost:10092, localhost:" + server.getPort())); HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test", ByteBuf.class) .withUriTemplate("/") .withMethod("GET") .build(); RibbonRequest<ByteBuf> request = template.requestBuilder().build(); String result = request.execute().toString(Charset.defaultCharset()); assertEquals(content, result); // repeat the same request ByteBuf raw = request.execute(); result = raw.toString(Charset.defaultCharset()); raw.release(); assertEquals(content, result); result = request.queue().get().toString(Charset.defaultCharset()); assertEquals(content, result); } @Test public void testHystrixCache() throws IOException { // LogManager.getRootLogger().setLevel((Level)Level.DEBUG); MockWebServer server = new MockWebServer(); String content = "Hello world"; MockResponse response = new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content); server.enqueue(response); server.enqueue(response); server.play(); HttpResourceGroup group = Ribbon.createHttpResourceGroupBuilder("myclient").build(); HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test", ByteBuf.class) .withUriTemplate("http://localhost:" + server.getPort()) .withMethod("GET") .withRequestCacheKey("xyz") .build(); RibbonRequest<ByteBuf> request = template .requestBuilder().build(); HystrixRequestContext context = HystrixRequestContext.initializeContext(); try { RibbonResponse<ByteBuf> ribbonResponse = request.withMetadata().execute(); assertFalse(ribbonResponse.getHystrixInfo().isResponseFromCache()); ribbonResponse = request.withMetadata().execute(); assertTrue(ribbonResponse.getHystrixInfo().isResponseFromCache()); } finally { context.shutdown(); } } @Test @Ignore public void testCommandWithMetaData() throws IOException, InterruptedException, ExecutionException { // LogManager.getRootLogger().setLevel((Level)Level.DEBUG); MockWebServer server = new MockWebServer(); String content = "Hello world"; for (int i = 0; i < 6; i++) { server.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content)); } server.play(); HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withConfigurationBasedServerList("localhost:" + server.getPort()) .withMaxAutoRetriesNextServer(3)); HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test") .withUriTemplate("/") .withMethod("GET") .withCacheProvider("somekey", new CacheProvider<ByteBuf>(){ @Override public Observable<ByteBuf> get(String key, Map<String, Object> vars) { return Observable.error(new Exception("Cache miss")); } }).build(); RibbonRequest<ByteBuf> request = template .requestBuilder().build(); final AtomicBoolean success = new AtomicBoolean(false); RequestWithMetaData<ByteBuf> metaRequest = request.withMetadata(); Observable<String> result = metaRequest.toObservable().flatMap(new Func1<RibbonResponse<Observable<ByteBuf>>, Observable<String>>(){ @Override public Observable<String> call( final RibbonResponse<Observable<ByteBuf>> response) { success.set(response.getHystrixInfo().isSuccessfulExecution()); return response.content().map(new Func1<ByteBuf, String>(){ @Override public String call(ByteBuf t1) { return t1.toString(Charset.defaultCharset()); } }); } }); String s = result.toBlocking().single(); assertEquals(content, s); assertTrue(success.get()); Future<RibbonResponse<ByteBuf>> future = metaRequest.queue(); RibbonResponse<ByteBuf> response = future.get(); assertTrue(future.isDone()); assertEquals(content, response.content().toString(Charset.defaultCharset())); assertTrue(response.getHystrixInfo().isSuccessfulExecution()); RibbonResponse<ByteBuf> result1 = metaRequest.execute(); assertEquals(content, result1.content().toString(Charset.defaultCharset())); assertTrue(result1.getHystrixInfo().isSuccessfulExecution()); } @Test public void testValidator() throws IOException, InterruptedException { // LogManager.getRootLogger().setLevel((Level)Level.DEBUG); MockWebServer server = new MockWebServer(); String content = "Hello world"; server.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content)); server.play(); HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withConfigurationBasedServerList("localhost:" + server.getPort())); HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test", ByteBuf.class) .withUriTemplate("/") .withMethod("GET") .withResponseValidator(new ResponseValidator<HttpClientResponse<ByteBuf>>() { @Override public void validate(HttpClientResponse<ByteBuf> t1) throws UnsuccessfulResponseException { throw new UnsuccessfulResponseException("error", new IllegalArgumentException()); } }).build(); RibbonRequest<ByteBuf> request = template.requestBuilder().build(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); request.toObservable().subscribe(new Action1<ByteBuf>() { @Override public void call(ByteBuf t1) { } }, new Action1<Throwable>(){ @Override public void call(Throwable t1) { error.set(t1); latch.countDown(); } }, new Action0() { @Override public void call() { } }); latch.await(); assertTrue(error.get() instanceof HystrixBadRequestException); assertTrue(error.get().getCause() instanceof UnsuccessfulResponseException); } @Test public void testFallback() throws IOException { HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withConfigurationBasedServerList("localhost:12345") .withMaxAutoRetriesNextServer(1)); final String fallback = "fallback"; HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test", ByteBuf.class) .withUriTemplate("/") .withMethod("GET") .withFallbackProvider(new FallbackHandler<ByteBuf>() { @Override public Observable<ByteBuf> getFallback( HystrixInvokableInfo<?> hystrixInfo, Map<String, Object> requestProperties) { try { return Observable.just(Unpooled.buffer().writeBytes(fallback.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { return Observable.error(e); } } }).build(); RibbonRequest<ByteBuf> request = template .requestBuilder().build(); final AtomicReference<HystrixInvokableInfo<?>> hystrixInfo = new AtomicReference<HystrixInvokableInfo<?>>(); final AtomicBoolean failed = new AtomicBoolean(false); Observable<String> result = request.withMetadata().toObservable().flatMap(new Func1<RibbonResponse<Observable<ByteBuf>>, Observable<String>>(){ @Override public Observable<String> call( final RibbonResponse<Observable<ByteBuf>> response) { hystrixInfo.set(response.getHystrixInfo()); failed.set(response.getHystrixInfo().isFailedExecution()); return response.content().map(new Func1<ByteBuf, String>(){ @Override public String call(ByteBuf t1) { return t1.toString(Charset.defaultCharset()); } }); } }); String s = result.toBlocking().single(); // this returns true only after the blocking call is done assertTrue(hystrixInfo.get().isResponseFromFallback()); assertTrue(failed.get()); assertEquals(fallback, s); } @Test public void testCacheHit() { HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withConfigurationBasedServerList("localhost:12345") .withMaxAutoRetriesNextServer(1)); final String content = "from cache"; final String cacheKey = "somekey"; HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test") .withCacheProvider(cacheKey, new CacheProvider<ByteBuf>(){ @Override public Observable<ByteBuf> get(String key, Map<String, Object> vars) { if (key.equals(cacheKey)) { try { return Observable.just(Unpooled.buffer().writeBytes(content.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { return Observable.error(e); } } else { return Observable.error(new Exception("Cache miss")); } } }).withUriTemplate("/") .withMethod("GET").build(); RibbonRequest<ByteBuf> request = template .requestBuilder().build(); String result = request.execute().toString(Charset.defaultCharset()); assertEquals(content, result); } @Test public void testObserve() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); String content = "Hello world"; server.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content)); server.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content)); server.play(); HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withMaxAutoRetriesNextServer(3) .withReadTimeout(300000) .withConfigurationBasedServerList("localhost:12345, localhost:10092, localhost:" + server.getPort())); HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test", ByteBuf.class) .withUriTemplate("/") .withMethod("GET").build(); RibbonRequest<ByteBuf> request = template .requestBuilder().build(); Observable<ByteBuf> result = request.observe(); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<String> fromCommand = new AtomicReference<String>(); // We need to wait until the response is received and processed by event loop // and make sure that subscribing to it again will not cause ByteBuf ref count issue result.toBlocking().last(); result.subscribe(new Action1<ByteBuf>() { @Override public void call(ByteBuf t1) { try { fromCommand.set(t1.toString(Charset.defaultCharset())); } catch (Exception e) { e.printStackTrace(); } latch.countDown(); } }); latch.await(); assertEquals(content, fromCommand.get()); Observable<RibbonResponse<Observable<ByteBuf>>> metaResult = request.withMetadata().observe(); String result2 = ""; // We need to wait until the response is received and processed by event loop // and make sure that subscribing to it again will not cause ByteBuf ref count issue metaResult.toBlocking().last(); result2 = metaResult.flatMap(new Func1<RibbonResponse<Observable<ByteBuf>>, Observable<ByteBuf>>(){ @Override public Observable<ByteBuf> call( RibbonResponse<Observable<ByteBuf>> t1) { return t1.content(); } }).map(new Func1<ByteBuf, String>(){ @Override public String call(ByteBuf t1) { return t1.toString(Charset.defaultCharset()); } }).toBlocking().single(); assertEquals(content, result2); } @Test public void testCacheMiss() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); String content = "Hello world"; server.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-type", "text/plain") .setBody(content)); server.play(); HttpResourceGroup group = Ribbon.createHttpResourceGroup("myclient", ClientOptions.create() .withConfigurationBasedServerList("localhost:" + server.getPort()) .withMaxAutoRetriesNextServer(1)); final String cacheKey = "somekey"; HttpRequestTemplate<ByteBuf> template = group.newTemplateBuilder("test") .withCacheProvider(cacheKey, new CacheProvider<ByteBuf>(){ @Override public Observable<ByteBuf> get(String key, Map<String, Object> vars) { return Observable.error(new Exception("Cache miss again")); } }) .withMethod("GET") .withUriTemplate("/").build(); RibbonRequest<ByteBuf> request = template .requestBuilder().build(); String result = toStringBlocking(request); assertEquals(content, result); } }