/** * Copyright (c) 2009-2011 VMware, Inc. All Rights Reserved. * * 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.springsource.insight.plugin.apache.http.hc3; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.TreeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpHost; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import org.mortbay.jetty.Handler; import org.mortbay.jetty.HttpConnection; import org.mortbay.jetty.Request; import org.mortbay.jetty.Server; import com.springsource.insight.collection.ObscuredValueSetMarker; import com.springsource.insight.collection.http.HttpObfuscator; import com.springsource.insight.collection.test.OperationCollectionAspectTestSupport; import com.springsource.insight.intercept.operation.Operation; import com.springsource.insight.intercept.operation.OperationFields; import com.springsource.insight.intercept.operation.OperationList; import com.springsource.insight.intercept.operation.OperationMap; import com.springsource.insight.intercept.operation.OperationUtils; import com.springsource.insight.util.ArrayUtil; import com.springsource.insight.util.MapUtil; import com.springsource.insight.util.StringFormatterUtils; import com.springsource.insight.util.StringUtil; /** * Tests {@link HttpClientExecutionCollectionAspect} using embedded * Jetty server */ public class HttpClientExecutionCollectionAspectTest extends OperationCollectionAspectTestSupport { private static final int TEST_PORT = 7365; private static final String TEST_URI = "http://localhost:" + TEST_PORT + "/"; private static Server SERVER; private HttpObfuscator originalObfuscator; private final ObscuredValueSetMarker marker = new ObscuredValueSetMarker(); public HttpClientExecutionCollectionAspectTest() { super(); } @BeforeClass public static void startEmbeddedServer() throws Exception { SERVER = new Server(TEST_PORT); SERVER.setHandler(new TestHandler()); System.out.println("Starting embedded server on port " + TEST_PORT); SERVER.start(); System.out.println("Started embedded server on port " + TEST_PORT); } @AfterClass public static void stopEmbeddedServer() throws Exception { if (SERVER != null) { System.out.println("Stopping embedded server"); SERVER.stop(); System.out.println("Server stopped"); } } @Before @Override public void setUp() { super.setUp(); HttpClientExecutionCollectionAspect aspectInstance = getAspect(); originalObfuscator = aspectInstance.getHttpHeadersObfuscator(); marker.clear(); aspectInstance.setHttpHeadersObfuscator(new HttpObfuscator(marker)); } @After @Override public void restore() { HttpClientExecutionCollectionAspect aspectInstance = getAspect(); aspectInstance.setHttpHeadersObfuscator(originalObfuscator); marker.clear(); super.restore(); } @Test public void testExecuteMethodOnly() throws IOException { runExecuteMethodTest("testExecuteMethodOnly", null, null); } @Test public void testExecuteMethodWithHost() throws Exception { runExecuteHostMethodTest("testExecuteMethodWithHost", null); } @Test public void testExecuteMethodWithState() throws Exception { runExecuteHostMethodTest("testExecuteMethodWithState", new HttpState()); } @Test public void testDefaultObfuscatedHeaders() throws Exception { runHeadersObfuscationTest("testDefaultObfuscatedHeaders", HttpObfuscator.DEFAULT_OBFUSCATED_HEADERS_LIST, true); } @Test public void testNonDefaultObfuscatedHeaders() throws Exception { runHeadersObfuscationTest("testNonDefaultObfuscatedHeaders", Arrays.asList("X-Hdr1", "X-Hdr2", "X-Hdr3"), false); } /** * This test is designed to ensure that when {@link HttpClient}'s * <code>execute</code> methods are called with a <code>null</code> * {@link HttpMethod} argument the aspect uses the {@link HttpPlaceholderMethod} * values instead */ @Test public void testMissingHttpMethodArgument() throws HttpException, IOException { HttpClient httpClient = new HttpClient(); for (MissingMethodTestRunner runner : MissingMethodTestRunner.values()) { String excValue = null; try { int statusCode = runner.executeMethod(httpClient); fail("Unexpected success for " + runner + ": " + statusCode); } catch (IllegalArgumentException e) { // expected - thrown by HttpClient when null method supplied excValue = StringFormatterUtils.formatStackTrace(e); } Operation op = assertExecutionResult(HttpClientDefinitions.PLACEHOLDER_URI_VALUE, HttpPlaceholderMethod.PLACEHOLDER, HttpClientDefinitions.FAILED_CALL_STATUS_CODE, true); assertEquals("Mismatched exception value for " + runner, excValue, op.get(OperationFields.EXCEPTION, String.class)); Mockito.reset(spiedOperationCollector); // prepare for next iteration } } static enum MissingMethodTestRunner { METHODONLY { @Override public int executeMethod(HttpClient httpClient) throws HttpException, IOException { return httpClient.executeMethod(null); } }, HOSTANDMETHOD { @Override public int executeMethod(HttpClient httpClient) throws HttpException, IOException { return httpClient.executeMethod(new HostConfiguration(), null); } }, HOSTMETHODSTATE { @Override public int executeMethod(HttpClient httpClient) throws HttpException, IOException { return httpClient.executeMethod(new HostConfiguration(), null, new HttpState()); } }; public abstract int executeMethod(HttpClient httpClient) throws HttpException, IOException; } private Map<String, String> runHeadersObfuscationTest(String testName, Collection<String> headerSet, boolean defaultHeaders) throws IOException { HttpClient httpClient = new HttpClient(); String uri = createTestUri(testName); HttpMethod method = new GetMethod(uri); for (String name : headerSet) { if ("WWW-Authenticate".equalsIgnoreCase(name)) { continue; // this is a response header } method.addRequestHeader(name, name); } HttpClientExecutionCollectionAspect aspectInstance = getAspect(); HttpObfuscator obfuscator = aspectInstance.getHttpHeadersObfuscator(); if (!defaultHeaders) { for (String name : HttpObfuscator.DEFAULT_OBFUSCATED_HEADERS_LIST) { if ("WWW-Authenticate".equalsIgnoreCase(name)) { continue; // this is a response header } method.addRequestHeader(name, name); } obfuscator.incrementalUpdate(HttpObfuscator.OBFUSCATED_HEADERS_SETTING, StringUtil.implode(headerSet, ",")); } int response = httpClient.executeMethod(method); Operation op = assertExecutionResult(uri, method, response, false); OperationMap reqDetails = op.get("request", OperationMap.class); OperationList reqHeaders = reqDetails.get("headers", OperationList.class); Map<String, String> requestHeaders = toHeadersMap(reqHeaders); OperationMap rspDetails = op.get("response", OperationMap.class); OperationList rspHeaders = rspDetails.get("headers", OperationList.class); Map<String, String> responseHeaders = toHeadersMap(rspHeaders); Map<String, String> hdrsMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); if (MapUtil.size(requestHeaders) > 0) { hdrsMap.putAll(requestHeaders); } if (MapUtil.size(responseHeaders) > 0) { hdrsMap.putAll(responseHeaders); } Collection<?> obscuredValues = (ObscuredValueSetMarker) obfuscator.getSensitiveValueMarker(); for (String name : headerSet) { String value = hdrsMap.get(name); assertNotNull("Missing value for header: " + name, value); assertTrue("Unobscured value of " + name, obscuredValues.contains(value)); } if (!defaultHeaders) { for (String name : HttpObfuscator.DEFAULT_OBFUSCATED_HEADERS_LIST) { assertFalse("Un-necessarily obscured value of " + name, obscuredValues.contains(name)); } } return hdrsMap; } private void runExecuteHostMethodTest(String testName, HttpState state) throws Exception { HttpHost host = new HttpHost("localhost", TEST_PORT); HostConfiguration hostConfig = new HostConfiguration(); hostConfig.setHost(host); runExecuteMethodTest(testName, hostConfig, state); } private void runExecuteMethodTest(String testName, HostConfiguration host, HttpState state) throws IOException { HttpClient httpClient = new HttpClient(); String uri = createTestUri(testName); HttpMethod method = new GetMethod(uri); int response; if (host == null) { response = httpClient.executeMethod(method); } else { response = (state == null) ? httpClient.executeMethod(host, method) : httpClient.executeMethod(host, method, state) ; } handleResponse(testName, uri, method, response, true); } private Operation handleResponse(String testName, String uri, HttpMethod method, int response, boolean checkHeaders) throws IOException { InputStream body = method.getResponseBodyAsStream(); try { String content = IOUtils.toString(body); assertEquals("Mismatched content", createResponseContent(testName), content); } finally { body.close(); } return assertExecutionResult(uri, method, response, checkHeaders); } private Operation assertExecutionResult(String uri, HttpMethod method, int response, boolean checkHeaders) { return assertExecutionResult(getLastEntered(), uri, method, response, checkHeaders); } private Operation assertExecutionResult(Operation op, String uri, HttpMethod method, int response, boolean checkHeaders) { assertNotNull("No operation extracted", op); assertEquals("Mismatched operation type for " + uri, HttpClientDefinitions.TYPE, op.getType()); assertRequestDetails(uri, op.get("request", OperationMap.class), method, checkHeaders); assertResponseDetails(uri, op.get("response", OperationMap.class), method, response, checkHeaders); return op; } private void assertRequestDetails(String uri, OperationMap details, HttpMethod method, boolean checkHeaders) { assertEquals("Mismatched method", method.getName(), details.get("method")); assertEquals("Mismatched URI", HttpClientExecutionCollectionAspect.getUri(method), details.get(OperationFields.URI)); assertEquals("Mismatched protocol", HttpClientExecutionCollectionAspect.createVersionValue(method), details.get("protocol")); if (checkHeaders) { assertHeadersContents(uri, "request", details, method, true); } } private void assertResponseDetails(String uri, OperationMap details, HttpMethod method, int statusCode, boolean checkHeaders) { assertEquals("Mismatched status code", statusCode, details.getInt("statusCode", (-1))); assertEquals("Mismatched reason phrase", method.getStatusText(), details.get("reasonPhrase", String.class)); if (checkHeaders) { assertHeadersContents(uri, "response", details, method, false); } } private void assertHeadersContents(String uri, String type, OperationMap details, HttpMethod method, boolean useRequestHeaders) { OperationList headers = details.get("headers", OperationList.class); assertNotNull("No " + type + " headers for " + uri, headers); Header[] hdrArray = useRequestHeaders ? method.getRequestHeaders() : method.getResponseHeaders(); int numHdrs = (hdrArray == null) ? 0 : hdrArray.length; assertEquals("Mismatched " + type + " number of headers", numHdrs, headers.size()); if (numHdrs <= 0) { return; } Map<String, String> opHdrs = toHeadersMap(headers); Map<String, String> msgHdrs = toHeadersMap(hdrArray); assertEquals("Mismatched " + type + " headers map size", msgHdrs.size(), opHdrs.size()); for (Map.Entry<String, String> hdrValue : msgHdrs.entrySet()) { String name = hdrValue.getKey(); assertEquals("Mismatched " + type + " value for " + name + " header", hdrValue.getValue(), opHdrs.get(name)); } } private Map<String, String> toHeadersMap(Header... headers) { if (ArrayUtil.length(headers) <= 0) { return Collections.emptyMap(); } Map<String, String> hdrsMap = new TreeMap<String, String>(); for (Header hdrValue : headers) { String name = hdrValue.getName(); String value = hdrValue.getValue(); hdrsMap.put(name, value); } return hdrsMap; } private Map<String, String> toHeadersMap(OperationList headers) { if ((headers == null) || (headers.size() <= 0)) { return Collections.emptyMap(); } Map<String, String> hdrsMap = new TreeMap<String, String>(); for (int index = 0; index < headers.size(); index++) { OperationMap hdrValue = headers.get(index, OperationMap.class); String name = hdrValue.get(OperationUtils.NAME_KEY, String.class); String value = hdrValue.get(OperationUtils.VALUE_KEY, String.class); hdrsMap.put(name, value); } return hdrsMap; } @Override public HttpClientExecutionCollectionAspect getAspect() { return HttpClientExecutionCollectionAspect.aspectOf(); } private static final class TestHandler implements Handler { private Server server; private boolean started; protected TestHandler() { super(); } public void addLifeCycleListener(Listener listener) { // ignored } public void removeLifeCycleListener(Listener listener) { // ignored } public void stop() throws Exception { if (!started) { throw new IllegalStateException("Not started"); } started = false; } public void start() throws Exception { if (started) { throw new IllegalStateException("Double start"); } started = true; } public boolean isStopping() { return true; } public boolean isStopped() { return !started; } public boolean isStarting() { return true; } public boolean isStarted() { return started; } public boolean isRunning() { return started; } public boolean isFailed() { return false; } public Server getServer() { return this.server; } public void setServer(Server serverInstance) { this.server = serverInstance; } public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException { int namePos = target.lastIndexOf('/'); String testName = target.substring(namePos + 1); response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/xml;charset=utf-8"); response.addHeader("X-Test-Name", testName); response.addHeader("WWW-Authenticate", "allowed"); Writer writer = response.getWriter(); try { writer.append(createResponseContent(testName)); } finally { writer.close(); } Request baseRequest = (request instanceof Request) ? (Request) request : HttpConnection.getCurrentConnection().getRequest(); baseRequest.setHandled(true); } public void destroy() { if (this.server != null) this.server = null; } } static String createResponseContent(String testName) { return "<test name=\"" + testName + "\" />"; } static String createTestUri(String testName) { return TEST_URI + testName; } }