/* * Copyright 2013-2015 the original author or authors. * * 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 org.springframework.cloud.netflix.ribbon.apache; import java.io.IOException; import java.net.URI; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicyFactory; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerContext; import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.test.util.ReflectionTestUtils; import com.netflix.client.DefaultLoadBalancerRetryHandler; 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.loadbalancer.AbstractLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * @author Sébastien Nussbaumer * @author Ryan Baxter */ public class RibbonLoadBalancingHttpClientTests { private ILoadBalancer loadBalancer; @Before public void setup() { loadBalancer = mock(AbstractLoadBalancer.class); doReturn(new Server("foo.com", 8000)).when(loadBalancer).chooseServer(eq("default")); doReturn(new Server("foo.com", 8000)).when(loadBalancer).chooseServer(eq("service")); } @After public void teardown() { loadBalancer = null; } @Test public void testRequestConfigUseDefaultsNoOverride() throws Exception { RequestConfig result = getBuiltRequestConfig(UseDefaults.class, null); assertThat(result.isRedirectsEnabled(), is(false)); } @Test public void testRequestConfigDoNotFollowRedirectsNoOverride() throws Exception { RequestConfig result = getBuiltRequestConfig(DoNotFollowRedirects.class, null); assertThat(result.isRedirectsEnabled(), is(false)); } @Test public void testRequestConfigFollowRedirectsNoOverride() throws Exception { RequestConfig result = getBuiltRequestConfig(FollowRedirects.class, null); assertThat(result.isRedirectsEnabled(), is(true)); } @Test public void testTimeouts() throws Exception { RequestConfig result = getBuiltRequestConfig(Timeouts.class, null); assertThat(result.getConnectTimeout(), is(60000)); assertThat(result.getSocketTimeout(), is (50000)); } @Test public void testConnections() throws Exception { SpringClientFactory factory = new SpringClientFactory(); factory.setApplicationContext(new AnnotationConfigApplicationContext( RibbonAutoConfiguration.class, Connections.class)); RetryableRibbonLoadBalancingHttpClient client = factory.getClient("service", RetryableRibbonLoadBalancingHttpClient.class); HttpClient delegate = client.getDelegate(); PoolingHttpClientConnectionManager connManager = (PoolingHttpClientConnectionManager) ReflectionTestUtils.getField(delegate, "connManager"); assertThat(connManager.getMaxTotal(), is(101)); assertThat(connManager.getDefaultMaxPerRoute(), is(201)); } @Test public void testRequestConfigDoNotFollowRedirectsOverrideWithFollowRedirects() throws Exception { DefaultClientConfigImpl override = new DefaultClientConfigImpl(); override.set(CommonClientConfigKey.FollowRedirects, true); override.set(CommonClientConfigKey.IsSecure, false); RequestConfig result = getBuiltRequestConfig(DoNotFollowRedirects.class, override); assertThat(result.isRedirectsEnabled(), is(true)); } @Test public void testRequestConfigFollowRedirectsOverrideWithDoNotFollowRedirects() throws Exception { DefaultClientConfigImpl override = new DefaultClientConfigImpl(); override.set(CommonClientConfigKey.FollowRedirects, false); override.set(CommonClientConfigKey.IsSecure, false); RequestConfig result = getBuiltRequestConfig(FollowRedirects.class, override); assertThat(result.isRedirectsEnabled(), is(false)); } @Test public void testUpdatedTimeouts() throws Exception { SpringClientFactory factory = new SpringClientFactory(); RequestConfig result = getBuiltRequestConfig(Timeouts.class, null, factory); assertThat(result.getConnectTimeout(), is(60000)); assertThat(result.getSocketTimeout(), is (50000)); IClientConfig config = factory.getClientConfig("service"); config.set(CommonClientConfigKey.ConnectTimeout, 60); config.set(CommonClientConfigKey.ReadTimeout, 50); result = getBuiltRequestConfig(Timeouts.class, null, factory); assertThat(result.getConnectTimeout(), is(60)); assertThat(result.getSocketTimeout(), is (50)); } @Test public void testNeverRetry() throws Exception { ServerIntrospector introspector = mock(ServerIntrospector.class); HttpClient delegate = mock(HttpClient.class); HttpResponse response = mock(HttpResponse.class); doThrow(new IOException("boom")).when(delegate).execute(any(HttpUriRequest.class)); DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); clientConfig.setClientName("foo"); RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient(delegate, clientConfig, introspector); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); try { client.execute(request, null); fail("Expected IOException"); } catch(IOException e) {} finally { verify(delegate, times(1)).execute(any(HttpUriRequest.class)); } } private RetryableRibbonLoadBalancingHttpClient setupClientForRetry(int retriesNextServer, int retriesSameServer, boolean retryable, boolean retryOnAllOps, String serviceName, String host, int port, HttpClient delegate, ILoadBalancer lb, String statusCodes) throws Exception { ServerIntrospector introspector = mock(ServerIntrospector.class); RetryHandler retryHandler = new DefaultLoadBalancerRetryHandler(retriesSameServer, retriesNextServer, retryable); doReturn(new Server(host, port)).when(lb).chooseServer(eq(serviceName)); DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); clientConfig.set(CommonClientConfigKey.OkToRetryOnAllOperations, retryOnAllOps); clientConfig.set(CommonClientConfigKey.MaxAutoRetriesNextServer, retriesNextServer); clientConfig.set(CommonClientConfigKey.MaxAutoRetries, retriesSameServer); clientConfig.set(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES, statusCodes); clientConfig.setClientName(serviceName); RibbonLoadBalancerContext context = new RibbonLoadBalancerContext(lb, clientConfig, retryHandler); SpringClientFactory clientFactory = mock(SpringClientFactory.class); doReturn(context).when(clientFactory).getLoadBalancerContext(eq(serviceName)); doReturn(clientConfig).when(clientFactory).getClientConfig(eq(serviceName)); LoadBalancedRetryPolicyFactory factory = new RibbonLoadBalancedRetryPolicyFactory(clientFactory); RetryableRibbonLoadBalancingHttpClient client = new RetryableRibbonLoadBalancingHttpClient(clientConfig, introspector, factory); client.setLoadBalancer(lb); ReflectionTestUtils.setField(client, "delegate", delegate); return client; } @Test public void testRetrySameServerOnly() throws Exception { int retriesNextServer = 0; int retriesSameServer = 1; boolean retryable = true; boolean retryOnAllOps = false; String serviceName = "foo"; String host = serviceName; int port = 80; HttpMethod method = HttpMethod.GET; URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); final HttpResponse response = mock(HttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); doThrow(new IOException("boom")).doReturn(response).when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb, ""); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(method).when(request).getMethod(); doReturn(request).when(request).withNewUri(any(URI.class)); HttpUriRequest uriRequest = mock(HttpUriRequest.class); doReturn(uri).when(uriRequest).getURI(); doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); RibbonApacheHttpResponse returnedResponse = client.execute(request, null); verify(delegate, times(2)).execute(any(HttpUriRequest.class)); verify(lb, times(0)).chooseServer(eq(serviceName)); } @Test public void testRetryNextServer() throws Exception { int retriesNextServer = 1; int retriesSameServer = 1; boolean retryable = true; boolean retryOnAllOps = false; String serviceName = "foo"; String host = serviceName; int port = 80; HttpMethod method = HttpMethod.GET; URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); final HttpResponse response = mock(HttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb, ""); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(method).when(request).getMethod(); doReturn(request).when(request).withNewUri(any(URI.class)); HttpUriRequest uriRequest = mock(HttpUriRequest.class); doReturn(uri).when(uriRequest).getURI(); doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); RibbonApacheHttpResponse returnedResponse = client.execute(request, null); verify(delegate, times(3)).execute(any(HttpUriRequest.class)); verify(lb, times(1)).chooseServer(eq(serviceName)); } @Test public void testRetryOnPost() throws Exception { int retriesNextServer = 1; int retriesSameServer = 1; boolean retryable = true; boolean retryOnAllOps = true; String serviceName = "foo"; String host = serviceName; int port = 80; HttpMethod method = HttpMethod.POST; URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); final HttpResponse response = mock(HttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb, ""); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(method).when(request).getMethod(); doReturn(uri).when(request).getURI(); doReturn(request).when(request).withNewUri(any(URI.class)); HttpUriRequest uriRequest = mock(HttpUriRequest.class); doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); RibbonApacheHttpResponse returnedResponse = client.execute(request, null); verify(delegate, times(3)).execute(any(HttpUriRequest.class)); verify(lb, times(1)).chooseServer(eq(serviceName)); } @Test public void testNoRetryOnPost() throws Exception { int retriesNextServer = 1; int retriesSameServer = 1; boolean retryable = true; boolean retryOnAllOps = false; String serviceName = "foo"; String host = serviceName; int port = 80; HttpMethod method = HttpMethod.POST; URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); final HttpResponse response = mock(HttpResponse.class); doThrow(new IOException("boom")).doThrow(new IOException("boom again")).doReturn(response). when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb, ""); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(method).when(request).getMethod(); doReturn(uri).when(request).getURI(); doReturn(request).when(request).withNewUri(any(URI.class)); HttpUriRequest uriRequest = mock(HttpUriRequest.class); doReturn(uri).when(uriRequest).getURI(); doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); try { client.execute(request, null); fail("Expected IOException"); } catch(IOException e) {} finally { verify(delegate, times(1)).execute(any(HttpUriRequest.class)); verify(lb, times(0)).chooseServer(eq(serviceName)); } } @Test public void testRetryOnStatusCode() throws Exception { int retriesNextServer = 0; int retriesSameServer = 1; boolean retryable = true; boolean retryOnAllOps = false; String serviceName = "foo"; String host = serviceName; int port = 80; HttpMethod method = HttpMethod.GET; URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); final HttpResponse response = mock(HttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(response).getStatusLine(); final HttpResponse fourOFourResponse = mock(HttpResponse.class); StatusLine fourOFourStatusLine = mock(StatusLine.class); doReturn(404).when(fourOFourStatusLine).getStatusCode(); doReturn(fourOFourStatusLine).when(fourOFourResponse).getStatusLine(); doReturn(fourOFourResponse).doReturn(response).when(delegate).execute(any(HttpUriRequest.class)); ILoadBalancer lb = mock(ILoadBalancer.class); RetryableRibbonLoadBalancingHttpClient client = setupClientForRetry(retriesNextServer, retriesSameServer, retryable, retryOnAllOps, serviceName, host, port, delegate, lb, "404"); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(method).when(request).getMethod(); doReturn(request).when(request).withNewUri(any(URI.class)); HttpUriRequest uriRequest = mock(HttpUriRequest.class); doReturn(uri).when(uriRequest).getURI(); doReturn(uriRequest).when(request).toRequest(any(RequestConfig.class)); RibbonApacheHttpResponse returnedResponse = client.execute(request, null); verify(delegate, times(2)).execute(any(HttpUriRequest.class)); verify(lb, times(0)).chooseServer(eq(serviceName)); } @Configuration protected static class UseDefaults { } @Configuration protected static class FollowRedirects { @Bean public IClientConfig clientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.set(CommonClientConfigKey.FollowRedirects, true); return config; } } @Configuration protected static class DoNotFollowRedirects { @Bean public IClientConfig clientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.set(CommonClientConfigKey.FollowRedirects, false); return config; } } @Configuration protected static class Timeouts { @Bean public IClientConfig clientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.set(CommonClientConfigKey.ConnectTimeout, 60000); config.set(CommonClientConfigKey.ReadTimeout, 50000); return config; } } @Configuration protected static class Connections { @Bean public IClientConfig clientConfig() { DefaultClientConfigImpl config = new DefaultClientConfigImpl(); config.set(CommonClientConfigKey.MaxTotalConnections, 101); config.set(CommonClientConfigKey.MaxConnectionsPerHost, 201); return config; } } private RequestConfig getBuiltRequestConfig(Class<?> defaultConfigurationClass, IClientConfig configOverride) throws Exception { return getBuiltRequestConfig(defaultConfigurationClass, configOverride, new SpringClientFactory()); } private RequestConfig getBuiltRequestConfig(Class<?> defaultConfigurationClass, IClientConfig configOverride, SpringClientFactory factory) throws Exception { factory.setApplicationContext(new AnnotationConfigApplicationContext( RibbonAutoConfiguration.class, defaultConfigurationClass)); String serviceName = "foo"; String host = serviceName; int port = 80; URI uri = new URI("http://" + host + ":" + port); HttpClient delegate = mock(HttpClient.class); RibbonLoadBalancingHttpClient client = factory.getClient("service", RibbonLoadBalancingHttpClient.class); ReflectionTestUtils.setField(client, "delegate", delegate); ReflectionTestUtils.setField(client, "lb", loadBalancer); HttpResponse httpResponse = mock(HttpResponse.class); StatusLine statusLine = mock(StatusLine.class); doReturn(200).when(statusLine).getStatusCode(); doReturn(statusLine).when(httpResponse).getStatusLine(); given(delegate.execute(any(HttpUriRequest.class))).willReturn( httpResponse); RibbonApacheHttpRequest request = mock(RibbonApacheHttpRequest.class); doReturn(uri).when(request).getURI(); doReturn(request).when(request).withNewUri(any(URI.class)); given(request.toRequest(any(RequestConfig.class))).willReturn( mock(HttpUriRequest.class)); client.execute(request, configOverride); ArgumentCaptor<RequestConfig> requestConfigCaptor = ArgumentCaptor .forClass(RequestConfig.class); verify(request, times(1)).toRequest(requestConfigCaptor.capture()); return requestConfigCaptor.getValue(); } }