/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.hadoop.gateway.filter.rewrite.api; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.hadoop.gateway.util.urltemplate.Expander; import org.apache.hadoop.gateway.util.urltemplate.Matcher; import org.apache.hadoop.gateway.util.urltemplate.Parser; import org.apache.hadoop.gateway.util.urltemplate.Template; import org.easymock.EasyMock; import org.junit.Test; public class UrlRewriteProcessorTest { private static URL getTestResourceUrl( String name ) throws FileNotFoundException { name = UrlRewriteProcessorTest.class.getName().replaceAll( "\\.", "/" ) + "/" + name; URL url = ClassLoader.getSystemResource( name ); if( url == null ) { throw new FileNotFoundException( name ); } return url; } private static InputStream getTestResourceStream( String name ) throws IOException { URL url = getTestResourceUrl( name ); InputStream stream = url.openStream(); return stream; } private static Reader getTestResourceReader( String name, String charset ) throws IOException { return new InputStreamReader( getTestResourceStream( name ), charset ); } @Test public void testBasicPathRewrite() throws IOException, URISyntaxException { UrlRewriteEnvironment environment = EasyMock.createNiceMock( UrlRewriteEnvironment.class ); HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class ); HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class ); EasyMock.replay( environment, request, response ); UrlRewriteProcessor processor = new UrlRewriteProcessor(); UrlRewriteRulesDescriptor config = UrlRewriteRulesDescriptorFactory.load( "xml", getTestResourceReader( "rewrite.xml", "UTF-8" ) ); processor.initialize( environment, config ); Template inputUrl = Parser.parseLiteral( "test-scheme://test-host:1/test-input-path" ); Template outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.IN, null ); assertThat( "Expect rewrite to produce a new URL", outputUrl, notNullValue() ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "test-scheme://test-host:1/test-output-path" ) ); processor.destroy(); } @Test public void testMultipleIdenticalRewriteOutputRules() throws IOException, URISyntaxException { UrlRewriteEnvironment environment = EasyMock.createNiceMock( UrlRewriteEnvironment.class ); HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class ); HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class ); EasyMock.replay( environment, request, response ); UrlRewriteProcessor processor = new UrlRewriteProcessor(); UrlRewriteRulesDescriptor config = UrlRewriteRulesDescriptorFactory.load( "xml", getTestResourceReader( "rewrite-with-same-rules.xml", "UTF-8" ) ); processor.initialize( environment, config ); Template inputUrl = Parser.parseLiteral( "scheme://input-mock-host:42/test-input-path" ); Template outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, null ); assertThat( "Expect rewrite to produce a new URL", outputUrl, notNullValue() ); // Should always pick the first one. assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "output-mock-scheme-1://output-mock-host-1:42/test-input-path" ) ); inputUrl = Parser.parseLiteral( "mock-scheme://input-mock-host:42/no-query" ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, null ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "mock-scheme://output-mock-host-3:42/no-query" ) ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, "test-rule-4" ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "mock-scheme://output-mock-host-4:42/no-query" ) ); processor.destroy(); } @Test public void testIdenticalRewriteOutputRulesWithScopes() throws IOException, URISyntaxException { UrlRewriteEnvironment environment = EasyMock.createNiceMock( UrlRewriteEnvironment.class ); HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class ); HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class ); ArrayList<String> roles = new ArrayList<>(); roles.add("service-1"); EasyMock.expect(environment.resolve("service.role")).andReturn(roles).anyTimes(); EasyMock.replay( environment, request, response ); UrlRewriteProcessor processor = new UrlRewriteProcessor(); UrlRewriteRulesDescriptor config = UrlRewriteRulesDescriptorFactory.load( "xml", getTestResourceReader( "rewrite-with-same-rules-different-scope.xml", "UTF-8" ) ); processor.initialize( environment, config ); Template inputUrl = Parser.parseLiteral( "scheme://input-mock-host:42/test-input-path" ); Template outputUrl = processor.rewrite( environment, inputUrl, UrlRewriter.Direction.OUT, null ); assertThat( "Expect rewrite to produce a new URL", outputUrl, notNullValue() ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "output-mock-scheme-2://output-mock-host-2:42/test-input-path" ) ); inputUrl = Parser.parseLiteral( "mock-scheme://input-mock-host:42/no-query" ); outputUrl = processor.rewrite( environment, inputUrl, UrlRewriter.Direction.OUT, null ); roles.remove(0); roles.add("service-2"); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "mock-scheme://output-mock-host-5:42/no-query" ) ); outputUrl = processor.rewrite( environment, inputUrl, UrlRewriter.Direction.OUT, "service-2/test-rule-4" ); //no scope information should pick the first one assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "mock-scheme://output-mock-host-4:42/no-query" ) ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, "service-2/test-rule-4" ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "mock-scheme://output-mock-host-4:42/no-query" ) ); //Test the IN direction inputUrl = Parser.parseLiteral( "scheme://input-mock-host:42/test-input-path" ); outputUrl = processor.rewrite( environment, inputUrl, UrlRewriter.Direction.IN, null ); assertThat( "Expect rewrite to produce a new URL", outputUrl, notNullValue() ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "input-mock-scheme-2://input-mock-host-2:42/test-input-path" ) ); processor.destroy(); } @Test public void testRewriteViaRuleNameWithAmbiguousRules() throws IOException, URISyntaxException { UrlRewriteEnvironment environment = EasyMock.createNiceMock( UrlRewriteEnvironment.class ); HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class ); HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class ); EasyMock.replay( environment, request, response ); UrlRewriteProcessor processor = new UrlRewriteProcessor(); UrlRewriteRulesDescriptor config = UrlRewriteRulesDescriptorFactory.load( "xml", getTestResourceReader( "rewrite-with-same-rules.xml", "UTF-8" ) ); processor.initialize( environment, config ); Template inputUrl = Parser.parseLiteral( "input-mock-scheme-1://input-mock-host-1:42/test-input-path" ); Template outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, "test-rule-2" ); assertThat( "Expect rewrite to produce a new URL", outputUrl, notNullValue() ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "output-mock-scheme-2://output-mock-host-2:42/test-input-path" ) ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, "test-rule-1" ); assertThat( "Expect rewrite to produce a new URL", outputUrl, notNullValue() ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "output-mock-scheme-1://output-mock-host-1:42/test-input-path" ) ); processor.destroy(); } @Test public void testRewriteViaRuleWithComplexFlow() throws Exception { UrlRewriteEnvironment environment = EasyMock.createNiceMock( UrlRewriteEnvironment.class ); HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class ); HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class ); EasyMock.replay( environment, request, response ); UrlRewriteProcessor processor = new UrlRewriteProcessor(); UrlRewriteRulesDescriptor config = UrlRewriteRulesDescriptorFactory.load( "xml", getTestResourceReader( "rewrite.xml", "UTF-8" ) ); processor.initialize( environment, config ); Template inputUrl; Template outputUrl; inputUrl = Parser.parseLiteral( "test-scheme://test-host:777/test-path" ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.IN, "test-rule-with-complex-flow" ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "test-scheme-output://test-host-output:42/test-path-output/test-path" ) ); inputUrl = Parser.parseLiteral( "test-scheme://test-host:42/~/test-path" ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.IN, "test-rule-with-complex-flow" ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "test-scheme-output://test-host-output:777/test-path-output/test-home/test-path" ) ); processor.destroy(); } @Test public void testRewriteViaRuleWithWildcardTemplateAndOptionalQuery() throws Exception { UrlRewriteEnvironment environment = EasyMock.createNiceMock( UrlRewriteEnvironment.class ); HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class ); HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class ); EasyMock.replay( environment, request, response ); UrlRewriteProcessor processor = new UrlRewriteProcessor(); UrlRewriteRulesDescriptor config = UrlRewriteRulesDescriptorFactory.load( "xml", getTestResourceReader( "rewrite.xml", "UTF-8" ) ); processor.initialize( environment, config ); Template inputUrl; Template outputUrl; inputUrl = Parser.parseLiteral( "test-scheme-input://test-host-input:42/test-path-input-one/test-path-input-two?test-query-name=test-query-value" ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, "test-rule-2" ); assertThat( "Expect rewrite to contain the correct path and query.", outputUrl.toString(), is( "test-scheme-output://test-host-output:777/test-path-output/test-path-input-one/test-path-input-two?test-query-name=test-query-value" ) ); inputUrl = Parser.parseLiteral( "test-scheme-input://test-host-input:42/test-path-input-one/test-path-input-two" ); outputUrl = processor.rewrite( null, inputUrl, UrlRewriter.Direction.OUT, "test-rule-2" ); assertThat( "Expect rewrite to contain the correct path.", outputUrl.toString(), is( "test-scheme-output://test-host-output:777/test-path-output/test-path-input-one/test-path-input-two" ) ); processor.destroy(); } /** * Tests the rewrite pattern used for re-writing Solr urls passed through Knox. * * @throws Exception */ @Test public void testSolrRewrite() throws Exception { URI inputUri, outputUri; Matcher<Void> matcher; Matcher<Void>.Match match; Template input, pattern, template; inputUri = new URI( "https://hortonworks.sandbox.hdp.24.test:8443/gateway/sandbox/solr/TestCollection/select?q=*.*&wt=json&indent=true"); input = Parser.parseLiteral(inputUri.toString()); pattern = Parser.parseTemplate("*://*:*/**/solr/{collection=**}/{query=**}?{**}"); template = Parser.parseTemplate("http://sandbox.hortonworks.com/solr/{collection=**}/{query=**}?{**}"); matcher = new Matcher<Void>(); matcher.add(pattern, null); match = matcher.match(input); outputUri = Expander.expand(template, match.getParams(), null); final String reWrittenScheme = outputUri.getScheme(); assertEquals("http", reWrittenScheme); final String reWrittenHost = outputUri.getHost(); assertEquals("sandbox.hortonworks.com", reWrittenHost); final String reWrittenPath = outputUri.getPath(); assertEquals("/solr/TestCollection/select", reWrittenPath); // Whole thing is (non-deterministicly ordered around the &s): // "q=*.*&wt=json&indent=true" final String reWrittenQuery = outputUri.getQuery(); // Check individual parameters are present, and have the right value. final Map<String, String> reWrittenParams = mapUrlParameters(reWrittenQuery); assertTrue(reWrittenParams.containsKey("q")); assertEquals("*.*", reWrittenParams.get("q")); assertTrue(reWrittenParams.containsKey("wt")); assertEquals("json", reWrittenParams.get("wt")); assertEquals("true", reWrittenParams.get("indent")); } @Test public void testSolrRewriteDefaultPort() throws Exception { URI inputUri, outputUri; Matcher<Void> matcher; Matcher<Void>.Match match; Template input, pattern, template; inputUri = new URI( "https://hortonworks.sandbox.hdp.24.test/gateway/sandbox/solr/TestCollection/select?q=*.*&wt=json&indent=true"); input = Parser.parseLiteral(inputUri.toString()); pattern = Parser.parseTemplate("*://*:*/**/solr/{collection=**}/{query=**}?{**}"); template = Parser.parseTemplate("http://sandbox.hortonworks.com/solr/{collection=**}/{query=**}?{**}"); matcher = new Matcher<Void>(); matcher.add(pattern, null); match = matcher.match(input); outputUri = Expander.expand(template, match.getParams(), null); final String reWrittenScheme = outputUri.getScheme(); assertEquals("http", reWrittenScheme); final String reWrittenHost = outputUri.getHost(); assertEquals("sandbox.hortonworks.com", reWrittenHost); final String reWrittenPath = outputUri.getPath(); assertEquals("/solr/TestCollection/select", reWrittenPath); // Whole thing is (non-deterministicly ordered around the &s): // "q=*.*&wt=json&indent=true" final String reWrittenQuery = outputUri.getQuery(); // Check individual parameters are present, and have the right value. final Map<String, String> reWrittenParams = mapUrlParameters(reWrittenQuery); assertTrue(reWrittenParams.containsKey("q")); assertEquals("*.*", reWrittenParams.get("q")); assertTrue(reWrittenParams.containsKey("wt")); assertEquals("json", reWrittenParams.get("wt")); assertEquals("true", reWrittenParams.get("indent")); } /** * Turn a string containing URL parameters, e.g. * * <pre> * a=b&c=d&e=f * </pre> * * into a map such as * <table> * <tr> * <th>Key</th> * <th>Value</th> * </tr> * <tr> * <td>a</td> * <td>b</td> * </tr> * <tr> * <td>c</td> * <td>d</td> * </tr> * </table> * * @param urlParameters the URL parameter string. Expected to contain something of the form * "a=b&c=d" etc (i.e. Key=Value separated by &). * @return a map, with the key-values pairs representing the URL parameters. */ private Map<String, String> mapUrlParameters(String urlParameters) { final Map<String, String> map = new HashMap<String, String>(); for (String pair : urlParameters.split("&")) { String[] kv = pair.split("="); map.put(kv[0], kv[1]); } return map; } }