// // ======================================================================== // 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.servlet; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Test; @SuppressWarnings("serial") public class DispatcherForwardTest { private Server server; private LocalConnector connector; private HttpServlet servlet1; private HttpServlet servlet2; private List<Throwable> failures = new ArrayList<>(); public void prepare() throws Exception { server = new Server(); connector = new LocalConnector(server); server.addConnector(connector); ServletContextHandler context = new ServletContextHandler(server, "/"); context.addServlet(new ServletHolder(servlet1), "/one"); context.addServlet(new ServletHolder(servlet2), "/two"); server.start(); } @After public void dispose() throws Throwable { for (Throwable failure : failures) throw failure; server.stop(); } // Replacement for Assert that allows to check failures after the response has been sent. private <S> void checkThat(S item, Matcher<S> matcher) { try { Assert.assertThat(item,matcher); } catch(Throwable th) { failures.add(th); } } @Test public void testQueryRetainedByForwardWithoutQuery() throws Exception { // 1. request /one?a=1%20one // 1. forward /two // 2. assert query => a=1 one // 1. assert query => a=1 one CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; servlet1 = new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two").forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); } }; prepare(); String request = "" + "GET /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n"; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testQueryReplacedByForwardWithQuery() throws Exception { // 1. request /one?a=1 // 1. forward /two?a=2 // 2. assert query => a=2 // 1. assert query => a=1 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one&b=2%20two"; final String query2 = "a=3%20three"; final String query3 = "a=3%20three&b=2%20two"; servlet1 = new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two?" + query2).forward(req, resp); checkThat(req.getQueryString(), Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query3)); checkThat(req.getParameter("a"),Matchers.equalTo("3 three")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); } }; prepare(); String request = "" + "GET /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n"; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testQueryMergedByForwardWithQuery() throws Exception { // 1. request /one?a=1 // 1. forward /two?b=2 // 2. assert query => a=1&b=2 // 1. assert query => a=1 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "b=2%20two"; final String query3 = "b=2%20two&a=1%20one"; servlet1 = new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two?" + query2).forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query3)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); } }; prepare(); String request = "" + "GET /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n"; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testQueryAggregatesWithFormByForwardWithoutQuery() throws Exception { // 1. request /one?a=1 + content a=2 // 1. forward /two // 2. assert query => a=1 + params => a=1,2 // 1. assert query => a=1 + params => a=1,2 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String form = "a=2%20two"; servlet1 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two").forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); String[] values = req.getParameterValues("a"); checkThat(values, Matchers.notNullValue()); checkThat(2, Matchers.equalTo(values.length)); checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two")); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); String[] values = req.getParameterValues("a"); checkThat(values, Matchers.notNullValue()); checkThat(2, Matchers.equalTo(values.length)); checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two")); } }; prepare(); String request = "" + "POST /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: " + form.length() + "\r\n" + "Connection: close\r\n" + "\r\n" + form; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testQueryAggregatesWithFormReplacedByForwardWithQuery() throws Exception { // 1. request /one?a=1 + content a=2 // 1. forward /two?a=3 // 2. assert query => a=3 + params => a=3,2,1 // 1. assert query => a=1 + params => a=1,2 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "a=3%20three"; final String form = "a=2%20two"; servlet1 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two?" + query2).forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); String[] values = req.getParameterValues("a"); checkThat(values, Matchers.notNullValue()); checkThat(2, Matchers.equalTo(values.length)); checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two")); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query2)); String[] values = req.getParameterValues("a"); checkThat(values, Matchers.notNullValue()); checkThat(3, Matchers.equalTo(values.length)); checkThat(values, Matchers.arrayContainingInAnyOrder("3 three", "2 two", "1 one")); } }; prepare(); String request = "" + "POST /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: " + form.length() + "\r\n" + "Connection: close\r\n" + "\r\n" + form; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testQueryAggregatesWithFormMergedByForwardWithQuery() throws Exception { // 1. request /one?a=1 + content b=2 // 1. forward /two?c=3 // 2. assert query => a=1&c=3 + params => a=1&b=2&c=3 // 1. assert query => a=1 + params => a=1&b=2 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "c=3%20three"; final String query3 = "c=3%20three&a=1%20one"; final String form = "b=2%20two"; servlet1 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two?" + query2).forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); checkThat(req.getParameter("c"), Matchers.nullValue()); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query3)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); checkThat(req.getParameter("c"),Matchers.equalTo("3 three")); } }; prepare(); String request = "" + "POST /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: " + form.length() + "\r\n" + "Connection: close\r\n" + "\r\n" + form; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testQueryAggregatesWithFormBeforeAndAfterForward() throws Exception { // 1. request /one?a=1 + content b=2 // 1. assert params => a=1&b=2 // 1. forward /two?c=3 // 2. assert query => a=1&c=3 + params => a=1&b=2&c=3 // 1. assert query => a=1 + params => a=1&b=2 CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "c=3%20three"; final String query3 = "c=3%20three&a=1%20one"; final String form = "b=2%20two"; servlet1 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); req.getRequestDispatcher("/two?" + query2).forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); checkThat(req.getParameter("c"), Matchers.nullValue()); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query3)); checkThat(req.getParameter("a"),Matchers.equalTo("1 one")); checkThat(req.getParameter("b"),Matchers.equalTo("2 two")); checkThat(req.getParameter("c"),Matchers.equalTo("3 three")); } }; prepare(); String request = "" + "POST /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: " + form.length() + "\r\n" + "Connection: close\r\n" + "\r\n" + form; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testContentCanBeReadViaInputStreamAfterForwardWithoutQuery() throws Exception { CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String form = "c=3%20three"; servlet1 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two").forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("c"), Matchers.nullValue()); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); ServletInputStream input = req.getInputStream(); for (int i = 0; i < form.length(); ++i) checkThat(form.charAt(i) & 0xFFFF, Matchers.equalTo(input.read())); } }; prepare(); String request = "" + "POST /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: " + form.length() + "\r\n" + "Connection: close\r\n" + "\r\n" + form; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } @Test public void testContentCanBeReadViaInputStreamAfterForwardWithQuery() throws Exception { CountDownLatch latch = new CountDownLatch(1); final String query1 = "a=1%20one"; final String query2 = "b=2%20two"; final String query3 = "b=2%20two&a=1%20one"; final String form = "c=3%20three"; servlet1 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query1)); req.getRequestDispatcher("/two?" + query2).forward(req, resp); checkThat(req.getQueryString(),Matchers.equalTo(query1)); checkThat(req.getParameter("c"), Matchers.nullValue()); latch.countDown(); } }; servlet2 = new HttpServlet() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { checkThat(req.getQueryString(),Matchers.equalTo(query3)); ServletInputStream input = req.getInputStream(); for (int i = 0; i < form.length(); ++i) checkThat(form.charAt(i) & 0xFFFF, Matchers.equalTo(input.read())); checkThat(-1, Matchers.equalTo(input.read())); } }; prepare(); String request = "" + "POST /one?" + query1 + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: " + form.length() + "\r\n" + "Connection: close\r\n" + "\r\n" + form; String response = connector.getResponses(request); Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); Assert.assertTrue(response, response.startsWith("HTTP/1.1 200")); } // TODO: add multipart tests }