diff --git a/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml b/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml index da1263c1b8d7..79d16dcd8500 100644 --- a/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml +++ b/demos/demo-simple-webapp/src/main/webapp/WEB-INF/web.xml @@ -6,4 +6,10 @@ Simple Web Application + + + icon + image/vnd.microsoft.icon + + diff --git a/demos/demo-simple-webapp/src/main/webapp/jetty.icon b/demos/demo-simple-webapp/src/main/webapp/jetty.icon new file mode 100644 index 000000000000..54e2e6104332 Binary files /dev/null and b/demos/demo-simple-webapp/src/main/webapp/jetty.icon differ diff --git a/demos/demo-simple-webapp/src/main/webapp/jetty.png b/demos/demo-simple-webapp/src/main/webapp/jetty.png new file mode 100644 index 000000000000..d579fffddfe1 Binary files /dev/null and b/demos/demo-simple-webapp/src/main/webapp/jetty.png differ diff --git a/demos/demo-simple-webapp/src/main/webapp/jetty.webp b/demos/demo-simple-webapp/src/main/webapp/jetty.webp new file mode 100644 index 000000000000..2d1bfea3ef79 Binary files /dev/null and b/demos/demo-simple-webapp/src/main/webapp/jetty.webp differ diff --git a/jetty-server/src/main/config/etc/jetty-gzip.xml b/jetty-server/src/main/config/etc/jetty-gzip.xml index 7659b2a195d0..933f2ef4893e 100644 --- a/jetty-server/src/main/config/etc/jetty-gzip.xml +++ b/jetty-server/src/main/config/etc/jetty-gzip.xml @@ -18,8 +18,8 @@ - - + + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 9df6291acf4d..1a2cfd921284 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -768,6 +768,17 @@ public void setExcludedMimeTypes(String... types) _mimeTypes.exclude(types); } + /** + * Set the excluded filter list of MIME types (replacing any previously set) + * + * @param csvTypes The list of mime types to exclude (without charset or other parameters), CSV format + * @see #setIncludedMimeTypesList(String) + */ + public void setExcludedMimeTypesList(String csvTypes) + { + setExcludedMimeTypes(StringUtil.csvSplit(csvTypes)); + } + /** * Set the excluded filter list of Path specs (replacing any previously set) * @@ -819,6 +830,17 @@ public void setIncludedMimeTypes(String... types) _mimeTypes.include(types); } + /** + * Set the included filter list of MIME types (replacing any previously set) + * + * @param csvTypes The list of mime types to include (without charset or other parameters), CSV format + * @see #setExcludedMimeTypesList(String) + */ + public void setIncludedMimeTypesList(String csvTypes) + { + setIncludedMimeTypes(StringUtil.csvSplit(csvTypes)); + } + /** * Set the included filter list of Path specs (replacing any previously set) * diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java index 43d3f031f854..c6470583c79c 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java @@ -61,7 +61,6 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; -@SuppressWarnings("serial") public class GzipHandlerTest { private static final String __content = @@ -88,6 +87,8 @@ public class GzipHandlerTest private Server _server; private LocalConnector _connector; + private GzipHandler gzipHandler; + private ServletContextHandler context; @BeforeEach public void init() throws Exception @@ -96,25 +97,25 @@ public void init() throws Exception _connector = new LocalConnector(_server); _server.addConnector(_connector); - GzipHandler gzipHandler = new GzipHandler(); + gzipHandler = new GzipHandler(); gzipHandler.setMinGzipSize(16); gzipHandler.setInflateBufferSize(4096); - ServletContextHandler context = new ServletContextHandler(gzipHandler, "/ctx"); - ServletHandler servlets = context.getServletHandler(); + context = new ServletContextHandler(gzipHandler, "/ctx"); _server.setHandler(gzipHandler); gzipHandler.setHandler(context); - servlets.addServletWithMapping(MicroServlet.class, "/micro"); - servlets.addServletWithMapping(MicroChunkedServlet.class, "/microchunked"); - servlets.addServletWithMapping(TestServlet.class, "/content"); - servlets.addServletWithMapping(ForwardServlet.class, "/forward"); - servlets.addServletWithMapping(IncludeServlet.class, "/include"); - servlets.addServletWithMapping(EchoServlet.class, "/echo/*"); - servlets.addServletWithMapping(DumpServlet.class, "/dump/*"); - servlets.addServletWithMapping(AsyncServlet.class, "/async/*"); - servlets.addServletWithMapping(BufferServlet.class, "/buffer/*"); - servlets.addFilterWithMapping(CheckFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + context.addServlet(MicroServlet.class, "/micro"); + context.addServlet(MicroChunkedServlet.class, "/microchunked"); + context.addServlet(TestServlet.class, "/content"); + context.addServlet(MimeTypeContentServlet.class, "/mimetypes/*"); + context.addServlet(ForwardServlet.class, "/forward"); + context.addServlet(IncludeServlet.class, "/include"); + context.addServlet(EchoServlet.class, "/echo/*"); + context.addServlet(DumpServlet.class, "/dump/*"); + context.addServlet(AsyncServlet.class, "/async/*"); + context.addServlet(BufferServlet.class, "/buffer/*"); + context.addFilter(CheckFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); _server.start(); } @@ -147,6 +148,31 @@ protected void doGet(HttpServletRequest req, HttpServletResponse response) throw } } + public static class MimeTypeContentServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + String pathInfo = req.getPathInfo(); + resp.setContentType(getContentTypeFromRequest(pathInfo, req)); + resp.getWriter().println("This is content for " + pathInfo); + } + + private String getContentTypeFromRequest(String filename, HttpServletRequest req) + { + String defaultContentType = "application/octet-stream"; + if (req.getParameter("type") != null) + defaultContentType = req.getParameter("type"); + ServletContextHandler servletContextHandler = ServletContextHandler.getServletContextHandler(getServletContext()); + if (servletContextHandler == null) + return defaultContentType; + String contentType = servletContextHandler.getMimeTypes().getMimeByExtension(filename); + if (contentType != null) + return contentType; + return defaultContentType; + } + } + public static class TestServlet extends HttpServlet { @Override @@ -797,6 +823,52 @@ public void testGzipBomb() throws Exception assertThat(response.getContentBytes().length, is(512 * 1024)); } + @Test + public void testGzipExcludeNewMimeType() throws Exception + { + // setting all excluded mime-types to a mimetype new mime-type + // Note: this mime-type does not exist in MimeTypes object. + gzipHandler.setExcludedMimeTypes("image/webfoo"); + + // generated and parsed test + HttpTester.Request request = HttpTester.newRequest(); + HttpTester.Response response; + + // Request something that is not present on MimeTypes and is also + // excluded by GzipHandler configuration + request.setMethod("GET"); + request.setURI("/ctx/mimetypes/foo.webfoo?type=image/webfoo"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host", "tester"); + request.setHeader("Accept", "*/*"); + request.setHeader("Accept-Encoding", "gzip"); // allow compressed responses + request.setHeader("Connection", "close"); + + response = HttpTester.parseResponse(_connector.getResponse(request.generate())); + + assertThat(response.getStatus(), is(200)); + assertThat("Should not be compressed with gzip", response.get("Content-Encoding"), nullValue()); + assertThat(response.get("ETag"), nullValue()); + assertThat(response.get("Vary"), nullValue()); + + // Request something that is present on MimeTypes and is also compressible + // by the GzipHandler configuration + request.setMethod("GET"); + request.setURI("/ctx/mimetypes/zed.txt"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host", "tester"); + request.setHeader("Accept", "*/*"); + request.setHeader("Accept-Encoding", "gzip"); // allow compressed responses + request.setHeader("Connection", "close"); + + response = HttpTester.parseResponse(_connector.getResponse(request.generate())); + + assertThat(response.getStatus(), is(200)); + assertThat(response.get("Content-Encoding"), containsString("gzip")); + assertThat(response.get("ETag"), nullValue()); + assertThat(response.get("Vary"), is("Accept-Encoding")); + } + public static class CheckFilter implements Filter { @Override diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/GzipModuleTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/GzipModuleTests.java new file mode 100644 index 000000000000..040e79e9d6ae --- /dev/null +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/GzipModuleTests.java @@ -0,0 +1,176 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.tests.distribution; + +import java.io.File; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GzipModuleTests extends AbstractJettyHomeTest +{ + @Test + public void testGzipDefault() throws Exception + { + Path jettyBase = newTestJettyBaseDirectory(); + String jettyVersion = System.getProperty("jettyVersion"); + JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + int httpPort = distribution.freePort(); + + String[] argsConfig = { + "--add-modules=gzip", + "--add-modules=deploy,webapp,http" + }; + + try (JettyHomeTester.Run runConfig = distribution.start(argsConfig)) + { + assertTrue(runConfig.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, runConfig.getExitValue()); + + String[] argsStart = { + "jetty.http.port=" + httpPort, + "jetty.httpConfig.port=" + httpPort + }; + + File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-simple-webapp:war:" + jettyVersion); + distribution.installWarFile(war, "demo"); + + try (JettyHomeTester.Run runStart = distribution.start(argsStart)) + { + assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/index.html"); + String responseDetails = toResponseDetails(response); + assertEquals(HttpStatus.OK_200, response.getStatus(), responseDetails); + assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_ENCODING), containsString("gzip")); + } + } + } + + @Test + public void testGzipDefaultExcludedMimeType() throws Exception + { + Path jettyBase = newTestJettyBaseDirectory(); + String jettyVersion = System.getProperty("jettyVersion"); + JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + int httpPort = distribution.freePort(); + + String[] argsConfig = { + "--add-modules=gzip", + "--add-modules=deploy,webapp,http" + }; + + try (JettyHomeTester.Run runConfig = distribution.start(argsConfig)) + { + assertTrue(runConfig.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, runConfig.getExitValue()); + + String[] argsStart = { + "jetty.http.port=" + httpPort, + "jetty.httpConfig.port=" + httpPort + }; + + File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-simple-webapp:war:" + jettyVersion); + distribution.installWarFile(war, "demo"); + + try (JettyHomeTester.Run runStart = distribution.start(argsStart)) + { + assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/jetty.webp"); + String responseDetails = toResponseDetails(response); + assertEquals(HttpStatus.OK_200, response.getStatus(), responseDetails); + assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("image/webp")); + assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_ENCODING), not(containsString("gzip"))); + } + } + } + + @Test + public void testGzipAddWebappSpecificExcludeMimeType() throws Exception + { + Path jettyBase = newTestJettyBaseDirectory(); + String jettyVersion = System.getProperty("jettyVersion"); + JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .jettyBase(jettyBase) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + int httpPort = distribution.freePort(); + + String[] argsConfig = { + "--add-modules=gzip", + "--add-modules=deploy,webapp,http" + }; + + try (JettyHomeTester.Run runConfig = distribution.start(argsConfig)) + { + assertTrue(runConfig.awaitFor(5, TimeUnit.SECONDS)); + assertEquals(0, runConfig.getExitValue()); + + String[] argsStart = { + "jetty.http.port=" + httpPort, + "jetty.httpConfig.port=" + httpPort, + "jetty.gzip.excludedMimeTypeList=image/vnd.microsoft.icon" + }; + + File war = distribution.resolveArtifact("org.eclipse.jetty.demos:demo-simple-webapp:war:" + jettyVersion); + distribution.installWarFile(war, "demo"); + + try (JettyHomeTester.Run runStart = distribution.start(argsStart)) + { + assertTrue(runStart.awaitConsoleLogsFor("Started Server@", 20, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + httpPort + "/demo/jetty.icon"); + String responseDetails = toResponseDetails(response); + assertEquals(HttpStatus.OK_200, response.getStatus(), responseDetails); + assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_ENCODING), not(containsString("gzip"))); + assertThat(responseDetails, response.getHeaders().get(HttpHeader.CONTENT_TYPE), containsString("image/vnd.microsoft.icon")); + } + } + } + + private static String toResponseDetails(ContentResponse response) + { + StringBuilder ret = new StringBuilder(); + ret.append(response.toString()).append(System.lineSeparator()); + ret.append(response.getHeaders().toString()).append(System.lineSeparator()); + ret.append(response.getContentAsString()).append(System.lineSeparator()); + return ret.toString(); + } +}