package org.jolokia.jvmagent.handler; /* * Copyright 2009-2013 Roland Huss * * 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. */ import java.io.*; import java.net.*; import java.text.SimpleDateFormat; import java.util.*; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import org.easymock.EasyMock; import org.jolokia.config.ConfigKey; import org.jolokia.config.Configuration; import org.jolokia.restrictor.*; import org.jolokia.util.LogHandler; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.testng.annotations.*; import static org.easymock.EasyMock.*; import static org.testng.Assert.*; /** * @author roland * @since 24.10.10 */ public class JolokiaHttpHandlerTest { private JolokiaHttpHandler handler; @BeforeMethod public void setup() { handler = new JolokiaHttpHandler(getConfig()); handler.start(false); } @AfterMethod public void tearDown() { handler.stop(); } @Test public void testCallbackGet() throws IOException, URISyntaxException { HttpExchange exchange = prepareExchange("http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage?callback=data"); // Simple GET method expect(exchange.getRequestMethod()).andReturn("GET"); Headers header = new Headers(); ByteArrayOutputStream out = prepareResponse(handler, exchange, header); handler.doHandle(exchange); assertEquals(header.getFirst("content-type"),"text/javascript; charset=utf-8"); String result = out.toString("utf-8"); assertTrue(result.endsWith("});")); assertTrue(result.startsWith("data({")); } @Test public void testCallbackPost() throws URISyntaxException, IOException, java.text.ParseException { HttpExchange exchange = prepareExchange("http://localhost:8080/jolokia?callback=data", "Content-Type","text/plain; charset=UTF-8", "Origin",null ); // Simple GET method prepareMemoryPostReadRequest(exchange); Headers header = new Headers(); ByteArrayOutputStream out = prepareResponse(handler, exchange, header); handler.doHandle(exchange); assertEquals(header.getFirst("content-type"),"text/javascript; charset=utf-8"); String result = out.toString("utf-8"); assertTrue(result.endsWith("});")); assertTrue(result.startsWith("data({")); assertTrue(result.contains("\"used\"")); assertEquals(header.getFirst("Cache-Control"),"no-cache"); assertEquals(header.getFirst("Pragma"),"no-cache"); SimpleDateFormat rfc1123Format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); rfc1123Format.setTimeZone(TimeZone.getTimeZone("GMT")); String expires = header.getFirst("Expires"); String date = header.getFirst("Date"); Date parsedExpires = rfc1123Format.parse(expires); Date parsedDate = rfc1123Format.parse(date); assertTrue(parsedExpires.before(parsedDate) || parsedExpires.equals(parsedDate)); Date now = new Date(); assertTrue(parsedExpires.before(now) || parsedExpires.equals(now)); } @Test public void invalidMethod() throws URISyntaxException, IOException, ParseException { HttpExchange exchange = prepareExchange("http://localhost:8080/"); // Simple GET method expect(exchange.getRequestMethod()).andReturn("PUT"); Headers header = new Headers(); ByteArrayOutputStream out = prepareResponse(handler, exchange, header); handler.doHandle(exchange); JSONObject resp = (JSONObject) new JSONParser().parse(out.toString()); assertTrue(resp.containsKey("error")); assertEquals(resp.get("error_type"), IllegalArgumentException.class.getName()); assertTrue(((String) resp.get("error")).contains("PUT")); } @Test(expectedExceptions = IllegalStateException.class,expectedExceptionsMessageRegExp = ".*not.*started.*") public void handlerNotStarted() throws URISyntaxException, IOException { JolokiaHttpHandler newHandler = new JolokiaHttpHandler(getConfig()); newHandler.doHandle(prepareExchange("http://localhost:8080/")); } @Test public void customRestrictor() throws URISyntaxException, IOException, ParseException { System.setProperty("jolokia.test1.policy.location","access-restrictor.xml"); System.setProperty("jolokia.test2.policy.location","access-restrictor"); for (String[] params : new String[][] { {"classpath:/access-restrictor.xml","not allowed"}, {"file:///not-existing.xml","No access"}, {"classpath:/${prop:jolokia.test1.policy.location}", "not allowed"}, {"classpath:/${prop:jolokia.test2.policy.location}.xml", "not allowed"} }) { Configuration config = getConfig(ConfigKey.POLICY_LOCATION,params[0]); JSONObject resp = simpleMemoryGetReadRequest(config); assertTrue(resp.containsKey("error")); assertTrue(((String) resp.get("error")).contains(params[1])); } } @Test public void customTestRestrictorTrue() throws URISyntaxException, IOException, ParseException { Configuration config = getConfig(ConfigKey.RESTRICTOR_CLASS, AllowAllRestrictor.class.getName()); JSONObject resp = simpleMemoryGetReadRequest(config); assertFalse(resp.containsKey("error")); } @Test public void customTestRestrictorFalse() throws URISyntaxException, IOException, ParseException { Configuration config = getConfig(ConfigKey.RESTRICTOR_CLASS, DenyAllRestrictor.class.getName()); JSONObject resp = simpleMemoryGetReadRequest(config); assertTrue(resp.containsKey("error")); assertTrue(((String) resp.get("error")).contains("No access")); } @Test public void customTestRestrictorWithConfigTrue() throws URISyntaxException, IOException, ParseException { Configuration config = getConfig( ConfigKey.RESTRICTOR_CLASS, TestRestrictorWithConfig.class.getName(), ConfigKey.POLICY_LOCATION, "true" ); JSONObject resp = simpleMemoryGetReadRequest(config); assertFalse(resp.containsKey("error")); } @Test public void customTestRestrictorWithConfigFalse() throws URISyntaxException, IOException, ParseException { Configuration config = getConfig( ConfigKey.RESTRICTOR_CLASS, TestRestrictorWithConfig.class.getName(), ConfigKey.POLICY_LOCATION, "false" ); JSONObject resp = simpleMemoryGetReadRequest(config); assertTrue(resp.containsKey("error")); assertTrue(((String) resp.get("error")).contains("No access")); } @Test public void restrictorWithNoReverseDnsLookup() throws URISyntaxException, IOException, ParseException { Configuration config = getConfig( ConfigKey.RESTRICTOR_CLASS, TestReverseDnsLookupRestrictor.class.getName(), ConfigKey.ALLOW_DNS_REVERSE_LOOKUP, "false"); InetSocketAddress address = new InetSocketAddress(8080); TestReverseDnsLookupRestrictor.expectedRemoteHostsToCheck = new String[] { address.getAddress().getHostAddress() }; JSONObject resp = simpleMemoryGetReadRequest(config); assertFalse(resp.containsKey("error")); } @Test public void restrictorWithReverseDnsLookup() throws URISyntaxException, IOException, ParseException { Configuration config = getConfig( ConfigKey.RESTRICTOR_CLASS, TestReverseDnsLookupRestrictor.class.getName(), ConfigKey.ALLOW_DNS_REVERSE_LOOKUP, "true"); InetSocketAddress address = new InetSocketAddress(8080); TestReverseDnsLookupRestrictor.expectedRemoteHostsToCheck = new String[] { address.getHostName(), address.getAddress().getHostAddress() }; JSONObject resp = simpleMemoryGetReadRequest(config); assertFalse(resp.containsKey("error")); } @Test public void customLogHandler1() throws Exception { JolokiaHttpHandler handler = new JolokiaHttpHandler(getConfig(), new CustomLogHandler()); handler.start(false); handler.stop(); assertTrue(CustomLogHandler.infoCount > 0); } @Test public void customLogHandler2() throws Exception { CustomLogHandler.infoCount = 0; JolokiaHttpHandler handler = new JolokiaHttpHandler(getConfig(ConfigKey.LOGHANDLER_CLASS,CustomLogHandler.class.getName())); handler.start(false); handler.stop(); assertTrue(CustomLogHandler.infoCount > 0); } @Test(expectedExceptions = IllegalArgumentException.class) public void invalidCustomLogHandler() throws Exception { new JolokiaHttpHandler(getConfig(ConfigKey.LOGHANDLER_CLASS,InvalidLogHandler.class.getName())); } @Test public void simlePostRequestWithCors() throws URISyntaxException, IOException { HttpExchange exchange = prepareExchange("http://localhost:8080/jolokia", "Content-Type","text/plain; charset=UTF-8", "Origin","http://localhost:8080/" ); prepareMemoryPostReadRequest(exchange); Headers header = new Headers(); prepareResponse(handler, exchange, header); handler.doHandle(exchange); assertEquals(header.getFirst("content-type"), "text/plain; charset=utf-8"); assertEquals(header.getFirst("Access-Control-Allow-Origin"),"http://localhost:8080/"); } private void prepareMemoryPostReadRequest(HttpExchange pExchange) throws UnsupportedEncodingException { expect(pExchange.getRequestMethod()).andReturn("POST"); String response = "{\"mbean\":\"java.lang:type=Memory\",\"attribute\":\"HeapMemoryUsage\",\"type\":\"read\"}"; byte[] buf = response.getBytes("utf-8"); InputStream is = new ByteArrayInputStream(buf); expect(pExchange.getRequestBody()).andReturn(is); } @Test public void preflightCheck() throws URISyntaxException, IOException { HttpExchange exchange = prepareExchange("http://localhost:8080/", "Origin","http://localhost:8080/", "Access-Control-Request-Headers","X-Bla, X-Blub"); expect(exchange.getRequestMethod()).andReturn("OPTIONS"); Headers header = new Headers(); ByteArrayOutputStream out = prepareResponse(handler, exchange, header); handler.doHandle(exchange); assertEquals(header.getFirst("Access-Control-Allow-Origin"),"http://localhost:8080/"); assertEquals(header.getFirst("Access-Control-Allow-Headers"),"X-Bla, X-Blub"); assertNotNull(header.getFirst("Access-Control-Allow-Max-Age")); } @Test public void usingStreamingJSON() throws IOException, URISyntaxException, ParseException { Configuration config = getConfig(ConfigKey.STREAMING, "true"); JolokiaHttpHandler newHandler = new JolokiaHttpHandler(config); newHandler.start(false); HttpExchange exchange = prepareExchange("http://localhost:8080/jolokia/list?maxDepth=1"); expect(exchange.getRequestMethod()).andReturn("GET"); Headers header = new Headers(); ByteArrayOutputStream out = prepareResponse(newHandler, exchange, header); newHandler.doHandle(exchange); String result = out.toString("utf-8"); assertNull(header.getFirst("Content-Length")); JSONObject resp = (JSONObject) new JSONParser().parse(result); assertTrue(resp.containsKey("value")); } private HttpExchange prepareExchange(String pUri) throws URISyntaxException { return prepareExchange(pUri,"Origin",null); } private HttpExchange prepareExchange(String pUri,String ... pHeaders) throws URISyntaxException { HttpExchange exchange = EasyMock.createMock(HttpExchange.class); URI uri = new URI(pUri); expect(exchange.getRequestURI()).andReturn(uri); expect(exchange.getRemoteAddress()).andReturn(new InetSocketAddress(8080)); Headers headers = new Headers(); expect(exchange.getRequestHeaders()).andReturn(headers).anyTimes(); for (int i = 0; i < pHeaders.length; i += 2) { headers.set(pHeaders[i], pHeaders[i + 1]); } return exchange; } private JSONObject simpleMemoryGetReadRequest(Configuration config) throws URISyntaxException, IOException, ParseException { JolokiaHttpHandler newHandler = new JolokiaHttpHandler(config); HttpExchange exchange = prepareExchange("http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage"); // Simple GET method expect(exchange.getRequestMethod()).andReturn("GET"); Headers header = new Headers(); ByteArrayOutputStream out = prepareResponse(handler, exchange, header); newHandler.start(false); try { newHandler.doHandle(exchange); } finally { newHandler.stop(); } return (JSONObject) new JSONParser().parse(out.toString()); } private ByteArrayOutputStream prepareResponse(JolokiaHttpHandler handler, HttpExchange exchange, Headers header) throws IOException { expect(exchange.getResponseHeaders()).andReturn(header).anyTimes(); exchange.sendResponseHeaders(anyInt(),anyLong()); ByteArrayOutputStream out = new ByteArrayOutputStream(); expect(exchange.getResponseBody()).andReturn(out); replay(exchange); return out; } private static boolean debugToggle = false; public Configuration getConfig(Object ... extra) { ArrayList list = new ArrayList(); list.add(ConfigKey.AGENT_CONTEXT); list.add("/jolokia"); list.add(ConfigKey.DEBUG); list.add(debugToggle ? "true" : "false"); list.add(ConfigKey.AGENT_ID); list.add(UUID.randomUUID().toString()); for (Object e : extra) { list.add(e); } Configuration config = new Configuration(list.toArray()); debugToggle = !debugToggle; return config; } public static class CustomLogHandler implements LogHandler { private static int debugCount, infoCount, errorCount; public CustomLogHandler() { debugCount = 0; infoCount = 0; errorCount = 0; } @Override public void debug(String message) { debugCount++; } @Override public void info(String message) { infoCount++; } @Override public void error(String message, Throwable t) { errorCount++; } } private class InvalidLogHandler implements LogHandler { @Override public void debug(String message) { } @Override public void info(String message) { } @Override public void error(String message, Throwable t) { } } }