package com.mossle.core.servlet; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import javax.activation.MimetypesFileTypeMap; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.mossle.core.util.ServletUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StaticContentFilter implements Filter { private static Logger logger = LoggerFactory .getLogger(StaticContentFilter.class); /** 需要被Gzip压缩的Mime类型. */ public static final String[] GZIP_MIME_TYPES = { "text/html", "application/xhtml+xml", "text/plain", "text/css", "text/javascript", "application/x-javascript", "application/json" }; /** 需要被Gzip压缩的最小文件大小. */ public static final int GZIP_MINI_LENGTH = 512; private MimetypesFileTypeMap mimetypesFileTypeMap; private long expiresSeconds; private FilterConfig filterConfig; public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; // 初始化mimeTypes, 默认缺少css的定义,添加之. mimetypesFileTypeMap = new MimetypesFileTypeMap(); mimetypesFileTypeMap.addMimeTypes("text/css css"); } public void destroy() { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 获取请求内容的基本信息. String requestUri = request.getRequestURI(); String contentPath = requestUri.substring(request.getContextPath() .length()); ContentInfo contentInfo = getContentInfo(contentPath); if (contentInfo.getFile().isDirectory()) { if (requestUri.endsWith("/")) { response.sendRedirect(requestUri + "index.html"); } else { response.sendRedirect(requestUri + "/index.html"); } return; } if (!contentInfo.getFile().exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // 根据Etag或ModifiedSince Header判断客户端的缓存文件是否有效, 如仍有效则设置返回码为304,直接返回. if (!ServletUtils.checkIfModifiedSince(request, response, contentInfo.getLastModified()) || !ServletUtils.checkIfNoneMatchEtag(request, response, contentInfo.getEtag())) { return; } // 设置Etag/过期时间 ServletUtils.setExpiresHeader(response, ServletUtils.ONE_YEAR_SECONDS); ServletUtils.setLastModifiedHeader(response, contentInfo.getLastModified()); ServletUtils.setEtag(response, contentInfo.getEtag()); // 设置MIME类型 response.setContentType(contentInfo.getMimeType()); // 设置弹出下载文件请求窗口的Header if (request.getParameter("download") != null) { ServletUtils.setFileDownloadHeader(request, response, contentInfo.getFileName()); } // 构造OutputStream OutputStream output; // if (checkAccetptGzip(request) && contentInfo.isNeedGzip()) { // 使用压缩传输的outputstream, 使用http1.1 chunked编码不设置content-length. // output = buildGzipOutputStream(response); // } else { // 使用普通outputstream, 设置content-length. response.setContentLength(contentInfo.length); output = response.getOutputStream(); // } // 高效读取文件内容并输出,然后关闭input file FileUtils.copyFile(contentInfo.getFile(), output); output.flush(); } /** * 检查浏览器客户端是否支持gzip编码. */ private static boolean checkAccetptGzip(HttpServletRequest request) { // Http1.1 header String acceptEncoding = request.getHeader("Accept-Encoding"); return StringUtils.contains(acceptEncoding, "gzip"); } /** * 设置Gzip Header并返回GZIPOutputStream. */ private OutputStream buildGzipOutputStream(HttpServletResponse response) throws IOException { response.setHeader("Content-Encoding", "gzip"); response.setHeader("Vary", "Accept-Encoding"); return new GZIPOutputStream(response.getOutputStream()); } /** * 创建Content基本信息. */ private ContentInfo getContentInfo(String contentPath) { ContentInfo contentInfo = new ContentInfo(); String realFilePath = this.findRealFilePath(contentPath); logger.debug("realFilePath {}", realFilePath); File file = new File(realFilePath); contentInfo.setFile(file); contentInfo.setContentPath(contentPath); contentInfo.setFileName(file.getName()); contentInfo.setLength((int) file.length()); contentInfo.setLastModified(file.lastModified()); contentInfo.setEtag("W/\"" + contentInfo.lastModified + "\""); contentInfo.setMimeType(mimetypesFileTypeMap .getContentType(contentInfo.fileName)); if ((contentInfo.length >= GZIP_MINI_LENGTH) && ArrayUtils.contains(GZIP_MIME_TYPES, contentInfo.getMimeType())) { contentInfo.setNeedGzip(true); } else { contentInfo.setNeedGzip(false); } return contentInfo; } public String findRealFilePath(String contentPath) { String realFilePath = filterConfig.getServletContext().getRealPath( contentPath); return realFilePath; } public void setExpiresSeconds(long expiresSeconds) { this.expiresSeconds = expiresSeconds; } static class ContentInfo { private String contentPath; private File file; private String fileName; private int length; private String mimeType; private long lastModified; private String etag; private boolean needGzip; public String getContentPath() { return this.contentPath; } public void setContentPath(String contentPath) { this.contentPath = contentPath; } public File getFile() { return this.file; } public void setFile(File file) { this.file = file; } public String getFileName() { return this.fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public int getLength() { return this.length; } public void setLength(int length) { this.length = length; } public String getMimeType() { return this.mimeType; } public void setMimeType(String mimeType) { this.mimeType = mimeType; } public long getLastModified() { return this.lastModified; } public void setLastModified(long lastModified) { this.lastModified = lastModified; } public String getEtag() { return this.etag; } public void setEtag(String etag) { this.etag = etag; } public boolean isNeedGzip() { return this.needGzip; } public void setNeedGzip(boolean needGzip) { this.needGzip = needGzip; } } }