mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Static resource compression
- added compile time maven plugin to compress css and js files - Added new StaticResourceServlet to serve the requests to static files, this replaces the tomcat DefaultServlet - Tests - mapping of the static resource servlet to css and js files Signed-off-by: Laszlo Hornyak <laszlo.hornyak@gmail.com>
This commit is contained in:
parent
ecec8d368d
commit
fc68922286
@ -58,10 +58,15 @@
|
|||||||
<servlet-name>registerCompleteServlet</servlet-name>
|
<servlet-name>registerCompleteServlet</servlet-name>
|
||||||
<servlet-class>com.cloud.servlet.RegisterCompleteServlet</servlet-class>
|
<servlet-class>com.cloud.servlet.RegisterCompleteServlet</servlet-class>
|
||||||
</servlet>
|
</servlet>
|
||||||
|
|
||||||
<servlet-mapping>
|
<servlet>
|
||||||
<servlet-name>apiServlet</servlet-name>
|
<servlet-name>staticResources</servlet-name>
|
||||||
<url-pattern>/api/*</url-pattern>
|
<servlet-class>com.cloud.servlet.StaticResourceServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>apiServlet</servlet-name>
|
||||||
|
<url-pattern>/api/*</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
@ -73,4 +78,11 @@
|
|||||||
<servlet-name>registerCompleteServlet</servlet-name>
|
<servlet-name>registerCompleteServlet</servlet-name>
|
||||||
<url-pattern>/mycloud/complete</url-pattern>
|
<url-pattern>/mycloud/complete</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>staticResources</servlet-name>
|
||||||
|
<url-pattern>*.css</url-pattern>
|
||||||
|
<url-pattern>*.html</url-pattern>
|
||||||
|
<url-pattern>*.js</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
</web-app>
|
</web-app>
|
||||||
|
|||||||
@ -358,6 +358,23 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.googlecode.todomap</groupId>
|
||||||
|
<artifactId>maven-jettygzip-plugin</artifactId>
|
||||||
|
<version>0.0.4</version>
|
||||||
|
<configuration>
|
||||||
|
<webappDirectory>${project.build.directory}/generated-webapp</webappDirectory>
|
||||||
|
<outputDirectory>${project.build.directory}/generated-webapp</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>process</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-war-plugin</artifactId>
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
|||||||
115
server/src/com/cloud/servlet/StaticResourceServlet.java
Normal file
115
server/src/com/cloud/servlet/StaticResourceServlet.java
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 com.cloud.servlet;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serves static resources with support for gzip compression and content
|
||||||
|
* caching.
|
||||||
|
*/
|
||||||
|
public class StaticResourceServlet extends HttpServlet {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -8833228931973461812L;
|
||||||
|
|
||||||
|
private File getRequestedFile(final HttpServletRequest req) {
|
||||||
|
return new File(getServletContext().getRealPath(req.getServletPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(final HttpServletRequest req,
|
||||||
|
final HttpServletResponse resp) throws ServletException,
|
||||||
|
IOException {
|
||||||
|
final File requestedFile = getRequestedFile(req);
|
||||||
|
if (!requestedFile.exists() || !requestedFile.isFile()) {
|
||||||
|
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String etag = getEtag(requestedFile);
|
||||||
|
if (etag.equals(req.getHeader("If-None-Match"))) {
|
||||||
|
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// have to send data, either compressed or the original
|
||||||
|
final File compressedStatic = getCompressedVersion(requestedFile);
|
||||||
|
InputStream fileContent = null;
|
||||||
|
try {
|
||||||
|
resp.setContentType(getContentType(requestedFile.getName()));
|
||||||
|
resp.setHeader("ETag", etag);
|
||||||
|
resp.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
if (isClientCompressionSupported(req) && compressedStatic.exists()) {
|
||||||
|
// gzip compressed
|
||||||
|
resp.setHeader("Content-Encoding", "gzip");
|
||||||
|
resp.setContentLength((int) compressedStatic.length());
|
||||||
|
fileContent = new FileInputStream(compressedStatic);
|
||||||
|
} else {
|
||||||
|
// uncompressed
|
||||||
|
resp.setContentLength((int) requestedFile.length());
|
||||||
|
fileContent = new FileInputStream(requestedFile);
|
||||||
|
}
|
||||||
|
IOUtils.copy(fileContent, resp.getOutputStream());
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(fileContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
static final Map<String, String> contentTypes = Collections
|
||||||
|
.unmodifiableMap(new HashMap<String, String>() {
|
||||||
|
{
|
||||||
|
put("css", "text/css");
|
||||||
|
put("svg", "image/svg+xml");
|
||||||
|
put("js", "application/javascript");
|
||||||
|
put("htm", "text/html");
|
||||||
|
put("html", "text/html");
|
||||||
|
put("txt", "text/plain");
|
||||||
|
put("xml", "text/xml");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static String getContentType(final String fileName) {
|
||||||
|
return contentTypes.get(StringUtils.lowerCase(StringUtils
|
||||||
|
.substringAfterLast(fileName, ".")));
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getCompressedVersion(final File requestedFile) {
|
||||||
|
return new File(requestedFile.getAbsolutePath() + ".gz");
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isClientCompressionSupported(final HttpServletRequest req) {
|
||||||
|
return StringUtils.contains(req.getHeader("Accept-Encoding"), "gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getEtag(final File resource) {
|
||||||
|
return "W/\"" + resource.length() + "-" + resource.lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
235
server/test/com/cloud/servlet/StaticResourceServletTest.java
Normal file
235
server/test/com/cloud/servlet/StaticResourceServletTest.java
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
// 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 com.cloud.servlet;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Matchers;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
public class StaticResourceServletTest {
|
||||||
|
|
||||||
|
File rootDirectory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupFiles() throws IOException {
|
||||||
|
rootDirectory = new File("target/tmp");
|
||||||
|
rootDirectory.mkdirs();
|
||||||
|
final File webInf = new File(rootDirectory, "WEB-INF");
|
||||||
|
webInf.mkdirs();
|
||||||
|
final File dir = new File(rootDirectory, "dir");
|
||||||
|
dir.mkdirs();
|
||||||
|
final File indexHtml = new File(rootDirectory, "index.html");
|
||||||
|
indexHtml.createNewFile();
|
||||||
|
FileUtils.writeStringToFile(indexHtml, "index.html");
|
||||||
|
final File defaultCss = new File(rootDirectory, "default.css");
|
||||||
|
defaultCss.createNewFile();
|
||||||
|
final File defaultCssGziped = new File(rootDirectory, "default.css.gz");
|
||||||
|
defaultCssGziped.createNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanupFiles() {
|
||||||
|
FileUtils.deleteQuietly(rootDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// negative tests
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSuchFile() throws ServletException, IOException {
|
||||||
|
final StaticResourceServlet servlet = Mockito
|
||||||
|
.mock(StaticResourceServlet.class);
|
||||||
|
Mockito.doCallRealMethod()
|
||||||
|
.when(servlet)
|
||||||
|
.doGet(Matchers.any(HttpServletRequest.class),
|
||||||
|
Matchers.any(HttpServletResponse.class));
|
||||||
|
final ServletContext servletContext = Mockito
|
||||||
|
.mock(ServletContext.class);
|
||||||
|
Mockito.when(servletContext.getRealPath("notexisting.css")).thenReturn(
|
||||||
|
new File(rootDirectory, "notexisting.css").getAbsolutePath());
|
||||||
|
Mockito.when(servlet.getServletContext()).thenReturn(servletContext);
|
||||||
|
|
||||||
|
final HttpServletRequest request = Mockito
|
||||||
|
.mock(HttpServletRequest.class);
|
||||||
|
Mockito.when(request.getServletPath()).thenReturn("notexisting.css");
|
||||||
|
final HttpServletResponse response = Mockito
|
||||||
|
.mock(HttpServletResponse.class);
|
||||||
|
servlet.doGet(request, response);
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDirectory() throws ServletException, IOException {
|
||||||
|
final HttpServletResponse response = doGetTest("dir");
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWebInf() throws ServletException, IOException {
|
||||||
|
final HttpServletResponse response = doGetTest("WEB-INF/web.xml");
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// positive tests
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNotCompressedFile() throws ServletException, IOException {
|
||||||
|
final HttpServletResponse response = doGetTest("index.html");
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
|
||||||
|
Mockito.verify(response).setContentType("text/html");
|
||||||
|
Mockito.verify(response, Mockito.times(0)).setHeader(
|
||||||
|
"Content-Encoding", "gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompressedFile() throws ServletException, IOException {
|
||||||
|
final HashMap<String, String> headers = new HashMap<String, String>();
|
||||||
|
headers.put("Accept-Encoding", "gzip");
|
||||||
|
final HttpServletResponse response = doGetTest("default.css", headers);
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
|
||||||
|
Mockito.verify(response).setContentType("text/css");
|
||||||
|
Mockito.verify(response, Mockito.times(1)).setHeader(
|
||||||
|
"Content-Encoding", "gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompressedFileWithoutBrowserSupport()
|
||||||
|
throws ServletException, IOException {
|
||||||
|
final HashMap<String, String> headers = new HashMap<String, String>();
|
||||||
|
headers.put("Accept-Encoding", "");
|
||||||
|
final HttpServletResponse response = doGetTest("default.css", headers);
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
|
||||||
|
Mockito.verify(response).setContentType("text/css");
|
||||||
|
Mockito.verify(response, Mockito.times(0)).setHeader(
|
||||||
|
"Content-Encoding", "gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithEtag() throws ServletException, IOException {
|
||||||
|
final HashMap<String, String> headers = new HashMap<String, String>();
|
||||||
|
headers.put("If-None-Match", StaticResourceServlet.getEtag(new File(
|
||||||
|
rootDirectory, "default.css")));
|
||||||
|
final HttpServletResponse response = doGetTest("default.css", headers);
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithEtagOutdated() throws ServletException, IOException {
|
||||||
|
final HashMap<String, String> headers = new HashMap<String, String>();
|
||||||
|
headers.put("If-None-Match", "NO-GOOD-ETAG");
|
||||||
|
final HttpServletResponse response = doGetTest("default.css", headers);
|
||||||
|
Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility methods
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getEtag() {
|
||||||
|
Assert.assertNotNull(StaticResourceServlet.getEtag(new File(
|
||||||
|
rootDirectory, "index.html")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getContentType() {
|
||||||
|
Assert.assertEquals("text/plain",
|
||||||
|
StaticResourceServlet.getContentType("foo.txt"));
|
||||||
|
Assert.assertEquals("text/html",
|
||||||
|
StaticResourceServlet.getContentType("index.html"));
|
||||||
|
Assert.assertEquals("text/plain",
|
||||||
|
StaticResourceServlet.getContentType("README.TXT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isClientCompressionSupported() {
|
||||||
|
final HttpServletRequest request = Mockito
|
||||||
|
.mock(HttpServletRequest.class);
|
||||||
|
Mockito.when(request.getHeader("Accept-Encoding")).thenReturn(
|
||||||
|
"gzip, deflate");
|
||||||
|
Assert.assertTrue(StaticResourceServlet
|
||||||
|
.isClientCompressionSupported(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isClientCompressionSupportedWithoutHeader() {
|
||||||
|
final HttpServletRequest request = Mockito
|
||||||
|
.mock(HttpServletRequest.class);
|
||||||
|
Mockito.when(request.getHeader("Accept-Encoding")).thenReturn(null);
|
||||||
|
Assert.assertFalse(StaticResourceServlet
|
||||||
|
.isClientCompressionSupported(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
// test utilities
|
||||||
|
private HttpServletResponse doGetTest(final String uri)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
return doGetTest(uri, Collections.<String, String> emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServletResponse doGetTest(final String uri,
|
||||||
|
final Map<String, String> headers) throws ServletException,
|
||||||
|
IOException {
|
||||||
|
final StaticResourceServlet servlet = Mockito
|
||||||
|
.mock(StaticResourceServlet.class);
|
||||||
|
Mockito.doCallRealMethod()
|
||||||
|
.when(servlet)
|
||||||
|
.doGet(Matchers.any(HttpServletRequest.class),
|
||||||
|
Matchers.any(HttpServletResponse.class));
|
||||||
|
final ServletContext servletContext = Mockito
|
||||||
|
.mock(ServletContext.class);
|
||||||
|
Mockito.when(servletContext.getRealPath(uri)).thenReturn(
|
||||||
|
new File(rootDirectory, uri).getAbsolutePath());
|
||||||
|
Mockito.when(servlet.getServletContext()).thenReturn(servletContext);
|
||||||
|
|
||||||
|
final HttpServletRequest request = Mockito
|
||||||
|
.mock(HttpServletRequest.class);
|
||||||
|
Mockito.when(request.getServletPath()).thenReturn(uri);
|
||||||
|
Mockito.when(request.getHeader(Matchers.anyString())).thenAnswer(
|
||||||
|
new Answer<String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String answer(final InvocationOnMock invocation)
|
||||||
|
throws Throwable {
|
||||||
|
return headers.get(invocation.getArguments()[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final HttpServletResponse response = Mockito
|
||||||
|
.mock(HttpServletResponse.class);
|
||||||
|
final ServletOutputStream responseBody = Mockito
|
||||||
|
.mock(ServletOutputStream.class);
|
||||||
|
Mockito.when(response.getOutputStream()).thenReturn(responseBody);
|
||||||
|
servlet.doGet(request, response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user