diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyUtils.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyUtils.java index 7d61f74..d7894a3 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyUtils.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/ProxyUtils.java @@ -25,6 +25,7 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -40,6 +41,19 @@ public static final String E_HTTP_HTTPS_ONLY = "This filter only works for HTTP/HTTPS"; public static final String LOCATION = "Location"; + public static final String BODY_HAS_MOVED = "Content has moved "; + public static final String TITLE_APPLICATION_COMPLETED = + "Application has finished"; + public static final String BODY_COMPLETED = "Logs available "; + /** + * There's no web UI. This may change + */ + public static final String TITLE_NO_REGISTERED_WEB_UI = + "No Application Web UI"; + public static final String BODY_NO_REGISTERED_WEB_UI = + "This application has no registered Web UI"; + public static final String TITLE_UNSAFE_LINK = + "WARNING: The following page may not be safe!"; public static class _ implements Hamlet._ { //Empty @@ -54,7 +68,7 @@ return new HTML<>("html", null, EnumSet.of(EOpt.ENDTAG)); } } - + /** * Handle redirects with a status code that can in future support verbs other * than GET, thus supporting full REST functionality. @@ -62,7 +76,6 @@ * The target URL is included in the redirect text returned *

* At the end of this method, the output stream is closed. - * * @param request request (hence: the verb and any other information * relevant to a redirect) * @param response the response @@ -73,30 +86,67 @@ public static void sendRedirect(HttpServletRequest request, HttpServletResponse response, String target) throws IOException { + sendRedirect(request, response, target, + false, "Moved", BODY_HAS_MOVED); + } + + /** + * Handle redirects with a status code that can in future support verbs other + * than GET, thus supporting full REST functionality. + *

+ * The target URL is included in the redirect text returned + *

+ * At the end of this method, the output stream is closed. + * @param request request (hence: the verb and any other information + * relevant to a redirect) + * @param response the response + * @param target the target URL -unencoded + * @param failureTarget true if target is a result of some failure + * @param title H1 title + * @param text body text + */ + public static void sendRedirect(HttpServletRequest request, + HttpServletResponse response, + String target, + boolean failureTarget, + String title, + String text) + throws IOException { + String method = request.getMethod(); if (LOG.isDebugEnabled()) { - LOG.debug("Redirecting {} {} to {}", - request.getMethod(), + LOG.debug("Redirecting {} {} to {} due to {}", + method, request.getRequestURI(), - target); + target, + title); } String location = response.encodeRedirectURL(target); - response.setStatus(HttpServletResponse.SC_FOUND); response.setHeader(LOCATION, location); + int status; + if (!failureTarget) { + status = statusCode(method); + } else { + // app is terminated. Here policy is + // simple 302 on GET/HEAD (to logs etc) + // and hard 404 failure of other verbs + status = isGetRequest(method) ? HttpServletResponse.SC_FOUND + : HttpServletResponse.SC_NOT_FOUND; + } + response.setStatus(status); response.setContentType(MimeType.HTML); - PrintWriter writer = response.getWriter(); - Page p = new Page(writer); - p.html() - .head().title("Moved")._() - .body() - .h1("Moved") - .div() - ._("Content has moved ") - .a(location, "here")._() - ._()._(); - writer.close(); + try(PrintWriter writer = response.getWriter()) { + Page p = new Page(writer); + p.html() + .head().title(title)._() + .body() + .h1(title) + .div() + ._(text) + .a(location, "here")._() + ._()._(); + } } - /** * Output 404 with appropriate message. * @param resp the http response. @@ -107,10 +157,12 @@ public static void notFound(HttpServletResponse resp, String message) throws IOException { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setContentType(MimeType.HTML); - Page p = new Page(resp.getWriter()); - p.html(). - h1(message). - _(); + try (PrintWriter writer = resp.getWriter()) { + Page p = new Page(writer); + p.html(). + h1(message). + _(); + } } /** @@ -124,4 +176,82 @@ public static void rejectNonHttpRequests(ServletRequest req) throws throw new ServletException(E_HTTP_HTTPS_ONLY); } } + + /** + * Test for the method being an idempotent "GET" operation (i.e. GET or HEAD) + * @param method method + * @return true if the method is considered a GET/HEAD query. + */ + public static boolean isGetRequest(String method) { + return "HEAD".equals(method) || "GET".equals(method); + } + + /** + * Test for the request being an idempotent "GET" operation (i.e. GET or HEAD). + * @param request incoming request + * @return true if the request is considered a GET/HEAD query. + */ + public static boolean isGetRequest(HttpServletRequest request) { + return isGetRequest(request.getMethod()); + } + + + /** + * Create the status code appropriate for a given method. + * @param method method + * @return status code + */ + public static int statusCode(String method) { + return isGetRequest(method) ? + HttpServletResponse.SC_FOUND + : HttpServletResponse.SC_TEMPORARY_REDIRECT; + } + + + /** + * Warn the user that the link may not be safe. + * For non GET/HEAD operations, this is escalated into a 401/Forbidden + * response. + * @param request originating request + * @param resp the http response + * @param link the link to point to + * @param user the user that owns the link. + * @throws IOException on any error. + */ + public static void warnUser( + HttpServletRequest request, + HttpServletResponse resp, + String link, String user, Cookie checkCookie) throws IOException { + if (isGetRequest(request)) { + //Set the cookie when we warn which overrides the query parameter + //This is so that if a user passes in the approved query parameter without + //having first visited this page then this page will still be displayed + resp.addCookie(checkCookie); + resp.setContentType(MimeType.HTML); + Page p = new Page(resp.getWriter()); + p.html(). + h1(TITLE_UNSAFE_LINK). + h3(). + _("click ").a(link, "here"). + _(" to continue to an Application Master web interface owned by ", + user). + _(). + _(); + } else { + // its a URL belonging to a service other than the user, and an attempt + // is being made to manipulate it. + // reject the operation + resp.setStatus(HttpServletResponse.SC_FORBIDDEN); + resp.setContentType(MimeType.HTML); + Page p = new Page(resp.getWriter()); + String message = String.format( + "Forbidden to access URL %s of %s via %s request", + link, + user, + request.getMethod()); + p.html(). + h1(message). + _(); + } + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java index 0e988b8..bba455f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java @@ -24,13 +24,11 @@ import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.OutputStream; -import java.io.PrintWriter; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.Arrays; -import java.util.EnumSet; import java.util.Enumeration; import java.util.HashSet; import java.util.List; @@ -51,11 +49,10 @@ import org.apache.hadoop.yarn.exceptions.YarnException; import org.apache.hadoop.yarn.server.webproxy.AppReportFetcher.AppReportSource; import org.apache.hadoop.yarn.server.webproxy.AppReportFetcher.FetchedAppReport; +import static org.apache.hadoop.yarn.server.webproxy.ProxyUtils.*; import org.apache.hadoop.yarn.util.Apps; import org.apache.hadoop.yarn.util.StringHelper; import org.apache.hadoop.yarn.util.TrackingUriPlugin; -import org.apache.hadoop.yarn.webapp.MimeType; -import org.apache.hadoop.yarn.webapp.hamlet.Hamlet; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; @@ -101,23 +98,6 @@ private enum HTTP { GET, POST, HEAD, PUT, DELETE }; /** - * Empty Hamlet class. - */ - private static class _ implements Hamlet._ { - //Empty - } - - private static class Page extends Hamlet { - Page(PrintWriter out) { - super(out, 0, false); - } - - public HTML html() { - return new HTML<>("html", null, EnumSet.of(EOpt.ENDTAG)); - } - } - - /** * Default constructor */ public WebAppProxyServlet() { @@ -151,21 +131,14 @@ private static void notFound(HttpServletResponse resp, String message) * @param user the user that owns the link. * @throws IOException on any error. */ - private static void warnUserPage(HttpServletResponse resp, String link, + private static void warnUserPage(HttpServletRequest request, + HttpServletResponse resp, String link, String user, ApplicationId id) throws IOException { //Set the cookie when we warn which overrides the query parameter //This is so that if a user passes in the approved query parameter without //having first visited this page then this page will still be displayed - resp.addCookie(makeCheckCookie(id, false)); - resp.setContentType(MimeType.HTML); - Page p = new Page(resp.getWriter()); - p.html(). - h1("WARNING: The following page may not be safe!"). - h3(). - _("click ").a(link, "here"). - _(" to continue to an Application Master web interface owned by ", user). - _(). - _(); + Cookie checkCookie = makeCheckCookie(id, false); + warnUser(request, resp, link, user, checkCookie); } /** @@ -282,7 +255,13 @@ private String getProxyHost() throws IOException { return ((String) getServletContext() .getAttribute(WebAppProxy.PROXY_HOST_ATTRIBUTE)); } - + + /** + * Handle HEAD/GET requests. + * @param req request + * @param resp response + * @throws IOException + */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @@ -391,7 +370,10 @@ private void methodAction(final HttpServletRequest req, LOG.debug("Original tracking url is '{}'. Redirecting to RM app page", original == null? "NULL" : original); ProxyUtils.sendRedirect(req, resp, - StringHelper.pjoin(rmAppPageUrlBase, id.toString())); + StringHelper.pjoin(rmAppPageUrlBase, id.toString()), + true, + TITLE_NO_REGISTERED_WEB_UI, + BODY_NO_REGISTERED_WEB_UI); } else if (fetchedAppReport.getAppReportSource() == AppReportSource.AHS) { // fallback to Application History Server app page if the application @@ -416,8 +398,9 @@ private void methodAction(final HttpServletRequest req, LOG.info("Asking {} if they want to connect to the " + "app master GUI of {} owned by {}", remoteUser, appId, runningUser); - warnUserPage(resp, ProxyUriUtils.getPathAndQuery(id, rest, - req.getQueryString(), true), runningUser, id); + warnUserPage(req, resp, + ProxyUriUtils.getPathAndQuery(id, rest, req.getQueryString(), true), + runningUser, id); return; } @@ -439,7 +422,10 @@ private void methodAction(final HttpServletRequest req, case KILLED: case FINISHED: case FAILED: - ProxyUtils.sendRedirect(req, resp, toFetch.toString()); + // app has completed; send an appropriate redirect + // tagged as a non-live endpoint + ProxyUtils.sendRedirect(req, resp, toFetch.toString(), + true, TITLE_APPLICATION_COMPLETED, BODY_COMPLETED); return; default: // fall out of the switch diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/HttpServletResponseForTest.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/HttpServletResponseForTest.java new file mode 100644 index 0000000..2d7d79d --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/HttpServletResponseForTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.hadoop.yarn.server.webproxy; + +import org.glassfish.grizzly.servlet.HttpServletResponseImpl; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * Stub http servlet response for assertion checks + */ +public class HttpServletResponseForTest extends HttpServletResponseImpl { + public int status; + public String contentType; + private final Map headers = new HashMap<>(1); + public StringWriter body; + + @Override + public String encodeRedirectURL(String url) { + return url; + } + + @Override + public void setStatus(int status) { + this.status = status; + } + + @Override + public void setContentType(String type) { + this.contentType = type; + } + + @Override + public void setHeader(String name, String value) { + headers.put(name, value); + } + + public String getHeader(String name) { + return headers.get(name); + } + + @Override + public PrintWriter getWriter() throws IOException { + body = new StringWriter(); + return new PrintWriter(body); + } + + @Override + public String toString() { + final StringBuilder sb = + new StringBuilder("HttpServletResponseForTest{"); + sb.append("status=").append(status); + sb.append(", ["); + for (Map.Entry entry : headers.entrySet()) { + sb.append(entry.getKey()) + .append(":") + .append(entry.getValue()); + sb.append("; "); + } + sb.append(" ] body=").append(getBodyAsString()); + sb.append('}'); + return sb.toString(); + } + + public String getBodyAsString() { + return body.toString(); + } +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyRedirection.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyRedirection.java new file mode 100644 index 0000000..c27646b --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestProxyRedirection.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.hadoop.yarn.server.webproxy; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import static org.mockito.Mockito.when; +import static org.apache.hadoop.yarn.server.webproxy.ProxyUtils.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TestProxyRedirection extends Assert { + + private static void assertStatusCode(int code, String method) { + int status = statusCode(method); + assertEquals("Wrong status code for " + method, + code, status); + } + + @Test + public void testHead() throws Throwable { + assertStatusCode(HttpServletResponse.SC_FOUND, "HEAD"); + } + + @Test + public void testGet() throws Throwable { + assertStatusCode(HttpServletResponse.SC_FOUND, "GET"); + } + + @Test + public void testPut() throws Throwable { + assertStatusCode(HttpServletResponse.SC_TEMPORARY_REDIRECT, "PUT"); + } + + @Test + public void testPost() throws Throwable { + assertStatusCode(HttpServletResponse.SC_TEMPORARY_REDIRECT, "POST"); + } + + @Test + public void testDeletePost() throws Throwable { + assertStatusCode(HttpServletResponse.SC_TEMPORARY_REDIRECT, "DELETE"); + } + + @Test + public void testRedirectGet() throws Throwable { + HttpServletResponseForTest response = new HttpServletResponseForTest(); + + // request with HttpServletRequest + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRemoteAddr()).thenReturn("remoteAddr"); + Mockito.when(request.getRequestURI()).thenReturn("/requestURI"); + Mockito.when(request.getMethod()).thenReturn("GET"); + + ProxyUtils.sendRedirect(request, response, "http://target"); + // address "redirect" is not in host list + String resp = response.toString(); + assertEquals(resp, + HttpServletResponse.SC_FOUND, + response.status); + String redirect = response.getHeader(ProxyUtils.LOCATION); + assertEquals(resp, "http://target", redirect); + assertTrue(resp, response.getBodyAsString().contains(BODY_HAS_MOVED)); + } + + @Test + public void testRedirectPOST() throws Throwable { + HttpServletResponseForTest response = new HttpServletResponseForTest(); + + // request with HttpServletRequest + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + when(request.getRemoteAddr()).thenReturn("remoteAddr"); + when(request.getRequestURI()).thenReturn("/requestURI"); + when(request.getMethod()).thenReturn("POST"); + + sendRedirect(request, response, "http://target"); + // address "redirect" is not in host list + String resp = response.toString(); + assertEquals(resp, + HttpServletResponse.SC_TEMPORARY_REDIRECT, + response.status); + String redirect = response.getHeader(ProxyUtils.LOCATION); + assertEquals(resp, "http://target", redirect); + assertTrue(resp, response.getBodyAsString().contains(BODY_HAS_MOVED)); + } + + +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/amfilter/TestAmFilter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/amfilter/TestAmFilter.java index 4601c20..a9a2272 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/amfilter/TestAmFilter.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/amfilter/TestAmFilter.java @@ -19,8 +19,6 @@ package org.apache.hadoop.yarn.server.webproxy.amfilter; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,9 +29,9 @@ import static org.junit.Assert.*; +import org.apache.hadoop.yarn.server.webproxy.HttpServletResponseForTest; import org.apache.hadoop.yarn.server.webproxy.ProxyUtils; import org.apache.hadoop.yarn.server.webproxy.WebAppProxyServlet; -import org.glassfish.grizzly.servlet.HttpServletResponseImpl; import org.junit.Test; import org.mockito.Mockito; @@ -55,7 +53,7 @@ protected Set getProxyAddresses() { if (proxyAddresses == null) { - proxyAddresses = new HashSet(); + proxyAddresses = new HashSet<>(); } proxyAddresses.add(proxyHost); return proxyAddresses; @@ -126,25 +124,11 @@ public void doFilter(ServletRequest servletRequest, @Test(timeout = 1000) @SuppressWarnings("deprecation") public void testFilter() throws Exception { - Map params = new HashMap(); - params.put(AmIpFilter.PROXY_HOST, proxyHost); - params.put(AmIpFilter.PROXY_URI_BASE, proxyUri); - FilterConfig config = new DummyFilterConfig(params); - // dummy filter - FilterChain chain = new FilterChain() { - @Override - public void doFilter(ServletRequest servletRequest, - ServletResponse servletResponse) throws IOException, ServletException { - doFilterRequest = servletRequest.getClass().getName(); - if (servletRequest instanceof AmIpServletRequestWrapper) { - servletWrapper = (AmIpServletRequestWrapper) servletRequest; - } - } - }; - AmIpFilter testFilter = new AmIpFilter(); - testFilter.init(config); + // dummy filter + FilterChain chain = createDummyFilterChain(); + AmIpFilter testFilter = createTestFilter(); HttpServletResponseForTest response = new HttpServletResponseForTest(); // Test request should implements HttpServletRequest @@ -159,13 +143,15 @@ public void doFilter(ServletRequest servletRequest, // request with HttpServletRequest HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - Mockito.when(request.getRemoteAddr()).thenReturn("redirect"); - Mockito.when(request.getRequestURI()).thenReturn("/redirect"); + Mockito.when(request.getRemoteAddr()).thenReturn("remoteAddr"); + Mockito.when(request.getRequestURI()).thenReturn("/requestURI"); + Mockito.when(request.getMethod()).thenReturn("GET"); + testFilter.doFilter(request, response, chain); testFilter.doFilter(request, response, chain); // address "redirect" is not in host list - assertEquals(302, response.status); + assertEquals(response.toString(), 302, response.status); String redirect = response.getHeader(ProxyUtils.LOCATION); - assertEquals("http://bogus/redirect", redirect); + assertEquals(response.toString(), "http://bogus/requestURI", redirect); // "127.0.0.1" contains in host list. Without cookie Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1"); testFilter.doFilter(request, response, chain); @@ -189,53 +175,59 @@ public void doFilter(ServletRequest servletRequest, } - private class HttpServletResponseForTest extends HttpServletResponseImpl { - String redirectLocation = ""; - int status; - private String contentType; - private final Map headers = new HashMap<>(1); - private StringWriter body; - - - public String getRedirect() { - return redirectLocation; - } + public FilterChain createDummyFilterChain() { + return new FilterChain() { + @Override + public void doFilter(ServletRequest servletRequest, + ServletResponse servletResponse) throws + IOException, + ServletException { + doFilterRequest = servletRequest.getClass().getName(); + if (servletRequest instanceof AmIpServletRequestWrapper) { + servletWrapper = (AmIpServletRequestWrapper) servletRequest; - @Override - public void sendRedirect(String location) throws IOException { - redirectLocation = location; - } + } + } + }; + } - @Override - public String encodeRedirectURL(String url) { - return url; - } + public AmIpFilter createTestFilter() throws ServletException { + Map params = new HashMap<>(); + params.put(AmIpFilter.PROXY_HOST, proxyHost); + params.put(AmIpFilter.PROXY_URI_BASE, proxyUri); + FilterConfig config = new DummyFilterConfig(params); + AmIpFilter testFilter = new AmIpFilter(); + testFilter.init(config); + return testFilter; + } - @Override - public void setStatus(int status) { - this.status = status; - } - @Override - public void setContentType(String type) { - this.contentType = type; - } + /** + * Test AmIpFilter + */ + @Test(timeout = 1000) + @SuppressWarnings("deprecation") + public void testComplexVerbRedirect() throws Exception { - @Override - public void setHeader(String name, String value) { - headers.put(name, value); - } + // dummy filter + FilterChain chain = createDummyFilterChain(); + AmIpFilter testFilter = createTestFilter(); - public String getHeader(String name) { - return headers.get(name); - } + HttpServletResponseForTest response = new HttpServletResponseForTest(); - @Override - public PrintWriter getWriter() throws IOException { - body = new StringWriter(); - return new PrintWriter(body); - } + // request with HttpServletRequest + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRemoteAddr()).thenReturn("remoteAddr"); + Mockito.when(request.getRequestURI()).thenReturn("/requestURI"); + Mockito.when(request.getMethod()).thenReturn("PUT"); + testFilter.doFilter(request, response, chain); + testFilter.doFilter(request, response, chain); + // address "redirect" is not in host list + assertEquals(response.toString(), + HttpServletResponse.SC_TEMPORARY_REDIRECT, + response.status); + String redirect = response.getHeader(ProxyUtils.LOCATION); + assertEquals(response.toString(), "http://bogus/requestURI", redirect); } - }