aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java
diff options
context:
space:
mode:
Diffstat (limited to 'container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java')
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java294
1 files changed, 294 insertions, 0 deletions
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java
new file mode 100644
index 00000000000..a89c115a1c2
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscFilterInvokerFilter.java
@@ -0,0 +1,294 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.server.jetty;
+
+import com.yahoo.container.logging.AccessLogEntry;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncListener;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.yahoo.jdisc.http.server.jetty.JDiscHttpServlet.getConnector;
+import static com.yahoo.yolean.Exceptions.throwUnchecked;
+
+/**
+ * Runs JDisc security filters for Servlets
+ * This component is split in two:
+ * 1) JDiscFilterInvokerFilter, which uses package private methods to support JDisc APIs
+ * 2) SecurityFilterInvoker, which is intended for use in a servlet context.
+ *
+ * @author Tony Vaagenes
+ */
+class JDiscFilterInvokerFilter implements Filter {
+ private final JDiscContext jDiscContext;
+ private final FilterInvoker filterInvoker;
+
+ public JDiscFilterInvokerFilter(JDiscContext jDiscContext,
+ FilterInvoker filterInvoker) {
+ this.jDiscContext = jDiscContext;
+ this.filterInvoker = filterInvoker;
+ }
+
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {}
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest httpRequest = (HttpServletRequest)request;
+ HttpServletResponse httpResponse = (HttpServletResponse)response;
+
+ URI uri;
+ try {
+ uri = HttpRequestFactory.getUri(httpRequest);
+ } catch (RequestException e) {
+ httpResponse.sendError(e.getResponseStatus(), e.getMessage());
+ return;
+ }
+
+ AtomicReference<Boolean> responseReturned = new AtomicReference<>(null);
+
+ HttpServletRequest newRequest = runRequestFilterWithMatchingBinding(responseReturned, uri, httpRequest, httpResponse);
+ assert newRequest != null;
+ responseReturned.compareAndSet(null, false);
+
+ if (!responseReturned.get()) {
+ runChainAndResponseFilters(uri, newRequest, httpResponse, chain);
+ }
+ }
+
+ private void runChainAndResponseFilters(URI uri, HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+ Optional<OneTimeRunnable> responseFilterInvoker =
+ jDiscContext.filterResolver.resolveResponseFilter(request, uri)
+ .map(responseFilter ->
+ new OneTimeRunnable(() ->
+ filterInvoker.invokeResponseFilterChain(responseFilter, uri, request, response)));
+
+
+ HttpServletResponse responseForServlet = responseFilterInvoker
+ .<HttpServletResponse>map(invoker ->
+ new FilterInvokingResponseWrapper(response, invoker))
+ .orElse(response);
+
+ HttpServletRequest requestForServlet = responseFilterInvoker
+ .<HttpServletRequest>map(invoker ->
+ new FilterInvokingRequestWrapper(request, invoker, responseForServlet))
+ .orElse(request);
+
+ chain.doFilter(requestForServlet, responseForServlet);
+
+ responseFilterInvoker.ifPresent(invoker -> {
+ boolean requestHandledSynchronously = !request.isAsyncStarted();
+
+ if (requestHandledSynchronously) {
+ invoker.runIfFirstInvocation();
+ }
+ // For async requests, response filters will be invoked on AsyncContext.complete().
+ });
+ }
+
+ private HttpServletRequest runRequestFilterWithMatchingBinding(AtomicReference<Boolean> responseReturned, URI uri, HttpServletRequest request, HttpServletResponse response) throws IOException {
+ try {
+ RequestFilter requestFilter = jDiscContext.filterResolver.resolveRequestFilter(request, uri).orElse(null);
+ if (requestFilter == null)
+ return request;
+
+ ResponseHandler responseHandler = createResponseHandler(responseReturned, request, response);
+ return filterInvoker.invokeRequestFilterChain(requestFilter, uri, request, responseHandler);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed running request filter chain for uri " + uri, e);
+ }
+ }
+
+ private ResponseHandler createResponseHandler(AtomicReference<Boolean> responseReturned, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+ return jdiscResponse -> {
+ boolean oldValueWasNull = responseReturned.compareAndSet(null, true);
+ if (!oldValueWasNull)
+ throw new RuntimeException("Can't return response from filter asynchronously");
+
+ HttpRequestDispatch requestDispatch = createRequestDispatch(httpRequest, httpResponse);
+ return requestDispatch.handleRequestFilterResponse(jdiscResponse);
+ };
+ }
+
+ private HttpRequestDispatch createRequestDispatch(HttpServletRequest request, HttpServletResponse response) {
+ try {
+ final AccessLogEntry accessLogEntry = null; // Not used in this context.
+ return new HttpRequestDispatch(jDiscContext,
+ accessLogEntry,
+ getConnector(request).createRequestMetricContext(request, Map.of()),
+ request, response);
+ } catch (IOException e) {
+ throw throwUnchecked(e);
+ }
+ }
+
+ @Override
+ public void destroy() {}
+
+ // ServletRequest wrapper that is necessary because we need to wrap AsyncContext.
+ private static class FilterInvokingRequestWrapper extends HttpServletRequestWrapper {
+ private final OneTimeRunnable filterInvoker;
+ private final HttpServletResponse servletResponse;
+
+ public FilterInvokingRequestWrapper(
+ HttpServletRequest request,
+ OneTimeRunnable filterInvoker,
+ HttpServletResponse servletResponse) {
+ super(request);
+ this.filterInvoker = filterInvoker;
+ this.servletResponse = servletResponse;
+ }
+
+ @Override
+ public AsyncContext startAsync() {
+ final AsyncContext asyncContext = super.startAsync();
+ return new FilterInvokingAsyncContext(asyncContext, filterInvoker, this, servletResponse);
+ }
+
+ @Override
+ public AsyncContext startAsync(
+ final ServletRequest wrappedRequest,
+ final ServletResponse wrappedResponse) {
+ // According to the documentation, the passed request/response parameters here must either
+ // _be_ or _wrap_ the original request/response objects passed to the servlet - which are
+ // our wrappers, so no need to wrap again - we can use the user-supplied objects.
+ final AsyncContext asyncContext = super.startAsync(wrappedRequest, wrappedResponse);
+ return new FilterInvokingAsyncContext(asyncContext, filterInvoker, this, wrappedResponse);
+ }
+
+ @Override
+ public AsyncContext getAsyncContext() {
+ final AsyncContext asyncContext = super.getAsyncContext();
+ return new FilterInvokingAsyncContext(asyncContext, filterInvoker, this, servletResponse);
+ }
+ }
+
+ // AsyncContext wrapper that is necessary for two reasons:
+ // 1) Run response filters when AsyncContext.complete() is called.
+ // 2) Eliminate paths where application code can get its hands on un-wrapped response object, circumventing
+ // running of response filters.
+ private static class FilterInvokingAsyncContext implements AsyncContext {
+ private final AsyncContext delegate;
+ private final OneTimeRunnable filterInvoker;
+ private final ServletRequest servletRequest;
+ private final ServletResponse servletResponse;
+
+ public FilterInvokingAsyncContext(
+ AsyncContext delegate,
+ OneTimeRunnable filterInvoker,
+ ServletRequest servletRequest,
+ ServletResponse servletResponse) {
+ this.delegate = delegate;
+ this.filterInvoker = filterInvoker;
+ this.servletRequest = servletRequest;
+ this.servletResponse = servletResponse;
+ }
+
+ @Override
+ public ServletRequest getRequest() {
+ return servletRequest;
+ }
+
+ @Override
+ public ServletResponse getResponse() {
+ return servletResponse;
+ }
+
+ @Override
+ public boolean hasOriginalRequestAndResponse() {
+ return delegate.hasOriginalRequestAndResponse();
+ }
+
+ @Override
+ public void dispatch() {
+ delegate.dispatch();
+ }
+
+ @Override
+ public void dispatch(String s) {
+ delegate.dispatch(s);
+ }
+
+ @Override
+ public void dispatch(ServletContext servletContext, String s) {
+ delegate.dispatch(servletContext, s);
+ }
+
+ @Override
+ public void complete() {
+ // Completing may commit the response, so this is the last chance to run response filters.
+ filterInvoker.runIfFirstInvocation();
+ delegate.complete();
+ }
+
+ @Override
+ public void start(Runnable runnable) {
+ delegate.start(runnable);
+ }
+
+ @Override
+ public void addListener(AsyncListener asyncListener) {
+ delegate.addListener(asyncListener);
+ }
+
+ @Override
+ public void addListener(AsyncListener asyncListener, ServletRequest servletRequest, ServletResponse servletResponse) {
+ delegate.addListener(asyncListener, servletRequest, servletResponse);
+ }
+
+ @Override
+ public <T extends AsyncListener> T createListener(Class<T> aClass) throws ServletException {
+ return delegate.createListener(aClass);
+ }
+
+ @Override
+ public void setTimeout(long l) {
+ delegate.setTimeout(l);
+ }
+
+ @Override
+ public long getTimeout() {
+ return delegate.getTimeout();
+ }
+ }
+
+ private static class FilterInvokingResponseWrapper extends HttpServletResponseWrapper {
+ private final OneTimeRunnable filterInvoker;
+
+ public FilterInvokingResponseWrapper(HttpServletResponse response, OneTimeRunnable filterInvoker) {
+ super(response);
+ this.filterInvoker = filterInvoker;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ ServletOutputStream delegate = super.getOutputStream();
+ return new FilterInvokingServletOutputStream(delegate, filterInvoker);
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ PrintWriter delegate = super.getWriter();
+ return new FilterInvokingPrintWriter(delegate, filterInvoker);
+ }
+ }
+}