/* * Copyright 2013-2016 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.zuul.filters.route; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.zip.GZIPOutputStream; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.context.embedded.LocalServerPort; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import static org.springframework.boot.test.util.EnvironmentTestUtils.addEnvironment; import static org.springframework.util.StreamUtils.copyToByteArray; import static org.springframework.util.StreamUtils.copyToString; /** * @author Andreas Kluth * @author Spencer Gibb */ @RunWith(SpringRunner.class) @SpringBootTest(classes = SampleApplication.class, webEnvironment = RANDOM_PORT, properties = "server.contextPath: /app") @DirtiesContext public class SimpleHostRoutingFilterTests { private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @LocalServerPort private int port; @After public void clear() { if (this.context != null) { this.context.close(); } } @Test public void connectionPropertiesAreApplied() { addEnvironment(this.context, "zuul.host.maxTotalConnections=100", "zuul.host.maxPerRouteConnections=10"); setupContext(); PoolingHttpClientConnectionManager connMgr = getFilter().newConnectionManager(); assertEquals(100, connMgr.getMaxTotal()); assertEquals(10, connMgr.getDefaultMaxPerRoute()); } @Test public void validateSslHostnamesByDefault() { setupContext(); assertTrue("Hostname verification should be enabled by default", getFilter().isSslHostnameValidationEnabled()); } @Test public void validationOfSslHostnamesCanBeDisabledViaProperty() { addEnvironment(this.context, "zuul.sslHostnameValidationEnabled=false"); setupContext(); assertFalse("Hostname verification should be disabled via property", getFilter().isSslHostnameValidationEnabled()); } @Test public void defaultPropertiesAreApplied() { setupContext(); PoolingHttpClientConnectionManager connMgr = getFilter().newConnectionManager(); assertEquals(200, connMgr.getMaxTotal()); assertEquals(20, connMgr.getDefaultMaxPerRoute()); } @Test public void deleteRequestBuiltWithBody() { setupContext(); InputStreamEntity inputStreamEntity = new InputStreamEntity(new ByteArrayInputStream(new byte[]{1})); HttpRequest httpRequest = getFilter().buildHttpRequest("DELETE", "uri", inputStreamEntity, new LinkedMultiValueMap<String, String>(), new LinkedMultiValueMap<String, String>(), new MockHttpServletRequest()); assertTrue(httpRequest instanceof HttpEntityEnclosingRequest); HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) httpRequest; assertTrue(httpEntityEnclosingRequest.getEntity() != null); } @Test public void httpClientDoesNotDecompressEncodedData() throws Exception { setupContext(); InputStreamEntity inputStreamEntity = new InputStreamEntity(new ByteArrayInputStream(new byte[]{1})); HttpRequest httpRequest = getFilter().buildHttpRequest("GET", "/app/compressed/get/1", inputStreamEntity, new LinkedMultiValueMap<String, String>(), new LinkedMultiValueMap<String, String>(), new MockHttpServletRequest()); CloseableHttpResponse response = getFilter().newClient().execute(new HttpHost("localhost", this.port), httpRequest); assertEquals(200, response.getStatusLine().getStatusCode()); byte[] responseBytes = copyToByteArray(response.getEntity().getContent()); assertTrue(Arrays.equals(GZIPCompression.compress("Get 1"), responseBytes)); } @Test public void httpClientPreservesUnencodedData() throws Exception { setupContext(); InputStreamEntity inputStreamEntity = new InputStreamEntity(new ByteArrayInputStream(new byte[]{1})); HttpRequest httpRequest = getFilter().buildHttpRequest("GET", "/app/get/1", inputStreamEntity, new LinkedMultiValueMap<String, String>(), new LinkedMultiValueMap<String, String>(), new MockHttpServletRequest()); CloseableHttpResponse response = getFilter().newClient().execute(new HttpHost("localhost", this.port), httpRequest); assertEquals(200, response.getStatusLine().getStatusCode()); String responseString = copyToString(response.getEntity().getContent(), Charset.forName("UTF-8")); assertTrue("Get 1".equals(responseString)); } private void setupContext() { this.context.register(PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class); this.context.refresh(); } private SimpleHostRoutingFilter getFilter() { return this.context.getBean(SimpleHostRoutingFilter.class); } @Configuration @EnableConfigurationProperties(ZuulProperties.class) protected static class TestConfiguration { @Bean SimpleHostRoutingFilter simpleHostRoutingFilter(ZuulProperties zuulProperties) { return new SimpleHostRoutingFilter(new ProxyRequestHelper(), zuulProperties); } } } @Configuration @EnableAutoConfiguration @RestController class SampleApplication { public static void main(String[] args) { SpringApplication.run(SampleApplication.class, args); } @RequestMapping(value = "/compressed/get/{id}", method = RequestMethod.GET) public byte[] getCompressed(@PathVariable String id, HttpServletResponse response) throws IOException { response.setHeader("content-encoding", "gzip"); return GZIPCompression.compress("Get " + id); } @RequestMapping(value = "/get/{id}", method = RequestMethod.GET) public String getString(@PathVariable String id, HttpServletResponse response) throws IOException { return "Get " + id; } } class GZIPCompression { public static byte[] compress(final String str) throws IOException { if ((str == null) || (str.length() == 0)) { return null; } ByteArrayOutputStream obj = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(obj); gzip.write(str.getBytes("UTF-8")); gzip.close(); return obj.toByteArray(); } }