diff options
Diffstat (limited to 'container-core/src/test')
5 files changed, 620 insertions, 0 deletions
diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java new file mode 100644 index 00000000000..dfd240d3723 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterRequestTest.java @@ -0,0 +1,173 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.filter; + +import com.yahoo.jdisc.http.Cookie; +import com.yahoo.jdisc.http.HttpHeaders; +import com.yahoo.jdisc.http.server.jetty.JettyMockRequestBuilder; +import com.yahoo.jdisc.http.servlet.ServletRequest; +import org.eclipse.jetty.server.Request; +import org.junit.Before; +import org.junit.Test; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.yahoo.jdisc.http.HttpRequest.Version; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test the parts of the DiscFilterRequest API that are implemented + * by ServletFilterRequest, both directly and indirectly via + * {@link com.yahoo.jdisc.http.servlet.ServletRequest}. + * + * @author gjoranv + */ +public class ServletFilterRequestTest { + + private final String host = "host1"; + private final int port = 8080; + private final String path = "/path1"; + private final String paramName = "param1"; + private final String paramValue = "p1"; + private final String listParamName = "listParam"; + private final String[] listParamValue = new String[]{"1", "2"}; + private final String headerName = "header1"; + private final String headerValue = "h1"; + private final String attributeName = "attribute1"; + private final String attributeValue = "a1"; + + private URI uri; + private DiscFilterRequest filterRequest; + private ServletRequest parentRequest; + + @Before + public void init() throws Exception { + uri = new URI("http", null, host, port, path, paramName + "=" + paramValue, null); + + filterRequest = new ServletFilterRequest(newServletRequest()); + parentRequest = ((ServletFilterRequest)filterRequest).getServletRequest(); + } + + private ServletRequest newServletRequest() { + Request parent = JettyMockRequestBuilder.newBuilder() + .remote("1.2.3.4", host, port) + .header(headerName, List.of(headerValue)) + .parameter(paramName, List.of(paramValue)) + .parameter(listParamName, List.of(listParamValue)) + .attribute(attributeName, attributeValue) + .build(); + return new ServletRequest(parent, uri); + } + + @Test + public void parent_properties_are_propagated_to_disc_filter_request() throws Exception { + assertEquals(filterRequest.getVersion(), Version.HTTP_1_1); + assertEquals(filterRequest.getMethod(), "GET"); + assertEquals(filterRequest.getUri(), uri); + assertEquals(filterRequest.getRemoteHost(), host); + assertEquals(filterRequest.getRemotePort(), port); + assertEquals(filterRequest.getRequestURI(), path); // getRequestUri return only the path by design + + assertEquals(filterRequest.getParameter(paramName), paramValue); + assertEquals(filterRequest.getParameterMap().get(paramName), + Collections.singletonList(paramValue)); + assertEquals(filterRequest.getParameterValuesAsList(listParamName), Arrays.asList(listParamValue)); + + assertEquals(filterRequest.getHeader(headerName), headerValue); + assertEquals(filterRequest.getAttribute(attributeName), attributeValue); + } + + @Test + public void untreatedHeaders_is_populated_from_the_parent_request() { + assertEquals(filterRequest.getUntreatedHeaders().getFirst(headerName), headerValue); + } + + @Test + @SuppressWarnings("deprecation") + public void uri_can_be_set() throws Exception { + URI newUri = new URI("http", null, host, port + 1, path, paramName + "=" + paramValue, null); + filterRequest.setUri(newUri); + + assertEquals(filterRequest.getUri(), newUri); + assertEquals(parentRequest.getUri(), newUri); + } + + @Test + public void attributes_can_be_set() throws Exception { + String name = "newAttribute"; + String value = name + "Value"; + filterRequest.setAttribute(name, value); + + assertEquals(filterRequest.getAttribute(name), value); + assertEquals(parentRequest.getAttribute(name), value); + } + + @Test + public void attributes_can_be_removed() { + filterRequest.removeAttribute(attributeName); + + assertEquals(filterRequest.getAttribute(attributeName), null); + assertEquals(parentRequest.getAttribute(attributeName), null); + } + + @Test + public void headers_can_be_set() throws Exception { + String name = "myHeader"; + String value = name + "Value"; + filterRequest.setHeaders(name, value); + + assertEquals(filterRequest.getHeader(name), value); + assertEquals(parentRequest.getHeader(name), value); + } + + @Test + public void headers_can_be_removed() throws Exception { + filterRequest.removeHeaders(headerName); + + assertEquals(filterRequest.getHeader(headerName), null); + assertEquals(parentRequest.getHeader(headerName), null); + } + + @Test + public void headers_can_be_added() { + String value = "h2"; + filterRequest.addHeader(headerName, value); + + List<String> expected = Arrays.asList(headerValue, value); + assertEquals(filterRequest.getHeadersAsList(headerName), expected); + assertEquals(Collections.list(parentRequest.getHeaders(headerName)), expected); + } + + @Test + public void cookies_can_be_added_and_removed() { + Cookie cookie = new Cookie("name", "value"); + filterRequest.addCookie(JDiscCookieWrapper.wrap(cookie)); + + assertEquals(filterRequest.getCookies(), Collections.singletonList(cookie)); + assertEquals(parentRequest.getCookies().length, 1); + + javax.servlet.http.Cookie servletCookie = parentRequest.getCookies()[0]; + assertEquals(servletCookie.getName(), cookie.getName()); + assertEquals(servletCookie.getValue(), cookie.getValue()); + + filterRequest.clearCookies(); + assertTrue(filterRequest.getCookies().isEmpty()); + assertEquals(parentRequest.getCookies().length, 0); + } + + @Test + public void character_encoding_can_be_set() throws Exception { + // ContentType must be non-null before setting character encoding + filterRequest.setHeaders(HttpHeaders.Names.CONTENT_TYPE, ""); + + String encoding = "myEncoding"; + filterRequest.setCharacterEncoding(encoding); + + assertTrue(filterRequest.getCharacterEncoding().contains(encoding)); + assertTrue(parentRequest.getCharacterEncoding().contains(encoding)); + } + +} diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterResponseTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterResponseTest.java new file mode 100644 index 00000000000..1b49ad7ddd1 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/filter/ServletFilterResponseTest.java @@ -0,0 +1,87 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.filter; + +import com.yahoo.jdisc.http.Cookie; +import com.yahoo.jdisc.http.HttpHeaders; +import com.yahoo.jdisc.http.servlet.ServletResponse; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; + +/** + * @author gjoranv + * @since 5.27 + */ +public class ServletFilterResponseTest { + + private final String headerName = "header1"; + private final String headerValue = "h1"; + + private DiscFilterResponse filterResponse; + private ServletResponse parentResponse; + + @Before + public void init() throws Exception { + filterResponse = new ServletFilterResponse(newServletResponse()); + parentResponse = ((ServletFilterResponse)filterResponse).getServletResponse(); + + } + + private ServletResponse newServletResponse() throws Exception { + MockServletResponse parent = new MockServletResponse(); + parent.addHeader(headerName, headerValue); + return new ServletResponse(parent); + } + + + @Test + public void headers_can_be_set() throws Exception { + String name = "myHeader"; + String value = name + "Value"; + filterResponse.setHeaders(name, value); + + assertEquals(filterResponse.getHeader(name), value); + assertEquals(parentResponse.getHeader(name), value); + } + + @Test + public void headers_can_be_added() throws Exception { + String newValue = "h2"; + filterResponse.addHeader(headerName, newValue); + + // The DiscFilterResponse has no getHeaders() + assertEquals(filterResponse.getHeader(headerName), newValue); + + assertEquals(parentResponse.getHeaders(headerName), Arrays.asList(headerValue, newValue)); + } + + @Test + public void headers_can_be_removed() throws Exception { + filterResponse.removeHeaders(headerName); + + assertEquals(filterResponse.getHeader(headerName), null); + assertEquals(parentResponse.getHeader(headerName), null); + } + + @Test + public void set_cookie_overwrites_old_values() { + Cookie to_be_removed = new Cookie("to-be-removed", ""); + Cookie to_keep = new Cookie("to-keep", ""); + filterResponse.setCookie(to_be_removed.getName(), to_be_removed.getValue()); + filterResponse.setCookie(to_keep.getName(), to_keep.getValue()); + + assertEquals(filterResponse.getCookies(), Arrays.asList(to_keep)); + assertEquals(parentResponse.getHeaders(HttpHeaders.Names.SET_COOKIE), Arrays.asList(to_keep.toString())); + } + + + private static class MockServletResponse extends org.eclipse.jetty.server.Response { + private MockServletResponse() { + super(null, null); + } + } + +} diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java new file mode 100644 index 00000000000..c6d416b2b99 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/JDiscFilterForServletTest.java @@ -0,0 +1,165 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty.servlet; + +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.util.Modules; +import com.yahoo.jdisc.AbstractResource; +import com.yahoo.jdisc.Request; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.filter.RequestFilter; +import com.yahoo.jdisc.http.filter.ResponseFilter; +import com.yahoo.jdisc.http.server.jetty.FilterBindings; +import com.yahoo.jdisc.http.server.jetty.FilterInvoker; +import com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.ResponseValidator; +import com.yahoo.jdisc.http.server.jetty.JettyTestDriver; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; + +/** + * @author Tony Vaagenes + * @author bjorncs + */ +public class JDiscFilterForServletTest extends ServletTestBase { + @Test + public void request_filter_can_return_response() throws IOException, InterruptedException { + JettyTestDriver testDriver = requestFilterTestDriver(); + ResponseValidator response = httpGet(testDriver, TestServlet.PATH).execute(); + + response.expectContent(containsString(TestRequestFilter.responseContent)); + } + + @Test + public void request_can_be_forwarded_through_request_filter_to_servlet() throws IOException { + JettyTestDriver testDriver = requestFilterTestDriver(); + ResponseValidator response = httpGet(testDriver, TestServlet.PATH). + addHeader(TestRequestFilter.BYPASS_FILTER_HEADER, Boolean.TRUE.toString()). + execute(); + + response.expectContent(containsString(TestServlet.RESPONSE_CONTENT)); + } + + @Test + public void response_filter_can_modify_response() throws IOException { + JettyTestDriver testDriver = responseFilterTestDriver(); + ResponseValidator response = httpGet(testDriver, TestServlet.PATH).execute(); + + response.expectHeader(TestResponseFilter.INVOKED_HEADER, is(Boolean.TRUE.toString())); + } + + @Test + public void response_filter_is_run_on_empty_sync_response() throws IOException { + JettyTestDriver testDriver = responseFilterTestDriver(); + ResponseValidator response = httpGet(testDriver, NoContentTestServlet.PATH).execute(); + + response.expectHeader(TestResponseFilter.INVOKED_HEADER, is(Boolean.TRUE.toString())); + } + + @Test + public void response_filter_is_run_on_empty_async_response() throws IOException { + JettyTestDriver testDriver = responseFilterTestDriver(); + ResponseValidator response = httpGet(testDriver, NoContentTestServlet.PATH). + addHeader(NoContentTestServlet.HEADER_ASYNC, Boolean.TRUE.toString()). + execute(); + + response.expectHeader(TestResponseFilter.INVOKED_HEADER, is(Boolean.TRUE.toString())); + } + + private JettyTestDriver requestFilterTestDriver() throws IOException { + FilterBindings filterBindings = new FilterBindings.Builder() + .addRequestFilter("my-request-filter", new TestRequestFilter()) + .addRequestFilterBinding("my-request-filter", "http://*/*") + .build(); + return JettyTestDriver.newInstance(dummyRequestHandler, bindings(filterBindings)); + } + + private JettyTestDriver responseFilterTestDriver() throws IOException { + FilterBindings filterBindings = new FilterBindings.Builder() + .addResponseFilter("my-response-filter", new TestResponseFilter()) + .addResponseFilterBinding("my-response-filter", "http://*/*") + .build(); + return JettyTestDriver.newInstance(dummyRequestHandler, bindings(filterBindings)); + } + + + + private Module bindings(FilterBindings filterBindings) { + return Modules.combine( + new AbstractModule() { + @Override + protected void configure() { + bind(FilterBindings.class).toInstance(filterBindings); + bind(FilterInvoker.class).toInstance(new FilterInvoker() { + @Override + public HttpServletRequest invokeRequestFilterChain( + RequestFilter requestFilter, + URI uri, + HttpServletRequest httpRequest, + ResponseHandler responseHandler) { + TestRequestFilter filter = (TestRequestFilter) requestFilter; + filter.runAsSecurityFilter(httpRequest, responseHandler); + return httpRequest; + } + + @Override + public void invokeResponseFilterChain( + ResponseFilter responseFilter, + URI uri, + HttpServletRequest request, + HttpServletResponse response) { + + TestResponseFilter filter = (TestResponseFilter) responseFilter; + filter.runAsSecurityFilter(request, response); + } + }); + } + }, + guiceModule()); + } + + static class TestRequestFilter extends AbstractResource implements RequestFilter { + static final String simpleName = TestRequestFilter.class.getSimpleName(); + static final String responseContent = "Rejected by " + simpleName; + static final String BYPASS_FILTER_HEADER = "BYPASS_HEADER" + simpleName; + + @Override + public void filter(HttpRequest request, ResponseHandler handler) { + throw new UnsupportedOperationException(); + } + + public void runAsSecurityFilter(HttpServletRequest request, ResponseHandler responseHandler) { + if (Boolean.parseBoolean(request.getHeader(BYPASS_FILTER_HEADER))) + return; + + ContentChannel contentChannel = responseHandler.handleResponse(new Response(500)); + contentChannel.write(ByteBuffer.wrap(responseContent.getBytes(StandardCharsets.UTF_8)), null); + contentChannel.close(null); + } + } + + + static class TestResponseFilter extends AbstractResource implements ResponseFilter { + static final String INVOKED_HEADER = TestResponseFilter.class.getSimpleName() + "_INVOKED_HEADER"; + + @Override + public void filter(Response response, Request request) { + throw new UnsupportedClassVersionError(); + } + + public void runAsSecurityFilter(HttpServletRequest request, HttpServletResponse response) { + response.addHeader(INVOKED_HEADER, Boolean.TRUE.toString()); + } + } +} diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletAccessLoggingTest.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletAccessLoggingTest.java new file mode 100644 index 00000000000..17802b7f466 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletAccessLoggingTest.java @@ -0,0 +1,63 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty.servlet; + +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.util.Modules; +import com.yahoo.container.logging.AccessLog; +import com.yahoo.container.logging.RequestLog; +import com.yahoo.container.logging.RequestLogEntry; +import com.yahoo.jdisc.http.server.jetty.JettyTestDriver; +import org.junit.Test; +import org.mockito.verification.VerificationMode; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +/** + * @author bakksjo + * @author bjorncs + */ +public class ServletAccessLoggingTest extends ServletTestBase { + private static final long MAX_LOG_WAIT_TIME_MILLIS = TimeUnit.SECONDS.toMillis(60); + + @Test + public void accessLogIsInvokedForNonJDiscServlet() throws Exception { + final AccessLog accessLog = mock(AccessLog.class); + final JettyTestDriver testDriver = newTestDriver(accessLog); + httpGet(testDriver, TestServlet.PATH).execute(); + verifyCallsLog(accessLog, timeout(MAX_LOG_WAIT_TIME_MILLIS).times(1)); + } + + @Test + public void accessLogIsInvokedForJDiscServlet() throws Exception { + final AccessLog accessLog = mock(AccessLog.class); + final JettyTestDriver testDriver = newTestDriver(accessLog); + testDriver.client().newGet("/status.html").execute(); + verifyCallsLog(accessLog, timeout(MAX_LOG_WAIT_TIME_MILLIS).times(1)); + } + + private void verifyCallsLog(RequestLog requestLog, final VerificationMode verificationMode) { + verify(requestLog, verificationMode).log(any(RequestLogEntry.class)); + } + + private JettyTestDriver newTestDriver(RequestLog requestLog) throws IOException { + return JettyTestDriver.newInstance(dummyRequestHandler, bindings(requestLog)); + } + + private Module bindings(RequestLog requestLog) { + return Modules.combine( + new AbstractModule() { + @Override + protected void configure() { + bind(RequestLog.class).toInstance(requestLog); + } + }, + guiceModule()); + } +} diff --git a/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletTestBase.java b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletTestBase.java new file mode 100644 index 00000000000..f13769dec38 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/jdisc/http/server/jetty/servlet/ServletTestBase.java @@ -0,0 +1,132 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty.servlet; + +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import com.yahoo.component.ComponentId; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.jdisc.Request; +import com.yahoo.jdisc.handler.AbstractRequestHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.ServletPathsConfig; +import com.yahoo.jdisc.http.ServletPathsConfig.Servlets.Builder; +import com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.RequestExecutor; +import com.yahoo.jdisc.http.server.jetty.JettyTestDriver; +import org.eclipse.jetty.servlet.ServletHolder; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +/** + * @author Tony Vaagenes + * @author bakksjo + */ +public class ServletTestBase { + + private static class ServletInstance { + final ComponentId componentId; final String path; final HttpServlet instance; + + ServletInstance(ComponentId componentId, String path, HttpServlet instance) { + this.componentId = componentId; + this.path = path; + this.instance = instance; + } + } + + private final List<ServletInstance> servlets = List.of( + new ServletInstance(TestServlet.ID, TestServlet.PATH, new TestServlet()), + new ServletInstance(NoContentTestServlet.ID, NoContentTestServlet.PATH, new NoContentTestServlet())); + + protected RequestExecutor httpGet(JettyTestDriver testDriver, String path) { + return testDriver.client().newGet("/" + path); + } + + protected ServletPathsConfig createServletPathConfig() { + ServletPathsConfig.Builder configBuilder = new ServletPathsConfig.Builder(); + + servlets.forEach(servlet -> + configBuilder.servlets( + servlet.componentId.stringValue(), + new Builder().path(servlet.path))); + + return new ServletPathsConfig(configBuilder); + } + + protected ComponentRegistry<ServletHolder> servlets() { + ComponentRegistry<ServletHolder> result = new ComponentRegistry<>(); + + servlets.forEach(servlet -> + result.register(servlet.componentId, new ServletHolder(servlet.instance))); + + result.freeze(); + return result; + } + + protected Module guiceModule() { + return new AbstractModule() { + @Override + protected void configure() { + bind(new TypeLiteral<ComponentRegistry<ServletHolder>>(){}).toInstance(servlets()); + bind(ServletPathsConfig.class).toInstance(createServletPathConfig()); + } + }; + } + + protected static class TestServlet extends HttpServlet { + static final String PATH = "servlet/test-servlet"; + static final ComponentId ID = ComponentId.fromString("test-servlet"); + static final String RESPONSE_CONTENT = "Response from " + TestServlet.class.getSimpleName(); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + PrintWriter writer = response.getWriter(); + writer.write(RESPONSE_CONTENT); + writer.close(); + } + } + + @WebServlet(asyncSupported = true) + protected static class NoContentTestServlet extends HttpServlet { + static final String HEADER_ASYNC = "HEADER_ASYNC"; + + static final String PATH = "servlet/no-content-test-servlet"; + static final ComponentId ID = ComponentId.fromString("no-content-test-servlet"); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (request.getHeader(HEADER_ASYNC) != null) { + asyncGet(request); + } + } + + private void asyncGet(HttpServletRequest request) { + request.startAsync().start(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + log("Interrupted", e); + } finally { + request.getAsyncContext().complete(); + } + }); + } + } + + + protected static final RequestHandler dummyRequestHandler = new AbstractRequestHandler() { + @Override + public ContentChannel handleRequest(Request request, ResponseHandler handler) { + throw new UnsupportedOperationException(); + } + }; +} |