Author: michiel
Date: 2010-07-02 08:56:11 +0200 (Fri, 02 Jul 2010)
New Revision: 42790
Modified:
mmbase/branches/MMBase-1_9/applications/resources/src/main/java/org/mmbase/servlet/FileServlet.java
Log:
backported support for Ranges from trunk (MMB-1909)
Modified:
mmbase/branches/MMBase-1_9/applications/resources/src/main/java/org/mmbase/servlet/FileServlet.java
===================================================================
---
mmbase/branches/MMBase-1_9/applications/resources/src/main/java/org/mmbase/servlet/FileServlet.java
2010-07-02 06:48:46 UTC (rev 42789)
+++
mmbase/branches/MMBase-1_9/applications/resources/src/main/java/org/mmbase/servlet/FileServlet.java
2010-07-02 06:56:11 UTC (rev 42790)
@@ -34,7 +34,7 @@
* @see AttachmentServlet
*/
public class FileServlet extends BridgeServlet {
- private static Logger log;
+ private static Logger log = Logging.getLoggerInstance(FileServlet.class);
private static final UrlEscaper URL = new UrlEscaper();
private static final String SESSION_EXTENSION = ".SESSION";
@@ -76,9 +76,7 @@
@Override
public void setMMBase(MMBase mmb) {
super.setMMBase(mmb);
- if (log == null) {
- log = Logging.getLoggerInstance(FileServlet.class);
- }
+ log = Logging.getLoggerInstance(FileServlet.class);
if (files == null) {
File dataDir = MMBase.getMMBase().getDataDir();
@@ -265,8 +263,18 @@
if (file.isDirectory()) {
resp.setContentType("application/xhtml+xml"); // We hate IE
anyways.
} else {
+ resp.addHeader("Accept-Ranges", "bytes");
resp.setContentType(getServletContext().getMimeType(file.getName()));
- resp.setContentLength((int) file.length());
+ final ChainedRange range = getRange(req, file);
+ if (range != null) {
+ long length = range.getLength();
+ resp.setContentLength((int) length);
+ if (length < file.length()) {
+ resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ }
+ } else {
+ resp.setContentLength((int) file.length());
+ }
if (metaFiles) {
for (Map.Entry<String, String> e :
getMetaHeaders(file).entrySet()) {
resp.setHeader(e.getKey(), e.getValue());
@@ -295,27 +303,229 @@
if (file == null) {
return;
}
+ OutputStream out = resp.getOutputStream();
setHeaders(req, resp, file);
+
if (file.isDirectory()) {
- listing(req, resp, file);
+ listing(req, resp, out, file);
} else {
- stream(req, resp, file);
+ stream(req, resp, out, file);
}
}
- protected void stream(HttpServletRequest req, HttpServletResponse resp,
File file) throws IOException {
- BufferedOutputStream out = new
BufferedOutputStream(resp.getOutputStream());
- BufferedInputStream in = new BufferedInputStream(new
FileInputStream(file));
+
+ /**
+ * @TODO Ranges stuff can be generalized to also work e.g. with images and
attachments.
+ *
+ * Tomcat has an implementation of these headers: Content-Range,
Accep-Ranges, Range and If-Range.
+ * Use that? See org.apache.catalina.servlets.DefaultServlet
+ *
http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/servlets/DefaultServlet.html
+ *
+ * @since MMBase-2.0
+ */
+ protected static interface Range {
+ /**
+ * If we are at byte number i, how many are available from here until
we encounter one which isn't?
+ * @return A number of bytes which are available, <code>0</code> if
there are not bytes available. A large number near <code>Long.MAX_VALUE</code>
if there is no limit any more.
+ */
+ long available(long i);
+
+ /**
+ * If we are at byte number i, how many are not available from here
until we encounter one which is?
+ * @return Some number of bytes or <code>0</code> if the next
character is availabe. A large number near <code>Long.MAX_VALUE</code> if all
subsequent byes are unavailable.
+ */
+ long notavailable(long i);
+ }
+
+ /**
+ * Implementation of Range simply stating the first and last chars which
are available, perhaps with a maximum too.
+ * This only deals with <start>-<stop> entries in the Range specificiation.
+ * @since MMBase-2.0
+ */
+ protected static class FirstLastRange implements Range {
+ private final long first;
+ private final long last;
+ private final long max;
+ FirstLastRange(long f, long l, long m) {
+ first = f; last = Math.min(m, l);
+ max = m;
+ }
+ FirstLastRange(String parse, long max) {
+ String[] fl = parse.split("-", 2);
+ String firstString = fl[0].trim();
+ String lastString = fl[1].trim();
+ if (firstString.length() == 0) {
+ first = max - Long.parseLong(lastString);
+ last = max - 1;
+ } else {
+ first = Long.parseLong(firstString);
+ last = Math.min(max - 1, lastString.length() > 0 ?
Long.parseLong(lastString) : Long.MAX_VALUE);
+ }
+ this.max = max;
+ }
+ public long available(long i) {
+ if (i < first) return 0;
+ if (i > last) return 0;
+ return last - i + 1;
+ }
+ public long notavailable(long i) {
+ if (i < first) return first - i;
+ if (i > last) return max - last;
+ return 0;
+ }
+ @Override
+ public String toString() {
+ return "" + first + "-" + (last < Long.MAX_VALUE ? last : "");
+ }
+ }
+ /**
+ * This implementation of Range parses and combines a number of {...@link
FirstLastRange}s.
+ * So, this deals with the entire Range specification then.
+ * @since MMBase-2.0
+ */
+ protected static class ChainedRange implements Range {
+ final List<Range> ranges = new ArrayList<Range>();
+ final long max;
+ ChainedRange(String s, long max) {
+ String[] array = s.split(",");
+ for (String r : array) {
+ ranges.add(new FirstLastRange(r, max));
+ }
+ this.max = max;
+ }
+
+ public long available(long i) {
+ long available = 0;
+ for (Range r : ranges) {
+ long a = r.available(i);
+ available += a;
+ i += a;
+ }
+ return available;
+ }
+ public long notavailable(long i) {
+ long notavailable = max;
+ for (Range r : ranges) {
+ long na = r.notavailable(i);
+ if (na < notavailable) notavailable = na;
+ }
+ return notavailable;
+ }
+
+ public long getLength() {
+ long pos = 0;
+ long length = 0;
+ while (pos < max) {
+ long available = available(pos);
+ if (available > 0) {
+ length += available;
+ }
+ //System.out.println(pos + "/" + max + " available " +
available);
+ pos += available;
+ long notavailable = notavailable(pos);
+ pos += notavailable;
+
+ }
+ if (length > max) length = max;
+ return length;
+ }
+ @Override
+ public String toString() {
+ StringBuilder bul = new StringBuilder();
+ for (Range r : ranges) {
+ if (bul.length() > 0) bul.append(",");
+ bul.append(r.toString());
+ }
+ return bul.append("/").append(max).toString();
+ }
+ }
+
+ /**
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+ * @return A ChainedRange object if Range header was present and If-Range
didn't provide useage. <code>null</code> otherwise.
+ * @since MMBase-2.0
+ */
+ protected ChainedRange getRange(HttpServletRequest req, File file) {
+ try {
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27
+ long ifRange = req.getDateHeader("If-Range");
+ if (ifRange != -1) {
+ if (ifRange < file.lastModified()) {
+ log.debug("cannot use partial content, because the file
was changed in the mean time " + new Date(ifRange) + " < " + new
Date(file.lastModified()));
+ return null;
+ }
+ }
+ } catch (IllegalArgumentException ie) {
+ // never mind, it may be entity tag, which we don't support
+ log.warn("Could not parse " + req.getHeader("If-Range"));
+ }
+
+ String range = req.getHeader("Range");
+
+ if (range != null) {
+ String r[] = range.split("=");
+ if (r.length == 2 && r[0].trim().toLowerCase().equals("bytes")) {
+ ChainedRange parsed = new ChainedRange(r[1], file.length());
+ if (log.isDebugEnabled()) {
+ log.debug("Range: " + range + " -> " + r[1] + " -> " +
parsed);
+ }
+ return parsed;
+ }
+ }
+ return null;
+
+ }
+
+ /**
+ * @todo Generalize this stuff with Ranges to HandleServlet, so that it
also could work for images and attachments.
+ */
+ protected static void stream(ChainedRange range, InputStream in,
OutputStream out) throws IOException {
byte[] buf = new byte[1024];
- int b = 0;
- while ((b = in.read(buf)) != -1) {
- out.write(buf, 0, b);
+ if (range != null) {
+
+ long pos = 0;
+ while (pos < range.max) {
+ long available = range.available(pos);
+ if (log.isTraceEnabled()) {
+ log.trace("streaming " + available);
+ }
+ while(available > 0L) {
+ int b = in.read(buf, 0, (int) Math.min(available, 1024L));
+ out.write(buf, 0, b);
+ pos += b;
+ available -= b;
+ }
+ long notavailable = range.notavailable(pos);
+ if (notavailable > 0L) {
+ in.skip(notavailable);
+ pos += notavailable;
+ }
+ }
+ } else {
+ int b = 0;
+ while ((b = in.read(buf)) != -1) {
+ out.write(buf, 0, b);
+ }
}
out.flush();
in.close();
out.close();
}
+
+ protected void stream(HttpServletRequest req, HttpServletResponse resp,
OutputStream o, File file) throws IOException {
+ BufferedOutputStream out = new BufferedOutputStream(o);
+ BufferedInputStream in = new BufferedInputStream(new
FileInputStream(file));
+ final ChainedRange range = getRange(req, file);
+ if (range != null) {
+ log.info("USING RANGE " + range);
+ resp.addHeader("Content-Range", "bytes " + range.toString());
+ } else {
+ log.debug("No range in request found " +
Collections.list(req.getHeaderNames()));
+ }
+ stream(range, in, out);
+ }
+
private static final FormatFileSize formatFileSize = new FormatFileSize();
private static final Xml XML = new Xml();
@@ -386,10 +596,10 @@
return result.toString().getBytes();
}
}
- protected void listing(HttpServletRequest req, HttpServletResponse resp,
File directory) throws IOException {
+ protected void listing(HttpServletRequest req, HttpServletResponse resp,
OutputStream o, File directory) throws IOException {
byte [] bytes = getListingBytes(req, resp, directory);
resp.setContentLength(bytes.length);
- BufferedOutputStream out = new
BufferedOutputStream(resp.getOutputStream());
+ BufferedOutputStream out = new BufferedOutputStream(o);
out.write(bytes);
out.flush();
}
@@ -403,7 +613,7 @@
}
org.mmbase.bridge.Cloud cloud =
getCloud(readQuery(req.getQueryString()));
if (cloud.getUser().getRank() == org.mmbase.security.Rank.ANONYMOUS) {
- resp.sendError(HttpServletResponse.SC_FORBIDDEN, "The file '" +
req.getPathInfo() + "' already exists");
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Anonymous may
not put files");
return;
}
BufferedOutputStream out = new BufferedOutputStream(new
FileOutputStream(file));
_______________________________________________
Cvs mailing list
[email protected]
http://lists.mmbase.org/mailman/listinfo/cvs