diff --git a/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc b/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc
index 9e3a200c29c..b44a7588c64 100644
--- a/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc
+++ b/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc
@@ -30,6 +30,7 @@ include::balancer-servlet.adoc[]
include::cgi-servlet.adoc[]
include::qos-filter.adoc[]
include::dos-filter.adoc[]
+include::header-filter.adoc[]
include::gzip-filter.adoc[]
include::cross-origin-filter.adoc[]
include::resource-handler.adoc[]
diff --git a/jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc b/jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc
new file mode 100644
index 00000000000..e7e97d9079e
--- /dev/null
+++ b/jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc
@@ -0,0 +1,115 @@
+// ========================================================================
+// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
+// ========================================================================
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+[[header-filter]]
+=== Header Filter
+
+[[header-filter-metadata]]
+==== Info
+
+* Classname: `org.eclipse.jetty.servlets.HeaderFilter`
+* Maven Artifact: org.eclipse.jetty:jetty-servlets
+* Javadoc: {JDURL}/org/eclipse/jetty/servlets/HeaderFilter.html
+* Xref: {JXURL}/org/eclipse/jetty/servlets/HeaderFilter.html
+
+[[header-filter-usage]]
+==== Usage
+
+The header filter sets or adds headers to each response based on an optionally included/excluded list of path specs, mime types, and/or HTTP methods.
+
+===== Required JARs
+
+To use the Header Filter, these JAR files must be available in WEB-INF/lib:
+
+* $JETTY_HOME/lib/jetty-http.jar
+* $JETTY_HOME/lib/jetty-servlets.jar
+* $JETTY_HOME/lib/jetty-util.jar
+
+===== Sample Configuration
+
+Place the configuration in a webapp's `web.xml` or `jetty-web.xml`.
+This filter will perform the following actions on each response:
+
+* Set the X-Frame-Options header to DENY.
+* Add a Cache-Control header containing no-cache, no-store, must-revalidate
+* Set the Expires header to approximately one year in the future.
+* Add a Date header with the current system time.
+
+____
+[NOTE]
+Each action must be separated by a comma.
+____
+
+[source, xml, subs="{sub-order}"]
+----
+
+ HeaderFilter
+ org.eclipse.jetty.servlets.HeaderFilter
+
+ headerConfig
+
+ set X-Frame-Options: DENY,
+ "add Cache-Control: no-cache, no-store, must-revalidate",
+ setDate Expires: 31540000000,
+ addDate Date: 0
+
+
+
+----
+
+[[header-filter-init]]
+===== Configuring Header Filter Parameters
+
+The following `init` parameters control the behavior of the filter:
+
+includedPaths::
+Optional. CSV of included path specs.
+
+excludedPaths::
+Optional. CSV of excluded path specs.
+
+includedMimeTypes::
+Optional. CSV of included mime types.
+
+excludedMimeTypes::
+Optional. CSV of excluded mime types.
+
+includedHttpMethods::
+Optional. CSV of included http methods.
+
+excludedHttpMethods::
+Optional. CSV of excluded http methods.
+
+headerConfig::
+CSV of actions to perform on headers. The syntax for each action is `action headerName: headerValue`.
+
+Supported header actions:
+
+* `set` - causes set `setHeader` to be called on the response
+* `add` - causes set `addHeader` to be called on the response
+* `setDate` - causes `setDateHeader` to be called on the response.
+* `addDate` - causes `addDateHeader` to be called on the response.
+
+If `setDate` or `addDate` is used, `headerValue` should be the number of milliseconds to add to the current system time before writing the header value.
+
+If a property is both included and excluded by the filter configuration, then it will be considered excluded.
+
+Path spec rules:
+
+* If the spec starts with `^`, the spec is assumed to be a regex based path spec and will match with normal Java regex rules.
+* If the spec starts with `/`, the spec is assumed to be a Servlet url-pattern rules path spec for either an exact match or prefix based match.
+* If the spec starts with `*.`, the spec is assumed to be a Servlet url-pattern rules path spec for a suffix based match.
+* All other syntaxes are unsupported.
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java
new file mode 100644
index 00000000000..c23b6b5b88c
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java
@@ -0,0 +1,197 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Header Filter
+ *
+ * The {@code headerConfig} init param is a CSV of actions to perform on headers, with the following syntax:
+ * [action] [header name]: [header value]
+ * [action] can be one of set
, add
, setDate
, or addDate
+ * The date actions will add the header value in milliseconds to the current system time before setting a date header.
+ *
_configuredHeaders = new ArrayList<>();
+ private static final Logger LOG = Log.getLogger(HeaderFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException
+ {
+ super.init(filterConfig);
+ String header_config = filterConfig.getInitParameter("headerConfig");
+
+ if (header_config != null)
+ {
+ String[] configs = StringUtil.csvSplit(header_config);
+ for (String config : configs)
+ _configuredHeaders.add(parseHeaderConfiguration(config));
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(this.toString());
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+ {
+ chain.doFilter(request,response);
+
+ HttpServletRequest http_request = (HttpServletRequest)request;
+ HttpServletResponse http_response = (HttpServletResponse)response;
+
+ if (!super.shouldFilter(http_request,http_response))
+ {
+ return;
+ }
+
+ for (ConfiguredHeader header : _configuredHeaders)
+ {
+ if (header.isDate())
+ {
+ long header_value = System.currentTimeMillis() + header.getMsOffset();
+ if (header.isAdd())
+ {
+ http_response.addDateHeader(header.getName(),header_value);
+ }
+ else
+ {
+ http_response.setDateHeader(header.getName(),header_value);
+ }
+ }
+ else // constant header value
+ {
+ if (header.isAdd())
+ {
+ http_response.addHeader(header.getName(),header.getValue());
+ }
+ else
+ {
+ http_response.setHeader(header.getName(),header.getValue());
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString()).append("\n");
+ sb.append("configured headers:\n");
+ for (ConfiguredHeader c : _configuredHeaders)
+ sb.append(c).append("\n");
+
+ return sb.toString();
+ }
+
+ private ConfiguredHeader parseHeaderConfiguration(String config)
+ {
+ String[] config_tokens = config.trim().split(" ",2);
+ String method = config_tokens[0].trim();
+ String header = config_tokens[1];
+ String[] header_tokens = header.trim().split(":",2);
+ String header_name = header_tokens[0].trim();
+ String header_value = header_tokens[1].trim();
+ ConfiguredHeader configured_header = new ConfiguredHeader(header_name,header_value,method.startsWith("add"),method.endsWith("Date"));
+ return configured_header;
+ }
+
+ private static class ConfiguredHeader
+ {
+ private String _name;
+ private String _value;
+ private long _msOffset;
+ private boolean _add;
+ private boolean _date;
+
+ public ConfiguredHeader(String name, String value, boolean add, boolean date)
+ {
+ _name = name;
+ _value = value;
+ _add = add;
+ _date = date;
+
+ if (_date)
+ {
+ _msOffset = Long.parseLong(_value);
+ }
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String getValue()
+ {
+ return _value;
+ }
+
+ public boolean isAdd()
+ {
+ return _add;
+ }
+
+ public boolean isDate()
+ {
+ return _date;
+ }
+
+ public long getMsOffset()
+ {
+ return _msOffset;
+ }
+
+ @Override
+ public String toString()
+ {
+ return (_add?"add":"set") + (_date?"Date":"") + " " + _name + ": " + _value;
+ }
+ }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java
new file mode 100644
index 00000000000..b1ba2cf4342
--- /dev/null
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java
@@ -0,0 +1,162 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.pathmap.PathSpecSet;
+import org.eclipse.jetty.util.IncludeExclude;
+import org.eclipse.jetty.util.IncludeExcludeSet;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Include Exclude Based Filter
+ *
+ * This is an abstract filter which helps with filtering based on include/exclude of paths, mime types, and/or http methods.
+ *
+ * Use the {@link #shouldFilter(HttpServletRequest, HttpServletResponse)} method to determine if a request/response should be filtered. If mime types are used,
+ * it should be called after {@link javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} since the mime type may not
+ * be written until then.
+ *
+ * Supported init params:
+ *
+ * includedPaths
- CSV of path specs to include
+ * excludedPaths
- CSV of path specs to exclude
+ * includedMimeTypes
- CSV of mime types to include
+ * excludedMimeTypes
- CSV of mime types to exclude
+ * includedHttpMethods
- CSV of http methods to include
+ * excludedHttpMethods
- CSV of http methods to exclude
+ *
+ *
+ * Path spec rules:
+ *
+ * - If the spec starts with
'^'
the spec is assumed to be a regex based path spec and will match with normal Java regex rules.
+ * - If the spec starts with
'/'
the spec is assumed to be a Servlet url-pattern rules path spec for either an exact match or prefix based
+ * match.
+ * - If the spec starts with
'*.'
the spec is assumed to be a Servlet url-pattern rules path spec for a suffix based match.
+ * - All other syntaxes are unsupported.
+ *
+ *
+ * CSVs are parsed with {@link StringUtil#csvSplit(String)}
+ *
+ * @see PathSpecSet
+ * @see IncludeExcludeSet
+ */
+public abstract class IncludeExcludeBasedFilter implements Filter
+{
+ private final IncludeExclude _mimeTypes = new IncludeExclude<>();
+ private final IncludeExclude _httpMethods = new IncludeExclude<>();
+ private final IncludeExclude _paths = new IncludeExclude<>(PathSpecSet.class);
+ private static final Logger LOG = Log.getLogger(IncludeExcludeBasedFilter.class);
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException
+ {
+ String included_paths = filterConfig.getInitParameter("includedPaths");
+ String excluded_paths = filterConfig.getInitParameter("excludedPaths");
+ String included_mime_types = filterConfig.getInitParameter("includedMimeTypes");
+ String excluded_mime_types = filterConfig.getInitParameter("excludedMimeTypes");
+ String included_http_methods = filterConfig.getInitParameter("includedHttpMethods");
+ String excluded_http_methods = filterConfig.getInitParameter("excludedHttpMethods");
+
+ if (included_paths != null)
+ {
+ _paths.include(StringUtil.csvSplit(included_paths));
+ }
+ if (excluded_paths != null)
+ {
+ _paths.exclude(StringUtil.csvSplit(excluded_paths));
+ }
+ if (included_mime_types != null)
+ {
+ _mimeTypes.include(StringUtil.csvSplit(included_mime_types));
+ }
+ if (excluded_mime_types != null)
+ {
+ _mimeTypes.exclude(StringUtil.csvSplit(excluded_mime_types));
+ }
+ if (included_http_methods != null)
+ {
+ _httpMethods.include(StringUtil.csvSplit(included_http_methods));
+ }
+ if (excluded_http_methods != null)
+ {
+ _httpMethods.exclude(StringUtil.csvSplit(excluded_http_methods));
+ }
+ }
+
+ protected boolean shouldFilter(HttpServletRequest http_request, HttpServletResponse http_response)
+ {
+ String http_method = http_request.getMethod();
+ LOG.debug("HTTP method is: {}",http_method);
+ if (!_httpMethods.test(http_method))
+ {
+ LOG.debug("should not apply filter because HTTP method does not match");
+ return false;
+ }
+
+ String content_type = http_response.getContentType();
+ LOG.debug("Content Type is: {}",content_type);
+ content_type = (content_type == null)?"":content_type;
+ String mime_type = MimeTypes.getContentTypeWithoutCharset(content_type);
+
+ LOG.debug("Mime Type is: {}",content_type);
+ if (!_mimeTypes.test(mime_type))
+ {
+ LOG.debug("should not apply filter because mime type does not match");
+ return false;
+ }
+
+ ServletContext context = http_request.getServletContext();
+ String path = context == null?http_request.getRequestURI():URIUtil.addPaths(http_request.getServletPath(),http_request.getPathInfo());
+ LOG.debug("Path is: {}",path);
+ if (!_paths.test(path))
+ {
+ LOG.debug("should not apply filter because path does not match");
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("filter configuration:\n");
+ sb.append("paths:\n").append(_paths).append("\n");
+ sb.append("mime types:\n").append(_mimeTypes).append("\n");
+ sb.append("http methods:\n").append(_httpMethods);
+ return sb.toString();
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java
new file mode 100644
index 00000000000..5230a96fd24
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java
@@ -0,0 +1,137 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class HeaderFilterTest
+{
+ private ServletTester _tester;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ _tester = new ServletTester();
+ _tester.setContextPath("/context");
+ _tester.addServlet(NullServlet.class,"/test/*");
+
+ _tester.start();
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ _tester.stop();
+ }
+
+ @Test
+ public void testHeaderFilterSet() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(HeaderFilter.class);
+ holder.setInitParameter("headerConfig","set X-Frame-Options: DENY");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Frame-Options","DENY"));
+ }
+
+ @Test
+ public void testHeaderFilterAdd() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(HeaderFilter.class);
+ holder.setInitParameter("headerConfig","add X-Frame-Options: DENY");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Frame-Options","DENY"));
+ }
+
+ @Test
+ public void testHeaderFilterSetDate() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(HeaderFilter.class);
+ holder.setInitParameter("headerConfig","setDate Expires: 100");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains(HttpHeader.EXPIRES));
+ }
+
+ @Test
+ public void testHeaderFilterAddDate() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(HeaderFilter.class);
+ holder.setInitParameter("headerConfig","addDate Expires: 100");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains(HttpHeader.EXPIRES));
+ }
+
+ public static class NullServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.setStatus(HttpStatus.NO_CONTENT_204);
+ }
+
+ }
+}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java
new file mode 100644
index 00000000000..f9971247ad2
--- /dev/null
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java
@@ -0,0 +1,353 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletTester;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class IncludeExcludeBasedFilterTest
+{
+ private ServletTester _tester;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ _tester = new ServletTester();
+ _tester.setContextPath("/context");
+ _tester.addServlet(NullServlet.class,"/test/*");
+
+ _tester.start();
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ _tester.stop();
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludedPathMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedPaths","^/test/0$");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludedPathNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedPaths","^/nomatchtest$");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludedPathMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("excludedPaths","^/test/0$");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludedPathNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("excludedPaths","^/nomatchtest$");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludeOverridesInclude() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedPaths","^/test/0$");
+ holder.setInitParameter("excludedPaths","^/test/0$");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludeMethodMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedHttpMethods","GET");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludeMethodNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedHttpMethods","POST,PUT");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludeMethodMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("excludedHttpMethods","GET");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludeMethodNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("excludedHttpMethods","POST,PUT");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/0");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludeMimeTypeMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedMimeTypes","application/json");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/json");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludeMimeTypeNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedMimeTypes","application/xml");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/json");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludeMimeTypeMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("excludedMimeTypes","application/json");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/json");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterExcludeMimeTypeNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("excludedMimeTypes","application/xml");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/json");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludeMimeTypeSemicolonMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedMimeTypes","application/json");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/json-utf8");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertTrue(response.contains("X-Custom-Value","1"));
+ }
+
+ @Test
+ public void testIncludeExcludeFilterIncludeMimeTypeSemicolonNoMatch() throws Exception
+ {
+ FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class);
+ holder.setInitParameter("includedMimeTypes","application/xml");
+ _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST));
+
+ HttpTester.Request request = HttpTester.newRequest();
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.1");
+ request.setHeader("Host","localhost");
+ request.setURI("/context/test/json-utf8");
+
+ HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate()));
+ Assert.assertFalse(response.contains("X-Custom-Value","1"));
+ }
+
+ public static class MockIncludeExcludeFilter extends IncludeExcludeBasedFilter
+ {
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+ {
+ chain.doFilter(request,response);
+ HttpServletRequest http_request = (HttpServletRequest)request;
+ HttpServletResponse http_response = (HttpServletResponse)response;
+
+ if (!super.shouldFilter(http_request,http_response))
+ {
+ return;
+ }
+
+ http_response.setHeader("X-Custom-Value","1");
+ }
+ }
+
+ public static class NullServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ if (req.getPathInfo().equals("/json"))
+ {
+ resp.setContentType("application/json");
+ }
+ else if (req.getPathInfo().equals("/json-utf8"))
+ {
+ resp.setContentType("application/json; charset=utf-8");
+ }
+ resp.setStatus(HttpStatus.NO_CONTENT_204);
+ }
+
+ }
+}