diff options
Diffstat (limited to 'jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java')
-rw-r--r-- | jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java | 775 |
1 files changed, 65 insertions, 710 deletions
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index ab958d3fb8..5e2ac37ffc 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -18,23 +18,12 @@ package org.eclipse.jetty.servlet; -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.net.MalformedURLException; import java.net.URL; -import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Enumeration; import java.util.List; import java.util.StringTokenizer; -import javax.servlet.AsyncContext; -import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.UnavailableException; @@ -42,35 +31,19 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.DateParser; -import org.eclipse.jetty.http.GzipHttpContent; 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.MimeTypes; -import org.eclipse.jetty.http.PathMap.MappedEntry; import org.eclipse.jetty.http.PreEncodedHttpField; -import org.eclipse.jetty.http.ResourceHttpContent; -import org.eclipse.jetty.io.WriterOutputStream; -import org.eclipse.jetty.server.HttpOutput; -import org.eclipse.jetty.server.InclusiveByteRange; -import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.server.ResourceCache; import org.eclipse.jetty.server.ResourceContentFactory; -import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.ResourceService; import org.eclipse.jetty.server.handler.ContextHandler; -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; -import org.eclipse.jetty.util.resource.ResourceCollection; import org.eclipse.jetty.util.resource.ResourceFactory; @@ -148,36 +121,64 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory { private static final Logger LOG = Log.getLogger(DefaultServlet.class); - private static final long serialVersionUID = 4930458713846881193L; - - private static final PreEncodedHttpField ACCEPT_RANGES = new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes"); - + private static final long serialVersionUID = 4930458713846881193L; + + private final ResourceService _resourceService; private ServletContext _servletContext; private ContextHandler _contextHandler; - private boolean _acceptRanges=true; - private boolean _dirAllowed=true; private boolean _welcomeServlets=false; private boolean _welcomeExactServlets=false; - private boolean _redirectWelcome=false; - private boolean _gzip=false; - private boolean _pathInfoOnly=false; - private boolean _etags=false; private Resource _resourceBase; private ResourceCache _cache; - private HttpContent.Factory _contentFactory; private MimeTypes _mimeTypes; private String[] _welcomes; private Resource _stylesheet; private boolean _useFileMappedBuffer=false; - private HttpField _cacheControl; private String _relativeResourceBase; private ServletHandler _servletHandler; private ServletHolder _defaultHolder; - private List<String> _gzipEquivalentFileExtensions; + public DefaultServlet() + { + _resourceService = new ResourceService() + { + @Override + protected String getWelcomeFile(String pathInContext) + { + if (_welcomes==null) + return null; + + String welcome_servlet=null; + for (int i=0;i<_welcomes.length;i++) + { + String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]); + Resource welcome=getResource(welcome_in_context); + if (welcome!=null && welcome.exists()) + return _welcomes[i]; + + if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) + { + MappedResource<ServletHolder> entry=_servletHandler.getHolderEntry(welcome_in_context); + if (entry!=null && entry.getResource()!=_defaultHolder && + (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context)))) + welcome_servlet=welcome_in_context; + + } + } + return welcome_servlet; + } + + @Override + protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + }; + } + /* ------------------------------------------------------------ */ @Override public void init() @@ -192,12 +193,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (_welcomes==null) _welcomes=new String[] {"index.html","index.jsp"}; - _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges); - _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed); - _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome); - _gzip=getInitBoolean("gzip",_gzip); - _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly); - + _resourceService.setAcceptRanges(getInitBoolean("acceptRanges",_resourceService.isAcceptRanges())); + _resourceService.setDirAllowed(getInitBoolean("dirAllowed",_resourceService.isDirAllowed())); + _resourceService.setRedirectWelcome(getInitBoolean("redirectWelcome",_resourceService.isRedirectWelcome())); + _resourceService.setGzip(getInitBoolean("gzip",_resourceService.isGzip())); + _resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly())); + _resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags())); + if ("exact".equals(getInitParameter("welcomeServlets"))) { _welcomeExactServlets=true; @@ -248,8 +250,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory String cc=getInitParameter("cacheControl"); if (cc!=null) - _cacheControl=new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cc); - + _resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cc)); + + String resourceCache = getInitParameter("resourceCache"); int max_cache_size=getInitInt("maxCacheSize", -2); int max_cached_file_size=getInitInt("maxCachedFileSize", -2); @@ -261,18 +264,13 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (_relativeResourceBase!=null || _resourceBase!=null) throw new UnavailableException("resourceCache specified with resource bases"); _cache=(ResourceCache)_servletContext.getAttribute(resourceCache); - - if (LOG.isDebugEnabled()) - LOG.debug("Cache {}={}",resourceCache,_contentFactory); } - _etags = getInitBoolean("etags",_etags); - try { if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2)) { - _cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags,_gzip); + _cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.isGzip()); if (max_cache_size>=0) _cache.setMaxCacheSize(max_cache_size); if (max_cached_file_size>=-1) @@ -288,16 +286,16 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory throw new UnavailableException(e.toString()); } - if (_cache!=null) - _contentFactory=_cache; - else + HttpContent.Factory contentFactory=_cache; + if (contentFactory==null) { - _contentFactory=new ResourceContentFactory(this,_mimeTypes,_gzip); + contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.isGzip()); if (resourceCache!=null) - _servletContext.setAttribute(resourceCache,_contentFactory); + _servletContext.setAttribute(resourceCache,contentFactory); } + _resourceService.setContentFactory(contentFactory); - _gzipEquivalentFileExtensions = new ArrayList<String>(); + List<String> gzip_equivalent_file_extensions = new ArrayList<String>(); String otherGzipExtensions = getInitParameter("otherGzipFileExtensions"); if (otherGzipExtensions != null) { @@ -306,15 +304,17 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory while (tok.hasMoreTokens()) { String s = tok.nextToken().trim(); - _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s)); + gzip_equivalent_file_extensions.add((s.charAt(0)=='.'?s:"."+s)); } } else { //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip - _gzipEquivalentFileExtensions.add(".svgz"); + gzip_equivalent_file_extensions.add(".svgz"); } + _resourceService.setGzipEquivalentFileExtensions(gzip_equivalent_file_extensions); + _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class); for (ServletHolder h :_servletHandler.getServlets()) if (h.getServletInstance()==this) @@ -433,196 +433,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory protected 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=(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); - response.sendError(HttpServletResponse.SC_NOT_FOUND); - 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(_servletContext.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( _servletContext.getContextPath(),welcome)+"?"+q)); - else - response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.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) 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()); + _resourceService.doGet(request,response); } /* ------------------------------------------------------------ */ @@ -651,462 +462,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS"); } - /* ------------------------------------------------------------ */ - /** - * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of - * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>. - * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping. - * If there is none, then <code>null</code> is returned. - * The list of welcome files is read from the {@link ContextHandler} for this servlet, or - * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>. - * @param resource - * @return The path of the matching welcome file in context or null. - * @throws IOException - * @throws MalformedURLException - */ - private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException - { - if (_welcomes==null) - return null; - - String welcome_servlet=null; - for (int i=0;i<_welcomes.length;i++) - { - String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]); - Resource welcome=getResource(welcome_in_context); - if (welcome!=null && welcome.exists()) - return _welcomes[i]; - - if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) - { - MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context); - if (entry!=null && entry.getValue()!=_defaultHolder && - (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context)))) - welcome_servlet=welcome_in_context; - - } - } - return welcome_servlet; - } - - /* ------------------------------------------------------------ */ - /* 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); - - //If the DefaultServlet has a resource base set, use it - if (_resourceBase != null) - { - // handle ResourceCollection - if (_resourceBase instanceof ResourceCollection) - resource=_resourceBase.addPath(pathInContext); - } - //Otherwise, try using the resource base of its enclosing context handler - else if (_contextHandler.getBaseResource() instanceof ResourceCollection) - resource=_contextHandler.getBaseResource().addPath(pathInContext); - - 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()) - { - 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("DefaultServlet@%x$CB", DefaultServlet.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()); - } - } /* ------------------------------------------------------------ */ /* |