cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ko...@apache.org
Subject git commit: updated refs/heads/master to fc68922
Date Mon, 28 Apr 2014 20:16:57 GMT
Repository: cloudstack
Updated Branches:
  refs/heads/master ecec8d368 -> fc6892228


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>


Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/fc689222
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/fc689222
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/fc689222

Branch: refs/heads/master
Commit: fc68922286c42153f38a3862273d16223e86d7ea
Parents: ecec8d3
Author: Laszlo Hornyak <laszlo.hornyak@gmail.com>
Authored: Thu Sep 19 11:28:23 2013 +0200
Committer: Laszlo Hornyak <laszlo.hornyak@gmail.com>
Committed: Mon Apr 28 21:10:18 2014 +0200

----------------------------------------------------------------------
 client/WEB-INF/web.xml                          |  20 +-
 client/pom.xml                                  |  17 ++
 .../cloud/servlet/StaticResourceServlet.java    | 115 +++++++++
 .../servlet/StaticResourceServletTest.java      | 235 +++++++++++++++++++
 4 files changed, 383 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cloudstack/blob/fc689222/client/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/client/WEB-INF/web.xml b/client/WEB-INF/web.xml
index 1af38e1..6509a97 100644
--- a/client/WEB-INF/web.xml
+++ b/client/WEB-INF/web.xml
@@ -58,10 +58,15 @@
         <servlet-name>registerCompleteServlet</servlet-name>
         <servlet-class>com.cloud.servlet.RegisterCompleteServlet</servlet-class>
     </servlet>
-    
-    <servlet-mapping>   
-         <servlet-name>apiServlet</servlet-name>   
-         <url-pattern>/api/*</url-pattern> 
+ 
+    <servlet>
+        <servlet-name>staticResources</servlet-name>
+        <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>   
@@ -73,4 +78,11 @@
          <servlet-name>registerCompleteServlet</servlet-name>   
          <url-pattern>/mycloud/complete</url-pattern> 
     </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>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/fc689222/client/pom.xml
----------------------------------------------------------------------
diff --git a/client/pom.xml b/client/pom.xml
index 5038e66..5c56d08 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -359,6 +359,23 @@
         </executions>
       </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>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-war-plugin</artifactId>
         <version>2.3</version>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/fc689222/server/src/com/cloud/servlet/StaticResourceServlet.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/servlet/StaticResourceServlet.java b/server/src/com/cloud/servlet/StaticResourceServlet.java
new file mode 100644
index 0000000..8e96732
--- /dev/null
+++ b/server/src/com/cloud/servlet/StaticResourceServlet.java
@@ -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();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/fc689222/server/test/com/cloud/servlet/StaticResourceServletTest.java
----------------------------------------------------------------------
diff --git a/server/test/com/cloud/servlet/StaticResourceServletTest.java b/server/test/com/cloud/servlet/StaticResourceServletTest.java
new file mode 100644
index 0000000..ae5c384
--- /dev/null
+++ b/server/test/com/cloud/servlet/StaticResourceServletTest.java
@@ -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;
+    }
+
+}


Mime
View raw message