/******************************************************************************* * Copyright (c) 2005, 2014 springside.github.io * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ package org.springside.examples.showcase.demos.web; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.zip.GZIPOutputStream; import javax.activation.MimetypesFileTypeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springside.modules.web.Servlets; /** * 本地静态内容展示与下载的Servlet. * * 演示文件高效读取,客户端缓存控制及Gzip压缩传输. * 可使用org.springside.examples.showcase.cache包下的Ehcache或本地Map缓存静态内容基本信息(未演示). * * 演示访问地址为: * static-content?contentPath=static/images/logo.jpg * static-content?contentPath=static/images/logo.jpg&download=true * * @author calvin */ public class StaticContentServlet extends HttpServlet { private static final long serialVersionUID = -1855617048198368534L; /** 需要被Gzip压缩的Mime类型. */ private static final String[] GZIP_MIME_TYPES = { "text/html", "application/xhtml+xml", "text/plain", "text/css", "text/javascript", "application/x-javascript", "application/json" }; /** 需要被Gzip压缩的最小文件大小. */ private static final int GZIP_MINI_LENGTH = 512; private MimetypesFileTypeMap mimetypesFileTypeMap; private ApplicationContext applicationContext; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 取得参数 String contentPath = request.getParameter("contentPath"); if (StringUtils.isBlank(contentPath)) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "contentPath parameter is required."); return; } // 获取请求内容的基本信息. ContentInfo contentInfo = getContentInfo(contentPath); // 根据Etag或ModifiedSince Header判断客户端的缓存文件是否有效, 如仍有效则设置返回码为304,直接返回. if (!Servlets.checkIfModifiedSince(request, response, contentInfo.lastModified) || !Servlets.checkIfNoneMatchEtag(request, response, contentInfo.etag)) { return; } // 设置Etag/过期时间 Servlets.setExpiresHeader(response, Servlets.ONE_YEAR_SECONDS); Servlets.setLastModifiedHeader(response, contentInfo.lastModified); Servlets.setEtag(response, contentInfo.etag); // 设置MIME类型 response.setContentType(contentInfo.mimeType); // 设置弹出下载文件请求窗口的Header if (request.getParameter("download") != null) { Servlets.setFileDownloadHeader(request, response, contentInfo.fileName); } // 构造OutputStream OutputStream output; if (checkAccetptGzip(request) && contentInfo.needGzip) { // 使用压缩传输的outputstream, 使用http1.1 trunked编码不设置content-length. output = buildGzipOutputStream(response); } else { // 使用普通outputstream, 设置content-length. response.setContentLength(contentInfo.length); output = response.getOutputStream(); } // 高效读取文件内容并输出,然后关闭input file FileUtils.copyFile(contentInfo.file, 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()); } /** * 初始化. */ @Override public void init() throws ServletException { // 保存applicationContext以备后用,纯演示. applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); // 初始化mimeTypes, 默认缺少css的定义,添加之. mimetypesFileTypeMap = new MimetypesFileTypeMap(); mimetypesFileTypeMap.addMimeTypes("text/css css"); } /** * 创建Content基本信息. */ private ContentInfo getContentInfo(String contentPath) { ContentInfo contentInfo = new ContentInfo(); String realFilePath = getServletContext().getRealPath(contentPath); File file = new File(realFilePath); contentInfo.file = file; contentInfo.contentPath = contentPath; contentInfo.fileName = file.getName(); contentInfo.length = (int) file.length(); contentInfo.lastModified = file.lastModified(); contentInfo.etag = "W/\"" + contentInfo.lastModified + "\""; contentInfo.mimeType = mimetypesFileTypeMap.getContentType(contentInfo.fileName); if ((contentInfo.length >= GZIP_MINI_LENGTH) && ArrayUtils.contains(GZIP_MIME_TYPES, contentInfo.mimeType)) { contentInfo.needGzip = true; } else { contentInfo.needGzip = false; } return contentInfo; } /** * 定义Content的基本信息. */ static class ContentInfo { protected String contentPath; protected File file; protected String fileName; protected int length; protected String mimeType; protected long lastModified; protected String etag; protected boolean needGzip; } }