fileservlet.java
来自「RESIN 3.2 最新源码」· Java 代码 · 共 626 行
JAVA
626 行
/* * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */package com.caucho.servlets;import com.caucho.server.connection.CauchoRequest;import com.caucho.server.connection.CauchoResponse;import com.caucho.server.util.CauchoSystem;import com.caucho.server.webapp.Application;import com.caucho.util.Alarm;import com.caucho.util.Base64;import com.caucho.util.CharBuffer;import com.caucho.util.LruCache;import com.caucho.util.QDate;import com.caucho.util.RandomUtil;import com.caucho.vfs.CaseInsensitive;import com.caucho.vfs.Path;import com.caucho.vfs.ReadStream;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.FileNotFoundException;import java.io.IOException;import java.io.OutputStream;import java.util.logging.Level;import java.util.logging.Logger;/** * Serves static files. The cache headers are automatically set on these * files. */public class FileServlet extends GenericServlet { private static final Logger log = Logger.getLogger(FileServlet.class.getName()); private Path _context; private Application _app; private RequestDispatcher _dir; private LruCache<String,Cache> _pathCache; private QDate _calendar = new QDate(); private boolean _isCaseInsensitive; private boolean _isEnableRange = true; private String _characterEncoding; public FileServlet() { _isCaseInsensitive = CaseInsensitive.isCaseInsensitive(); } /** * Sets the character encoding. */ public void setCharacterEncoding(String encoding) { _characterEncoding = encoding; } /** * Flag to disable the "Range" header. */ public void setEnableRange(boolean isEnable) { _isEnableRange = isEnable; } @Override public void init(ServletConfig conf) throws ServletException { super.init(conf); _app = (Application) getServletContext(); _context = _app.getAppDir(); try { _dir = _app.getNamedDispatcher("directory"); } catch (Exception e) { log.finest(e.toString()); } _pathCache = new LruCache<String,Cache>(1024); String enable = getInitParameter("enable-range"); if (enable != null && enable.equals("false")) _isEnableRange = false; String encoding = getInitParameter("character-encoding"); if (encoding != null && ! "".equals(encoding)) _characterEncoding = encoding; } public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { CauchoRequest cauchoReq = null; HttpServletRequest req; HttpServletResponse res; if (request instanceof CauchoRequest) { cauchoReq = (CauchoRequest) request; req = cauchoReq; } else req = (HttpServletRequest) request; res = (HttpServletResponse) response; String method = req.getMethod(); if (! method.equalsIgnoreCase("GET") && ! method.equalsIgnoreCase("HEAD") && ! method.equalsIgnoreCase("POST")) { res.sendError(res.SC_NOT_IMPLEMENTED, "Method not implemented"); return; } boolean isInclude = false; String uri; uri = (String) req.getAttribute("javax.servlet.include.request_uri"); if (uri != null) isInclude = true; else uri = req.getRequestURI(); Cache cache = _pathCache.get(uri); String filename = null; if (cache == null) { CharBuffer cb = new CharBuffer(); String servletPath; if (cauchoReq != null) servletPath = cauchoReq.getPageServletPath(); else if (isInclude) servletPath = (String) req.getAttribute("javax.servlet.include.servlet_path"); else servletPath = req.getServletPath(); if (servletPath != null) cb.append(servletPath); String pathInfo; if (cauchoReq != null) pathInfo = cauchoReq.getPagePathInfo(); else if (isInclude) pathInfo = (String) req.getAttribute("javax.servlet.include.path_info"); else pathInfo = req.getPathInfo(); if (pathInfo != null) cb.append(pathInfo); String relPath = cb.toString(); if (_isCaseInsensitive) relPath = relPath.toLowerCase(); filename = getServletContext().getRealPath(relPath); Path path = _context.lookupNative(filename); // only top-level requests are checked if (cauchoReq == null || cauchoReq.getRequestDepth(0) != 0) { } else if (relPath.regionMatches(true, 0, "/web-inf", 0, 8) && (relPath.length() == 8 || ! Character.isLetterOrDigit(relPath.charAt(8)))) { res.sendError(res.SC_NOT_FOUND); return; } else if (relPath.regionMatches(true, 0, "/meta-inf", 0, 9) && (relPath.length() == 9 || ! Character.isLetterOrDigit(relPath.charAt(9)))) { res.sendError(res.SC_NOT_FOUND); return; } if (relPath.endsWith(".DS_store")) { // MacOS-X security hole with trailing '.' res.sendError(res.SC_NOT_FOUND); return; } else if (! CauchoSystem.isWindows() || relPath.length() == 0) { } else if (path.isDirectory()) { } else if (path.isWindowsInsecure()) { // Windows security issues with trailing '.' res.sendError(res.SC_NOT_FOUND); return; } // A null will cause problems. for (int i = relPath.length() - 1; i >= 0; i--) { char ch = relPath.charAt(i); if (ch == 0) { res.sendError(res.SC_NOT_FOUND); return; } } ServletContext app = getServletContext(); cache = new Cache(_calendar, path, relPath, app.getMimeType(relPath)); _pathCache.put(uri, cache); } cache.update(); if (cache.isDirectory()) { if (_dir != null) _dir.forward(req, res); else res.sendError(res.SC_NOT_FOUND); return; } if (! cache.canRead()) { if (isInclude) throw new FileNotFoundException(uri); else res.sendError(res.SC_NOT_FOUND); return; } String ifMatch = req.getHeader("If-None-Match"); String etag = cache.getEtag(); if (ifMatch != null && ifMatch.equals(etag)) { res.addHeader("ETag", etag); res.sendError(res.SC_NOT_MODIFIED); return; } String lastModified = cache.getLastModifiedString(); if (ifMatch == null) { String ifModified = req.getHeader("If-Modified-Since"); boolean isModified = true; if (ifModified == null) { } else if (ifModified.equals(lastModified)) { isModified = false; } else { long ifModifiedTime; synchronized (_calendar) { try { ifModifiedTime = _calendar.parseDate(ifModified); } catch (Exception e) { log.log(Level.FINER, e.toString(), e); ifModifiedTime = 0; } } isModified = ifModifiedTime != cache.getLastModified(); } if (! isModified) { if (etag != null) res.addHeader("ETag", etag); res.sendError(res.SC_NOT_MODIFIED); return; } } res.addHeader("ETag", etag); res.addHeader("Last-Modified", lastModified); if (_isEnableRange && cauchoReq != null && cauchoReq.isTop()) res.addHeader("Accept-Ranges", "bytes"); if (_characterEncoding != null) res.setCharacterEncoding(_characterEncoding); String mime = cache.getMimeType(); if (mime != null) res.setContentType(mime); if (method.equalsIgnoreCase("HEAD")) { res.setContentLength((int) cache.getLength()); return; } if (_isEnableRange) { String range = req.getHeader("Range"); if (range != null) { String ifRange = req.getHeader("If-Range"); if (ifRange != null && ! ifRange.equals(etag)) { } else if (handleRange(req, res, cache, range, mime)) return; } } res.setContentLength((int) cache.getLength()); if (res instanceof CauchoResponse) { CauchoResponse cRes = (CauchoResponse) res; cRes.getResponseStream().sendFile(cache.getPath(), cache.getLength()); } else { OutputStream os = res.getOutputStream(); cache.getPath().writeToStream(os); } } private boolean handleRange(HttpServletRequest req, HttpServletResponse res, Cache cache, String range, String mime) throws IOException { // This is duplicated in CacheInvocation. Possibly, it should be // completely removed although it's useful even without caching. int length = range.length(); boolean hasMore = range.indexOf(',') > 0; int head = 0; ServletOutputStream os = res.getOutputStream(); boolean isFirstChunk = true; String boundary = null; int off = range.indexOf("bytes=", head); if (off < 0) return false; off += 6; while (off > 0 && off < length) { boolean hasFirst = false; long first = 0; boolean hasLast = false; long last = 0; int ch = -1; // Skip whitespace for (; off < length && (ch = range.charAt(off)) == ' '; off++) { } // read range start (before '-') for (; off < length && (ch = range.charAt(off)) >= '0' && ch <= '9'; off++) { first = 10 * first + ch - '0'; hasFirst = true; } if (length <= off && ! isFirstChunk) break; else if (ch != '-') return false; // read range end (before '-') for (off++; off < length && (ch = range.charAt(off)) >= '0' && ch <= '9'; off++) { last = 10 * last + ch - '0'; hasLast = true; } // Skip whitespace for (; off < length && (ch = range.charAt(off)) == ' '; off++) { } head = off; long cacheLength = cache.getLength(); if (! hasLast) { if (first == 0) return false; last = cacheLength - 1; } // suffix if (! hasFirst) { first = cacheLength - last; last = cacheLength - 1; } if (last < first) break; if (cacheLength <= last) { // XXX: actually, an error break; } res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); StringBuilder cb = new StringBuilder(); cb.append("bytes "); cb.append(first); cb.append('-'); cb.append(last); cb.append('/'); cb.append(cacheLength); String chunkRange = cb.toString(); if (hasMore) { if (isFirstChunk) { StringBuilder cb1 = new StringBuilder(); cb1.append("--"); Base64.encode(cb1, RandomUtil.getRandomLong()); boundary = cb1.toString(); res.setContentType("multipart/byteranges; boundary=" + boundary); } else { os.write('\r'); os.write('\n'); } isFirstChunk = false; os.write('-'); os.write('-'); os.print(boundary); os.print("\r\nContent-Type: "); os.print(mime); os.print("\r\nContent-Range: "); os.print(chunkRange); os.write('\r'); os.write('\n'); os.write('\r'); os.write('\n'); } else { res.setContentLength((int) (last - first + 1)); res.addHeader("Content-Range", chunkRange); } ReadStream is = null; try { is = cache.getPath().openRead(); is.skip(first); os = res.getOutputStream(); is.writeToStream(os, (int) (last - first + 1)); } finally { if (is != null) is.close(); } for (off--; off < length && range.charAt(off) != ','; off++) { } off++; } if (hasMore) { os.write('\r'); os.write('\n'); os.write('-'); os.write('-'); os.print(boundary); os.write('-'); os.write('-'); os.write('\r'); os.write('\n'); } return true; } static class Cache { private final static long UPDATE_INTERVAL = 2000L; QDate _calendar; Path _path; boolean _isDirectory; boolean _canRead; long _length; long _lastCheck; long _lastModified = 0xdeadbabe1ee7d00dL; String _relPath; String _etag; String _lastModifiedString; String _mimeType; Cache(QDate calendar, Path path, String relPath, String mimeType) { _calendar = calendar; _path = path; _relPath = relPath; _mimeType = mimeType; update(); } Path getPath() { return _path; } boolean canRead() { return _canRead; } boolean isDirectory() { return _isDirectory; } long getLength() { return _length; } String getRelPath() { return _relPath; } String getEtag() { return _etag; } long getLastModified() { return _lastModified; } String getLastModifiedString() { return _lastModifiedString; } String getMimeType() { return _mimeType; } void update() { long now = Alarm.getCurrentTime(); if (_lastCheck + UPDATE_INTERVAL < now) { synchronized (this) { if (now <= _lastCheck + UPDATE_INTERVAL) return; if (_lastCheck == 0) { updateData(); _lastCheck = now; return; } _lastCheck = now; } updateData(); } } private void updateData() { long lastModified = _path.getLastModified(); long length = _path.getLength(); if (lastModified != _lastModified || length != _length) { _lastModified = lastModified; _length = length; _canRead = _path.canRead(); _isDirectory = _path.isDirectory(); StringBuilder sb = new StringBuilder(); sb.append('"'); Base64.encode(sb, _path.getCrc64()); sb.append('"'); _etag = sb.toString(); synchronized (_calendar) { _calendar.setGMTTime(lastModified); _lastModifiedString = _calendar.printDate(); } } if (lastModified == 0) { _canRead = false; _isDirectory = false; } } }}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?