Package org.springframework.js.resource

Source Code of org.springframework.js.resource.ResourceServlet$GZIPResponseStream

/*
* Copyright 2004-2012 the original author or authors.
*
* Licensed 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.springframework.js.resource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ClassUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HttpServletBean;

/**
* Special resource servlet for efficiently resolving and rendering static resources from within a JAR file.
*
* @author Jeremy Grelle
* @author Scott Andrews
*
* @deprecated Deprecated in favor of the new resource handling mechanism available in Spring 3.0.4. See the
* documentation on the custom mvc namespace, specifically the new resources element.
*/
public class ResourceServlet extends HttpServletBean {

  private static final String HTTP_CONTENT_LENGTH_HEADER = "Content-Length";

  private static final String HTTP_LAST_MODIFIED_HEADER = "Last-Modified";

  private static final String HTTP_EXPIRES_HEADER = "Expires";

  private static final String HTTP_CACHE_CONTROL_HEADER = "Cache-Control";

  private static final Log log = LogFactory.getLog(ResourceServlet.class);

  private final String protectedPath = "/?WEB-INF/.*";

  private String jarPathPrefix = "META-INF";

  private String springJsJarPathPrefix = "META-INF/web-resources";

  private boolean gzipEnabled = true;

  private Set<String> allowedResourcePaths = new HashSet<String>();
  {
    allowedResourcePaths.add("/**/*.css");
    allowedResourcePaths.add("/**/*.gif");
    allowedResourcePaths.add("/**/*.ico");
    allowedResourcePaths.add("/**/*.jpeg");
    allowedResourcePaths.add("/**/*.jpg");
    allowedResourcePaths.add("/**/*.js");
    allowedResourcePaths.add("/**/*.png");
    allowedResourcePaths.add("META-INF/**/*.css");
    allowedResourcePaths.add("META-INF/**/*.gif");
    allowedResourcePaths.add("META-INF/**/*.ico");
    allowedResourcePaths.add("META-INF/**/*.jpeg");
    allowedResourcePaths.add("META-INF/**/*.jpg");
    allowedResourcePaths.add("META-INF/**/*.js");
    allowedResourcePaths.add("META-INF/**/*.png");
  };

  private Map<String, String> defaultMimeTypes = new HashMap<String, String>();
  {
    defaultMimeTypes.put(".css", "text/css");
    defaultMimeTypes.put(".gif", "image/gif");
    defaultMimeTypes.put(".ico", "image/vnd.microsoft.icon");
    defaultMimeTypes.put(".jpeg", "image/jpeg");
    defaultMimeTypes.put(".jpg", "image/jpeg");
    defaultMimeTypes.put(".js", "text/javascript");
    defaultMimeTypes.put(".png", "image/png");
  }

  private Set<String> compressedMimeTypes = new HashSet<String>();
  {
    compressedMimeTypes.add("text/*");
  }

  private int cacheTimeout = 31556926;

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    String rawResourcePath = request.getPathInfo();

    if (log.isDebugEnabled()) {
      log.debug("Attempting to GET resource: " + rawResourcePath);
    }

    URL[] resources = getRequestResourceURLs(request);

    if (resources == null || resources.length == 0) {
      if (log.isDebugEnabled()) {
        log.debug("Resource not found: " + rawResourcePath);
      }
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    prepareResponse(response, resources, rawResourcePath);

    OutputStream out = selectOutputStream(request, response);

    try {
      for (URL resource : resources) {
        URLConnection resourceConn = resource.openConnection();
        InputStream in = resourceConn.getInputStream();
        try {
          byte[] buffer = new byte[1024];
          int bytesRead = -1;
          while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
          }
        } finally {
          in.close();
        }
      }
    } finally {
      out.close();
    }
  }

  private OutputStream selectOutputStream(HttpServletRequest request, HttpServletResponse response)
      throws IOException {

    String acceptEncoding = request.getHeader("Accept-Encoding");
    String mimeType = response.getContentType();

    if (gzipEnabled && StringUtils.hasText(acceptEncoding) && acceptEncoding.indexOf("gzip") > -1
        && matchesCompressedMimeTypes(mimeType)) {
      log.debug("Enabling GZIP compression for the current response.");
      return new GZIPResponseStream(response);
    } else {
      return response.getOutputStream();
    }
  }

  private boolean matchesCompressedMimeTypes(String mimeType) {
    PathMatcher pathMatcher = new AntPathMatcher();
    for (String compressedMimeType : compressedMimeTypes) {
      if (pathMatcher.match(compressedMimeType, mimeType)) {
        return true;
      }
    }
    return false;
  }

  private void prepareResponse(HttpServletResponse response, URL[] resources, String rawResourcePath)
      throws IOException {
    long lastModified = -1;
    int contentLength = 0;
    String mimeType = null;
    for (URL resource : resources) {
      URLConnection resourceConn = resource.openConnection();
      if (resourceConn.getLastModified() > lastModified) {
        lastModified = resourceConn.getLastModified();
      }

      String currentMimeType = getServletContext().getMimeType(resource.getPath());
      if (currentMimeType == null) {
        String extension = resource.getPath().substring(resource.getPath().lastIndexOf('.'));
        currentMimeType = defaultMimeTypes.get(extension);
      }
      if (mimeType == null) {
        mimeType = currentMimeType;
      } else if (!mimeType.equals(currentMimeType)) {
        throw new MalformedURLException("Combined resource path: " + rawResourcePath
            + " is invalid. All resources in a combined resource path must be of the same mime type.");
      }
      contentLength += resourceConn.getContentLength();
    }

    response.setContentType(mimeType);
    response.setHeader(HTTP_CONTENT_LENGTH_HEADER, Long.toString(contentLength));
    response.setDateHeader(HTTP_LAST_MODIFIED_HEADER, lastModified);
    if (cacheTimeout > 0) {
      configureCaching(response, cacheTimeout);
    }
  }

  protected long getLastModified(HttpServletRequest request) {
    if (log.isDebugEnabled()) {
      log.debug("Checking last modified of resource: " + request.getPathInfo());
    }
    URL[] resources;
    try {
      resources = getRequestResourceURLs(request);
    } catch (MalformedURLException e) {
      return -1;
    }

    if (resources == null || resources.length == 0) {
      return -1;
    }

    long lastModified = -1;

    for (URL resource : resources) {
      URLConnection resourceConn;
      try {
        resourceConn = resource.openConnection();
      } catch (IOException e) {
        return -1;
      }
      if (resourceConn.getLastModified() > lastModified) {
        lastModified = resourceConn.getLastModified();
      }
    }
    return lastModified;
  }

  private URL[] getRequestResourceURLs(HttpServletRequest request) throws MalformedURLException {

    String rawResourcePath = request.getPathInfo();
    String appendedPaths = request.getParameter("appended");
    if (StringUtils.hasText(appendedPaths)) {
      rawResourcePath = rawResourcePath + "," + appendedPaths;
    }
    String[] localResourcePaths = StringUtils.delimitedListToStringArray(rawResourcePath, ",");
    URL[] resources = new URL[localResourcePaths.length];
    for (int i = 0; i < localResourcePaths.length; i++) {
      String localResourcePath = localResourcePaths[i];
      if (!isAllowed(localResourcePath)) {
        if (log.isWarnEnabled()) {
          log.warn("An attempt to access a protected resource at " + localResourcePath + " was disallowed.");
        }
        return null;
      }
      URL resource = getServletContext().getResource(localResourcePath);
      if (resource == null) {
        resource = getJarResource(springJsJarPathPrefix, localResourcePath);
      }
      if (resource == null) {
        resource = getJarResource(jarPathPrefix, localResourcePath);
      }
      if (resource == null) {
        if (resources.length > 1) {
          log.debug("Combined resource not found: " + localResourcePath);
        }
        return null;
      } else {
        resources[i] = resource;
      }
    }
    return resources;
  }

  private URL getJarResource(String jarPrefix, String resourcePath) {
    String jarResourcePath = jarPrefix + resourcePath;
    if (!isAllowed(jarResourcePath)) {
      if (log.isWarnEnabled()) {
        log.warn("An attempt to access a protected resource at " + jarResourcePath + " was disallowed.");
      }
      return null;
    }
    if (jarResourcePath.startsWith("/")) {
      jarResourcePath = jarResourcePath.substring(1);
    }
    if (log.isDebugEnabled()) {
      log.debug("Searching classpath for resource: " + jarResourcePath);
    }
    return ClassUtils.getDefaultClassLoader().getResource(jarResourcePath);
  }

  private boolean isAllowed(String resourcePath) {
    if (resourcePath.matches(protectedPath)) {
      return false;
    }
    PathMatcher pathMatcher = new AntPathMatcher();
    for (String pattern : allowedResourcePaths) {
      if (pathMatcher.match(pattern, resourcePath)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Set HTTP headers to allow caching for the given number of seconds.
   * @param seconds number of seconds into the future that the response should be cacheable for
   */
  private void configureCaching(HttpServletResponse response, int seconds) {
    // HTTP 1.0 header
    response.setDateHeader(HTTP_EXPIRES_HEADER, System.currentTimeMillis() + seconds * 1000L);
    // HTTP 1.1 header
    response.setHeader(HTTP_CACHE_CONTROL_HEADER, "max-age=" + seconds);
  }

  private class GZIPResponseStream extends ServletOutputStream {

    private ByteArrayOutputStream byteStream = null;

    private GZIPOutputStream gzipStream = null;

    private boolean closed = false;

    private HttpServletResponse response = null;

    private ServletOutputStream servletStream = null;

    public GZIPResponseStream(HttpServletResponse response) throws IOException {
      super();
      closed = false;
      this.response = response;
      this.servletStream = response.getOutputStream();
      byteStream = new ByteArrayOutputStream();
      gzipStream = new GZIPOutputStream(byteStream);
    }

    public void close() throws IOException {
      if (closed) {
        throw new IOException("This output stream has already been closed");
      }
      gzipStream.finish();

      byte[] bytes = byteStream.toByteArray();

      response.setContentLength(bytes.length);
      response.addHeader("Content-Encoding", "gzip");
      servletStream.write(bytes);
      servletStream.flush();
      servletStream.close();
      closed = true;
    }

    public void flush() throws IOException {
      if (closed) {
        throw new IOException("Cannot flush a closed output stream");
      }
      gzipStream.flush();
    }

    public void write(int b) throws IOException {
      if (closed) {
        throw new IOException("Cannot write to a closed output stream");
      }
      gzipStream.write((byte) b);
    }

    public void write(byte b[]) throws IOException {
      write(b, 0, b.length);
    }

    public void write(byte b[], int off, int len) throws IOException {
      if (closed) {
        throw new IOException("Cannot write to a closed output stream");
      }
      gzipStream.write(b, off, len);
    }
  }

  /**
   * Set whether to apply gzip compression to resources if the requesting client supports it.
   */
  public void setGzipEnabled(boolean gzipEnabled) {
    this.gzipEnabled = gzipEnabled;
  }

  /**
   * Set allowed resources as an comma separated String of URL patterns, e.g. "META-INF/** /*.js", The paths may be
   * any Ant-style pattern parsable by AntPathMatcher.
   *
   * @see AntPathMatcher
   */
  public void setAllowedResourcePaths(String allowedResourcePaths) {
    this.allowedResourcePaths = new HashSet<String>(Arrays.asList(StringUtils.tokenizeToStringArray(
        allowedResourcePaths, ",", true, true)));
  }

  /**
   * Set comma separated MIME types that should have gzip compression applied. Typically, gzip compression is only
   * useful for text based content. Ant-style patterns are supported, e.g. "text/*".
   *
   * @see AntPathMatcher
   */
  public void setCompressedMimeTypes(String compressedMimeTypes) {
    this.compressedMimeTypes = new HashSet<String>(Arrays.asList(StringUtils.tokenizeToStringArray(
        compressedMimeTypes, ",", true, true)));
  }

  /**
   * Set the default path prefix to apply to resources being served from jar files. Default is "META-INF".
   */
  public void setJarPathPrefix(String jarPathPrefix) {
    this.jarPathPrefix = jarPathPrefix;
  }

  /**
   * Set the number of seconds resources should be cached by the client. Zero disables caching. Default is one year.
   */
  public void setCacheTimeout(int cacheTimeout) {
    this.cacheTimeout = cacheTimeout;
  }

}
TOP

Related Classes of org.springframework.js.resource.ResourceServlet$GZIPResponseStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.