Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java')
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java781
1 files changed, 781 insertions, 0 deletions
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
new file mode 100644
index 0000000000..1dbaa0668f
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
@@ -0,0 +1,781 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
+import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.DateParser;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.PreEncodedHttpField;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiPartOutputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * Abstract resource service, used by DefaultServlet and ResourceHandler
+ *
+ */
+public abstract class ResourceService
+{
+ private static final Logger LOG = Log.getLogger(ResourceService.class);
+
+ private static final PreEncodedHttpField ACCEPT_RANGES = new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
+
+ private HttpContent.Factory _contentFactory;
+ private boolean _acceptRanges=true;
+ private boolean _dirAllowed=true;
+ private boolean _redirectWelcome=false;
+ private boolean _gzip=false;
+ private boolean _pathInfoOnly=false;
+ private boolean _etags=false;
+ private HttpField _cacheControl;
+ private List<String> _gzipEquivalentFileExtensions;
+
+ public HttpContent.Factory getContentFactory()
+ {
+ return _contentFactory;
+ }
+
+ public void setContentFactory(HttpContent.Factory contentFactory)
+ {
+ _contentFactory = contentFactory;
+ }
+
+ public boolean isAcceptRanges()
+ {
+ return _acceptRanges;
+ }
+
+ public void setAcceptRanges(boolean acceptRanges)
+ {
+ _acceptRanges = acceptRanges;
+ }
+
+ public boolean isDirAllowed()
+ {
+ return _dirAllowed;
+ }
+
+ public void setDirAllowed(boolean dirAllowed)
+ {
+ _dirAllowed = dirAllowed;
+ }
+
+ public boolean isRedirectWelcome()
+ {
+ return _redirectWelcome;
+ }
+
+ public void setRedirectWelcome(boolean redirectWelcome)
+ {
+ _redirectWelcome = redirectWelcome;
+ }
+
+ public boolean isGzip()
+ {
+ return _gzip;
+ }
+
+ public void setGzip(boolean gzip)
+ {
+ _gzip = gzip;
+ }
+
+ public boolean isPathInfoOnly()
+ {
+ return _pathInfoOnly;
+ }
+
+ public void setPathInfoOnly(boolean pathInfoOnly)
+ {
+ _pathInfoOnly = pathInfoOnly;
+ }
+
+ public boolean isEtags()
+ {
+ return _etags;
+ }
+
+ public void setEtags(boolean etags)
+ {
+ _etags = etags;
+ }
+
+ public HttpField getCacheControl()
+ {
+ return _cacheControl;
+ }
+
+ public void setCacheControl(HttpField cacheControl)
+ {
+ _cacheControl = cacheControl;
+ }
+
+ public List<String> getGzipEquivalentFileExtensions()
+ {
+ return _gzipEquivalentFileExtensions;
+ }
+
+ public void setGzipEquivalentFileExtensions(List<String> gzipEquivalentFileExtensions)
+ {
+ _gzipEquivalentFileExtensions = gzipEquivalentFileExtensions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ String servletPath=null;
+ String pathInfo=null;
+ Enumeration<String> reqRanges = null;
+ boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
+ if (included)
+ {
+ servletPath= _pathInfoOnly?"/":(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+ if (servletPath==null)
+ {
+ servletPath=request.getServletPath();
+ pathInfo=request.getPathInfo();
+ }
+ }
+ else
+ {
+ servletPath = _pathInfoOnly?"/":request.getServletPath();
+ pathInfo = request.getPathInfo();
+
+ // Is this a Range request?
+ reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
+ if (!hasDefinedRange(reqRanges))
+ reqRanges = null;
+ }
+
+ String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+
+ boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+ boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
+
+ HttpContent content=null;
+ boolean release_content=true;
+ try
+ {
+ // Find the content
+ content=_contentFactory.getContent(pathInContext,response.getBufferSize());
+ if (LOG.isDebugEnabled())
+ LOG.info("content={}",content);
+
+ // Not found?
+ if (content==null || !content.getResource().exists())
+ {
+ if (included)
+ throw new FileNotFoundException("!" + pathInContext);
+ notFound(request,response);
+ return;
+ }
+
+ // Directory?
+ if (content.getResource().isDirectory())
+ {
+ sendWelcome(content,pathInContext,endsWithSlash,included,request,response);
+ return;
+ }
+
+ // Strip slash?
+ if (endsWithSlash && pathInContext.length()>1)
+ {
+ String q=request.getQueryString();
+ pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+ if (q!=null&&q.length()!=0)
+ pathInContext+="?"+q;
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),pathInContext)));
+ return;
+ }
+
+ // Conditional response?
+ if (!included && !passConditionalHeaders(request,response,content))
+ return;
+
+ // Gzip?
+ HttpContent gzip_content = gzippable?content.getGzipContent():null;
+ if (gzip_content!=null)
+ {
+ // Tell caches that response may vary by accept-encoding
+ response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+
+ // Does the client accept gzip?
+ String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+ if (accept!=null && accept.indexOf("gzip")>=0)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("gzip={}",gzip_content);
+ content=gzip_content;
+ }
+ }
+
+ // TODO this should be done by HttpContent#getContentEncoding
+ if (isGzippedContent(pathInContext))
+ response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
+
+ // Send the data
+ release_content=sendData(request,response,included,content,reqRanges);
+
+ }
+ catch(IllegalArgumentException e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ if(!response.isCommitted())
+ response.sendError(500, e.getMessage());
+ }
+ finally
+ {
+ if (release_content)
+ {
+ if (content!=null)
+ content.release();
+ }
+ }
+ }
+
+
+ protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ // Redirect to directory
+ if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+ {
+ StringBuffer buf=request.getRequestURL();
+ synchronized(buf)
+ {
+ int param=buf.lastIndexOf(";");
+ if (param<0)
+ buf.append('/');
+ else
+ buf.insert(param,'/');
+ String q=request.getQueryString();
+ if (q!=null&&q.length()!=0)
+ {
+ buf.append('?');
+ buf.append(q);
+ }
+ response.setContentLength(0);
+ response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+ }
+ return;
+ }
+
+ // look for a welcome file
+ String welcome=getWelcomeFile(pathInContext);
+ if (welcome!=null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("welcome={}",welcome);
+ if (_redirectWelcome)
+ {
+ // Redirect to the index
+ response.setContentLength(0);
+ String q=request.getQueryString();
+ if (q!=null&&q.length()!=0)
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),welcome)+"?"+q));
+ else
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),welcome)));
+ }
+ else
+ {
+ // Forward to the index
+ RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
+ if (dispatcher!=null)
+ {
+ if (included)
+ dispatcher.include(request,response);
+ else
+ {
+ request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+ dispatcher.forward(request,response);
+ }
+ }
+ }
+ return;
+ }
+
+ if (included || passConditionalHeaders(request,response, content))
+ sendDirectory(request,response,content.getResource(),pathInContext);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean isGzippedContent(String path)
+ {
+ if (path == null || _gzipEquivalentFileExtensions==null)
+ return false;
+
+ for (String suffix:_gzipEquivalentFileExtensions)
+ if (path.endsWith(suffix))
+ return true;
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ private boolean hasDefinedRange(Enumeration<String> reqRanges)
+ {
+ return (reqRanges!=null && reqRanges.hasMoreElements());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Finds a matching welcome file for the supplied {@link Resource}.
+ * @param pathInContext the path of the request
+ * @return The path of the matching welcome file in context or null.
+ */
+ protected abstract String getWelcomeFile(String pathInContext);
+
+ protected abstract void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ /* Check modification date headers.
+ */
+ protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
+ throws IOException
+ {
+ try
+ {
+ String ifm=null;
+ String ifnm=null;
+ String ifms=null;
+ long ifums=-1;
+
+ if (request instanceof Request)
+ {
+ // Find multiple fields by iteration as an optimization
+ HttpFields fields = ((Request)request).getHttpFields();
+ for (int i=fields.size();i-->0;)
+ {
+ HttpField field=fields.getField(i);
+ if (field.getHeader() != null)
+ {
+ switch (field.getHeader())
+ {
+ case IF_MATCH:
+ ifm=field.getValue();
+ break;
+ case IF_NONE_MATCH:
+ ifnm=field.getValue();
+ break;
+ case IF_MODIFIED_SINCE:
+ ifms=field.getValue();
+ break;
+ case IF_UNMODIFIED_SINCE:
+ ifums=DateParser.parseDate(field.getValue());
+ break;
+ default:
+ }
+ }
+ }
+ }
+ else
+ {
+ ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
+ ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+ ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+ ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+ }
+
+ if (!HttpMethod.HEAD.is(request.getMethod()))
+ {
+ if (_etags)
+ {
+ String etag=content.getETagValue();
+ if (ifm!=null)
+ {
+ boolean match=false;
+ if (etag!=null)
+ {
+ QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
+ while (!match && quoted.hasMoreTokens())
+ {
+ String tag = quoted.nextToken();
+ if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
+ match=true;
+ }
+ }
+
+ if (!match)
+ {
+ response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return false;
+ }
+ }
+
+ if (ifnm!=null && etag!=null)
+ {
+ // Handle special case of exact match OR gzip exact match
+ if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ response.setHeader(HttpHeader.ETAG.asString(),ifnm);
+ return false;
+ }
+
+ // Handle list of tags
+ QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
+ while (quoted.hasMoreTokens())
+ {
+ String tag = quoted.nextToken();
+ if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ response.setHeader(HttpHeader.ETAG.asString(),tag);
+ return false;
+ }
+ }
+
+ // If etag requires content to be served, then do not check if-modified-since
+ return true;
+ }
+ }
+
+ // Handle if modified since
+ if (ifms!=null)
+ {
+ //Get jetty's Response impl
+ String mdlm=content.getLastModifiedValue();
+ if (mdlm!=null && ifms.equals(mdlm))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ if (_etags)
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
+ response.flushBuffer();
+ return false;
+ }
+
+ long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+ if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000)
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ if (_etags)
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
+ response.flushBuffer();
+ return false;
+ }
+ }
+
+ // Parse the if[un]modified dates and compare to resource
+ if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/1000)
+ {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return false;
+ }
+
+ }
+ }
+ catch(IllegalArgumentException iae)
+ {
+ if(!response.isCommitted())
+ response.sendError(400, iae.getMessage());
+ throw iae;
+ }
+ return true;
+ }
+
+
+ /* ------------------------------------------------------------------- */
+ protected void sendDirectory(HttpServletRequest request,
+ HttpServletResponse response,
+ Resource resource,
+ String pathInContext)
+ throws IOException
+ {
+ if (!_dirAllowed)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ byte[] data=null;
+ String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
+ String dir = resource.getListHTML(base,pathInContext.length()>1);
+ if (dir==null)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN,
+ "No directory");
+ return;
+ }
+
+ data=dir.getBytes("utf-8");
+ response.setContentType("text/html;charset=utf-8");
+ response.setContentLength(data.length);
+ response.getOutputStream().write(data);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean sendData(HttpServletRequest request,
+ HttpServletResponse response,
+ boolean include,
+ final HttpContent content,
+ Enumeration<String> reqRanges)
+ throws IOException
+ {
+ final long content_length = content.getContentLengthValue();
+
+ // Get the output stream (or writer)
+ OutputStream out =null;
+ boolean written;
+ try
+ {
+ out = response.getOutputStream();
+
+ // has something already written to the response?
+ written = out instanceof HttpOutput
+ ? ((HttpOutput)out).isWritten()
+ : true;
+ }
+ catch(IllegalStateException e)
+ {
+ out = new WriterOutputStream(response.getWriter());
+ written=true; // there may be data in writer buffer, so assume written
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
+
+ if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
+ {
+ // if there were no ranges, send entire entity
+ if (include)
+ {
+ // write without headers
+ content.getResource().writeTo(out,0,content_length);
+ }
+ // else if we can't do a bypass write because of wrapping
+ else if (written || !(out instanceof HttpOutput))
+ {
+ // write normally
+ putHeaders(response,content,written?-1:0);
+ ByteBuffer buffer = content.getIndirectBuffer();
+ if (buffer!=null)
+ BufferUtil.writeTo(buffer,out);
+ else
+ content.getResource().writeTo(out,0,content_length);
+ }
+ // else do a bypass write
+ else
+ {
+ // write the headers
+ putHeaders(response,content,0);
+
+ // write the content asynchronously if supported
+ if (request.isAsyncSupported() && content.getContentLengthValue()>response.getBufferSize())
+ {
+ final AsyncContext context = request.startAsync();
+ context.setTimeout(0);
+
+ ((HttpOutput)out).sendContent(content,new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ context.complete();
+ content.release();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (x instanceof IOException)
+ LOG.debug(x);
+ else
+ LOG.warn(x);
+ context.complete();
+ content.release();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("ResourceService@%x$CB", ResourceService.this.hashCode());
+ }
+ });
+ return false;
+ }
+ // otherwise write content blocking
+ ((HttpOutput)out).sendContent(content);
+ }
+ }
+ else
+ {
+ // Parse the satisfiable ranges
+ List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+
+ // if there are no satisfiable ranges, send 416 response
+ if (ranges==null || ranges.size()==0)
+ {
+ putHeaders(response,content,0);
+ response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+ InclusiveByteRange.to416HeaderRangeString(content_length));
+ content.getResource().writeTo(out,0,content_length);
+ return true;
+ }
+
+ // if there is only a single valid range (must be satisfiable
+ // since were here now), send that range with a 216 response
+ if ( ranges.size()== 1)
+ {
+ InclusiveByteRange singleSatisfiableRange = ranges.get(0);
+ long singleLength = singleSatisfiableRange.getSize(content_length);
+ putHeaders(response,content,singleLength);
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ if (!response.containsHeader(HttpHeader.DATE.asString()))
+ response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+ response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+ singleSatisfiableRange.toHeaderRangeString(content_length));
+ content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+ return true;
+ }
+
+ // multiple non-overlapping valid ranges cause a multipart
+ // 216 response which does not require an overall
+ // content-length header
+ //
+ putHeaders(response,content,-1);
+ String mimetype=(content==null?null:content.getContentTypeValue());
+ if (mimetype==null)
+ LOG.warn("Unknown mimetype for "+request.getRequestURI());
+ MultiPartOutputStream multi = new MultiPartOutputStream(out);
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ if (!response.containsHeader(HttpHeader.DATE.asString()))
+ response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+
+ // If the request has a "Request-Range" header then we need to
+ // send an old style multipart/x-byteranges Content-Type. This
+ // keeps Netscape and acrobat happy. This is what Apache does.
+ String ctp;
+ if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
+ ctp = "multipart/x-byteranges; boundary=";
+ else
+ ctp = "multipart/byteranges; boundary=";
+ response.setContentType(ctp+multi.getBoundary());
+
+ InputStream in=content.getResource().getInputStream();
+ long pos=0;
+
+ // calculate the content-length
+ int length=0;
+ String[] header = new String[ranges.size()];
+ for (int i=0;i<ranges.size();i++)
+ {
+ InclusiveByteRange ibr = ranges.get(i);
+ header[i]=ibr.toHeaderRangeString(content_length);
+ length+=
+ ((i>0)?2:0)+
+ 2+multi.getBoundary().length()+2+
+ (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
+ HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
+ 2+
+ (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
+ }
+ length+=2+2+multi.getBoundary().length()+2+2;
+ response.setContentLength(length);
+
+ for (int i=0;i<ranges.size();i++)
+ {
+ InclusiveByteRange ibr = ranges.get(i);
+ multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
+
+ long start=ibr.getFirst(content_length);
+ long size=ibr.getSize(content_length);
+ if (in!=null)
+ {
+ // Handle non cached resource
+ if (start<pos)
+ {
+ in.close();
+ in=content.getResource().getInputStream();
+ pos=0;
+ }
+ if (pos<start)
+ {
+ in.skip(start-pos);
+ pos=start;
+ }
+
+ IO.copy(in,multi,size);
+ pos+=size;
+ }
+ else
+ // Handle cached resource
+ content.getResource().writeTo(multi,start,size);
+ }
+ if (in!=null)
+ in.close();
+ multi.close();
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void putHeaders(HttpServletResponse response,HttpContent content, long contentLength)
+ {
+ if (response instanceof Response)
+ {
+ Response r = (Response)response;
+ r.putHeaders(content,contentLength,_etags);
+ HttpFields f = r.getHttpFields();
+ if (_acceptRanges)
+ f.put(ACCEPT_RANGES);
+
+ if (_cacheControl!=null)
+ f.put(_cacheControl);
+ }
+ else
+ {
+ Response.putHeaders(response,content,contentLength,_etags);
+ if (_acceptRanges)
+ response.setHeader(ACCEPT_RANGES.getName(),ACCEPT_RANGES.getValue());
+
+ if (_cacheControl!=null)
+ response.setHeader(_cacheControl.getName(),_cacheControl.getValue());
+ }
+ }
+
+}

Back to the top