Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   //
   //  ========================================================================
   //  Copyright (c) 1995-2013 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.servlet;
  
  import java.net.URL;
  import java.util.List;
  import java.util.Map;
  
  
  
  
  
  /* ------------------------------------------------------------ */
The default servlet. This servlet, normally mapped to /, provides the handling for static content, OPTION and TRACE methods for the context. The following initParameters are supported, these can be set either on the servlet itself or as ServletContext initParameters with a prefix of org.eclipse.jetty.servlet.Default. :
  acceptRanges      If true, range requests and responses are
                    supported

  dirAllowed        If true, directory listings are returned if no
                    welcome file is found. Else 403 Forbidden.

  welcomeServlets   If true, attempt to dispatch to welcome files
                    that are servlets, but only after no matching static
                    resources could be found. If false, then a welcome
                    file must exist on disk. If "exact", then exact
                    servlet matches are supported without an existing file.
                    Default is true.

                    This must be false if you want directory listings,
                    but have index.jsp in your welcome file list.

  redirectWelcome   If true, welcome files are redirected rather than
                    forwarded to.

  gzip              If set to true, then static content will be served as
                    gzip content encoded if a matching resource is
                    found ending with ".gz"

  resourceBase      Set to replace the context resource base

  resourceCache     If set, this is a context attribute name, which the servlet 
                    will use to look for a shared ResourceCache instance. 
                        
  relativeResourceBase
                    Set with a pathname relative to the base of the
                    servlet context root. Useful for only serving static content out
                    of only specific subdirectories.

  pathInfoOnly      If true, only the path info will be applied to the resourceBase 
                        
  stylesheet       Set with the location of an optional stylesheet that will be used
                    to decorate the directory listing html.

  aliases           If True, aliases of resources are allowed (eg. symbolic
                    links and caps variations). May bypass security constraints.
                    
  etags             If True, weak etags will be handled.

  maxCacheSize      The maximum total size of the cache or 0 for no cache.
  maxCachedFileSize The maximum size of a file to cache
  maxCachedFiles    The maximum number of files to cache

  useFileMappedBuffer
                    If set to true, it will use mapped file buffer to serve static content
                    when using NIO connector. Setting this value to false means that
                    a direct buffer will be used instead of a mapped file buffer.
                    By default, this is set to true.

  cacheControl      If set, all static content will have this value set as the cache-control
                    header.


 
 
 public class DefaultServlet extends HttpServlet implements ResourceFactory
 {
     private static final Logger LOG = Log.getLogger(DefaultServlet.class);
 
     private static final long serialVersionUID = 4930458713846881193L;
     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=true;
     private boolean _pathInfoOnly=false;
     private boolean _etags=false;
 
     private Resource _resourceBase;
     private ResourceCache _cache;
 
     private MimeTypes _mimeTypes;
     private String[] _welcomes;
     private Resource _stylesheet;
     private boolean _useFileMappedBuffer=false;
     private ByteArrayBuffer _cacheControl;
     private String _relativeResourceBase;
     private ServletHandler _servletHandler;
     private ServletHolder _defaultHolder;
 
 
     /* ------------------------------------------------------------ */
     @Override
     public void init()
     throws UnavailableException
     {
 
          = .getMimeTypes();
 
         if (==null)
             =new String[] {"index.html","index.jsp"};
 
         =getInitBoolean("acceptRanges",);
         =getInitBoolean("dirAllowed",);
         =getInitBoolean("redirectWelcome",);
         =getInitBoolean("gzip",);
         =getInitBoolean("pathInfoOnly",);
 
         if ("exact".equals(getInitParameter("welcomeServlets")))
         {
             =true;
             =false;
         }
         else
             =getInitBoolean("welcomeServlets");
 
         if (getInitParameter("aliases")!=null)
             .setAliases(getInitBoolean("aliases",false));
 
         boolean aliases=.isAliases();
         if (!aliases && !FileResource.getCheckAliases())
             throw new IllegalStateException("Alias checking disabled");
         if (aliases)
             .log("Aliases are enabled! Security constraints may be bypassed!!!");
 
         =getInitBoolean("useFileMappedBuffer",);
 
          = getInitParameter("relativeResourceBase");
 
         String rb=getInitParameter("resourceBase");
         if (rb!=null)
         {
             if (!=null)
                 throw new  UnavailableException("resourceBase & relativeResourceBase");
             try{=.newResource(rb);}
             catch (Exception e)
             {
                 .warn(.,e);
                 throw new UnavailableException(e.toString());
             }
         }
 
         String css=getInitParameter("stylesheet");
         try
         {
             if(css!=null)
             {
                  = Resource.newResource(css);
                 if(!.exists())
                 {
                     .warn("!" + css);
                      = null;
                 }
             }
             if( == null)
             {
                  = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
             }
         }	
         catch(Exception e)
         {
             .warn(e.toString());
             .debug(e);
         }
 
         String t=getInitParameter("cacheControl");
         if (t!=null)
             =new ByteArrayBuffer(t);
 
         String resourceCache = getInitParameter("resourceCache");
         int max_cache_size=getInitInt("maxCacheSize", -2);
         int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
         int max_cached_files=getInitInt("maxCachedFiles", -2);
         if (resourceCache!=null)
         {
             if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
                 .debug("ignoring resource cache configuration, using resourceCache attribute");
             if (!=null || !=null)
                 throw new UnavailableException("resourceCache specified with resource bases");
             =(ResourceCache).getAttribute(resourceCache);
 
             .debug("Cache {}={}",resourceCache,);
         }
 
          = getInitBoolean("etags",);
         
         try
         {
             if (==null && max_cached_files>0)
             {
                 new ResourceCache(null,this,,,);
 
                 if (max_cache_size>0)
                     .setMaxCacheSize(max_cache_size);
                 if (max_cached_file_size>=-1)
                     .setMaxCachedFileSize(max_cached_file_size);
                 if (max_cached_files>=-1)
                     .setMaxCachedFiles(max_cached_files);
             }
         }
         catch (Exception e)
         {
             .warn(.,e);
             throw new UnavailableException(e.toString());
         }
 
         for (ServletHolder h :.getServlets())
             if (h.getServletInstance()==this)
                 =h;
 
         
         if (.isDebugEnabled()) 
             .debug("resource base = "+);
     }

    
Compute the field _contextHandler.
In the case where the DefaultServlet is deployed on the HttpService it is likely that this method needs to be overwritten to unwrap the ServletContext facade until we reach the original jetty's ContextHandler.

Parameters:
servletContext The servletContext of this servlet.
Returns:
the jetty's ContextHandler for this servletContext.
 
     protected ContextHandler initContextHandler(ServletContext servletContext)
     {
         ContextHandler.Context scontext=ContextHandler.getCurrentContext();
         if (scontext==null)
         {
             if (servletContext instanceof ContextHandler.Context)
                 return ((ContextHandler.Context)servletContext).getContextHandler();
             else
                 throw new IllegalArgumentException("The servletContext " + servletContext + " " + 
                     servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
         }
         else
             return ContextHandler.getCurrentContext().getContextHandler();
     }
 
     /* ------------------------------------------------------------ */
     @Override
     public String getInitParameter(String name)
     {
         String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
         if (value==null)
             value=super.getInitParameter(name);
         return value;
     }
 
     /* ------------------------------------------------------------ */
     private boolean getInitBoolean(String nameboolean dft)
     {
         String value=getInitParameter(name);
         if (value==null || value.length()==0)
             return dft;
         return (value.startsWith("t")||
                 value.startsWith("T")||
                 value.startsWith("y")||
                 value.startsWith("Y")||
                 value.startsWith("1"));
     }
 
     /* ------------------------------------------------------------ */
     private int getInitInt(String nameint dft)
     {
         String value=getInitParameter(name);
         if (value==null)
             value=getInitParameter(name);
         if (value!=null && value.length()>0)
             return Integer.parseInt(value);
         return dft;
     }
 
     /* ------------------------------------------------------------ */
    
get Resource to serve. Map a path to a resource. The default implementation calls HttpContext.getResource but derived servlets may provide their own mapping.

Parameters:
pathInContext The path to find a resource for.
Returns:
The resource to serve.
 
     public Resource getResource(String pathInContext)
     {	
         Resource r=null;
         if (!=null)
             pathInContext=URIUtil.addPaths(,pathInContext);
 
         try
         {
             if (!=null)
             {
                 r = .addPath(pathInContext);
             }
             else
             {
                 URL u = .getResource(pathInContext);
                 r = .newResource(u);
             }
 
             if (.isDebugEnabled())
                 .debug("Resource "+pathInContext+"="+r);
         }
         catch (IOException e)
         {
             .ignore(e);
         }
 
         if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
             r=;
 
         return r;
     }
 
     /* ------------------------------------------------------------ */
     @Override
     protected void doGet(HttpServletRequest requestHttpServletResponse response)
     throws ServletExceptionIOException
     {
         String servletPath=null;
         String pathInfo=null;
         Enumeration<StringreqRanges = null;
         Boolean included =request.getAttribute(.)!=null;
         if (included!=null && included.booleanValue())
         {
             servletPath=(String)request.getAttribute(.);
             pathInfo=(String)request.getAttribute(.);
             if (servletPath==null)
             {
                 servletPath=request.getServletPath();
                 pathInfo=request.getPathInfo();
             }
         }
         else
         {
             included = .;
             servletPath = ?"/":request.getServletPath();
             pathInfo = request.getPathInfo();
 
             // Is this a Range request?
             reqRanges = request.getHeaders(.);
             if (!hasDefinedRange(reqRanges))
                 reqRanges = null;
         }
         
         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
         boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(.);
         
 
         // Find the resource and content
         Resource resource=null;
         HttpContent content=null;
         try
         {
             // is gzip enabled?
             String pathInContextGz=null;
             boolean gzip=false;
             if (!included.booleanValue() &&  && reqRanges==null && !endsWithSlash )
             {
                 // Look for a gzip resource
                 pathInContextGz=pathInContext+".gz";
                 if (==null)
                     resource=getResource(pathInContextGz);
                 else
                 {
                     content=.lookup(pathInContextGz);
                     resource=(content==null)?null:content.getResource();
                 }
 
                 // Does a gzip resource exist?
                 if (resource!=null && resource.exists() && !resource.isDirectory())
                 {
                     // Tell caches that response may vary by accept-encoding
                     response.setHeader(.,.);
                     
                     // Does the client accept gzip?
                     String accept=request.getHeader(.);
                     if (accept!=null && accept.indexOf("gzip")>=0)
                         gzip=true;
                 }
             }
 
             // find resource
             if (!gzip)
             {
                 if (==null)
                     resource=getResource(pathInContext);
                 else
                 {
                     content=.lookup(pathInContext);
                     resource=content==null?null:content.getResource();
                 }
             }
 
             if (.isDebugEnabled())
                 .debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
             
             // Handle resource
             if (resource==null || !resource.exists())
             {
                 if (included
                     throw new FileNotFoundException("!" + pathInContext);
                 response.sendError(.);
             }
             else if (!resource.isDirectory())
             {
                 if (endsWithSlash && .isAliases() && 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(.getContextPath(),pathInContext)));
                 }
                 else
                 {
                     // ensure we have content
                     if (content==null)
                         content=new HttpContent.ResourceAsHttpContent(resource,.getMimeByExtension(resource.toString()),response.getBufferSize(),);
 
                     if (included.booleanValue() || passConditionalHeaders(request,responseresource,content))
                     {
                         if (gzip)
                         {
                             response.setHeader(.,"gzip");
                             String mt=.getMimeType(pathInContext);
                             if (mt!=null)
                                 response.setContentType(mt);
                         }
                         sendData(request,response,included.booleanValue(),resource,content,reqRanges);
                     }
                 }
             }
             else
             {
                 String welcome=null;
 
                 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()));
                     }
                 }
                 // else look for a welcome file
                 else if (null!=(welcome=getWelcomeFile(pathInContext)))
                 {
                     .debug("welcome={}",welcome);
                     if ()
                     {
                         // Redirect to the index
                         response.setContentLength(0);
                         String q=request.getQueryString();
                         if (q!=null&&q.length()!=0)
                             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths.getContextPath(),welcome)+"?"+q));
                         else
                             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths.getContextPath(),welcome)));
                     }
                     else
                     {
                         // Forward to the index
                         RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
                         if (dispatcher!=null)
                         {
                             if (included.booleanValue())
                                 dispatcher.include(request,response);
                             else
                             {
                                 request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
                                 dispatcher.forward(request,response);
                             }
                         }
                     }
                 }
                 else
                 {
                     content=new HttpContent.ResourceAsHttpContent(resource,.getMimeByExtension(resource.toString()),);
                     if (included.booleanValue() || passConditionalHeaders(request,responseresource,content))
                         sendDirectory(request,response,resource,pathInContext);
                 }
             }
         }
         catch(IllegalArgumentException e)
         {
             .warn(.,e);
             if(!response.isCommitted())
                 response.sendError(500, e.getMessage());
         }
         finally
         {
             if (content!=null)
                 content.release();
             else if (resource!=null)
                 resource.release();
         }
 
     }
 
     /* ------------------------------------------------------------ */
     private boolean hasDefinedRange(Enumeration<StringreqRanges)
     {
         return (reqRanges!=null && reqRanges.hasMoreElements());
     }
 
     /* ------------------------------------------------------------ */
     @Override
     protected void doPost(HttpServletRequest requestHttpServletResponse response)
     throws ServletExceptionIOException
     {
         doGet(request,response);
     }
 
     /* ------------------------------------------------------------ */
     /* (non-Javadoc)
      * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
     @Override
     protected void doTrace(HttpServletRequest reqHttpServletResponse respthrows ServletExceptionIOException
     {
     }
 
     /* ------------------------------------------------------------ */
     @Override
     protected void doOptions(HttpServletRequest reqHttpServletResponse resp)
     throws ServletExceptionIOException
     {
         resp.setHeader("Allow""GET,HEAD,POST,OPTIONS");
     }
 
     /* ------------------------------------------------------------ */
    
Finds a matching welcome file for the supplied org.eclipse.jetty.util.resource.Resource. This will be the first entry in the list of configured welcome files that existing within the directory referenced by the Resource. 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 null is returned. The list of welcome files is read from the org.eclipse.jetty.server.handler.ContextHandler for this servlet, or "index.jsp" , "index.html" if that is null.

Parameters:
resource
Returns:
The path of the matching welcome file in context or null.
Throws:
java.io.IOException
java.net.MalformedURLException
 
     private String getWelcomeFile(String pathInContextthrows MalformedURLExceptionIOException
     {
         if (==null)
             return null;
 
         String welcome_servlet=null;
         for (int i=0;i<.;i++)
         {
             String welcome_in_context=URIUtil.addPaths(pathInContext,[i]);
             Resource welcome=getResource(welcome_in_context);
             if (welcome!=null && welcome.exists())
                 return [i];
 
             if (( || ) && welcome_servlet==null)
             {
                 Map.Entry entry=.getHolderEntry(welcome_in_context);
                 if (entry!=null && entry.getValue()!= &&
                         ( || ( && entry.getKey().equals(welcome_in_context))))
                     welcome_servlet=welcome_in_context;
 
             }
         }
         return welcome_servlet;
     }
 
     /* ------------------------------------------------------------ */
     /* Check modification date headers.
      */
     protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse responseResource resourceHttpContent content)
     throws IOException
     {
         try
         {
             if (!request.getMethod().equals(.) )
             {
                 if ()
                 {
                     String ifm=request.getHeader(.);
                     if (ifm!=null)
                     {
                         boolean match=false;
                         if (content!=null && content.getETag()!=null)
                         {
                             QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
                             while (!match && quoted.hasMoreTokens())
                             {
                                 String tag = quoted.nextToken();
                                 if (content.getETag().toString().equals(tag))
                                     match=true;
                             }
                         }
 
                         if (!match)
                         {
                             Response r = Response.getResponse(response);
                             r.reset(true);
                             r.setStatus(.);
                             return false;
                         }
                     }
                     
                     String ifnm=request.getHeader(.);
                     if (ifnm!=null && content!=null && content.getETag()!=null)
                     {
                         // Look for GzipFiltered version of etag
                         if (content.getETag().toString().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
                         {
                             Response r = Response.getResponse(response);
                             r.reset(true);
                             r.setStatus(.);
                             r.getHttpFields().put(.,ifnm);
                             return false;
                         }
                         
                         
                         // Handle special case of exact match.
                         if (content.getETag().toString().equals(ifnm))
                         {
                             Response r = Response.getResponse(response);
                             r.reset(true);
                             r.setStatus(.);
                             r.getHttpFields().put(.,content.getETag());
                             return false;
                         }
 
                         // Handle list of tags
                         QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
                         while (quoted.hasMoreTokens())
                         {
                             String tag = quoted.nextToken();
                             if (content.getETag().toString().equals(tag))
                             {
                                 Response r = Response.getResponse(response);
                                 r.reset(true);
                                 r.setStatus(.);
                                 r.getHttpFields().put(.,content.getETag());
                                 return false;
                             }
                         }
                         
                         return true;
                     }
                 }
                 
                 String ifms=request.getHeader(.);
                 if (ifms!=null)
                 {
                     //Get jetty's Response impl
                     Response r = Response.getResponse(response);
                                        
                     if (content!=null)
                     {
                         Buffer mdlm=content.getLastModified();
                         if (mdlm!=null)
                         {
                             if (ifms.equals(mdlm.toString()))
                             {
                                 r.reset(true);
                                 r.setStatus(.);
                                 r.flushBuffer();
                                 return false;
                             }
                         }
                     }
 
                     long ifmsl=request.getDateHeader(.);
                     if (ifmsl!=-1)
                     {
                         if (resource.lastModified()/1000 <= ifmsl/1000)
                         { 
                             r.reset(true);
                             r.setStatus(.);
                             r.flushBuffer();
                             return false;
                         }
                     }
                 }
 
                 // Parse the if[un]modified dates and compare to resource
                 long date=request.getDateHeader(.);
 
                 if (date!=-1)
                 {
                     if (resource.lastModified()/1000 > date/1000)
                     {
                         response.sendError(.);
                         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 (!)
         {
             response.sendError(.);
             return;
         }
 
         byte[] data=null;
         String base = URIUtil.addPaths(request.getRequestURI(),.);
 
         //If the DefaultServlet has a resource base set, use it
         if ( != null)
         {
             // handle ResourceCollection
             if ( instanceof ResourceCollection)
                 resource=.addPath(pathInContext);
         }
         //Otherwise, try using the resource base of its enclosing context handler
         else if (.getBaseResource() instanceof ResourceCollection)
             resource=.getBaseResource().addPath(pathInContext);
 
         String dir = resource.getListHTML(base,pathInContext.length()>1);
         if (dir==null)
         {
             response.sendError(.,
             "No directory");
             return;
         }
 
         data=dir.getBytes("UTF-8");
         response.setContentType("text/html; charset=UTF-8");
         response.setContentLength(data.length);
         response.getOutputStream().write(data);
     }
 
     /* ------------------------------------------------------------ */
     protected void sendData(HttpServletRequest request,
             HttpServletResponse response,
             boolean include,
             Resource resource,
             HttpContent content,
             Enumeration reqRanges)
     throws IOException
     {
         boolean direct;
         long content_length;
         if (content==null)
         {
             direct=false;
             content_length=resource.length();
         }
         else
         {
             Connector connector = AbstractHttpConnection.getCurrentConnection().getConnector();
             direct=connector instanceof NIOConnector && ((NIOConnector)connector).getUseDirectBuffers() && !(connector instanceof SslConnector);
             content_length=content.getContentLength();
         }
 
 
         // Get the output stream (or writer)
         OutputStream out =null;
         boolean written;
         try
         {
             out = response.getOutputStream();
 
             // has a filter already written to the response?
             written = out instanceof HttpOutput 
                 ? ((HttpOutput)out).isWritten() 
                 : AbstractHttpConnection.getCurrentConnection().getGenerator().isWritten();
         }
         catch(IllegalStateException e
         {
             out = new WriterOutputStream(response.getWriter());
             written=true// there may be data in writer buffer, so assume written
         }
         
         if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
         {
             //  if there were no ranges, send entire entity
             if (include)
             {
                 resource.writeTo(out,0,content_length);
             }
             else
             {
                 // See if a direct methods can be used?
                 if (content!=null && !written && out instanceof HttpOutput)
                 {
                     if (response instanceof Response)
                     {
                         writeOptionHeaders(((Response)response).getHttpFields());
                         ((AbstractHttpConnection.Output)out).sendContent(content);
                     }
                     else 
                     {
                         Buffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer();
                         if (buffer!=null)
                         {
                             writeHeaders(response,content,content_length);
                             ((AbstractHttpConnection.Output)out).sendContent(buffer);
                         }
                         else
                         {
                             writeHeaders(response,content,content_length);
                             resource.writeTo(out,0,content_length);
                         }
                     }
                 }
                 else 
                 {
                     // Write headers normally
                     writeHeaders(response,content,written?-1:content_length);
 
                     // Write content normally
                     Buffer buffer = (content==null)?null:content.getIndirectBuffer();
                     if (buffer!=null)
                         buffer.writeTo(out);
                     else
                         resource.writeTo(out,0,content_length);
                 }
             }
         }
         else
         {
             // Parse the satisfiable ranges
             List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
 
             //  if there are no satisfiable ranges, send 416 response
             if (ranges==null || ranges.size()==0)
             {
                 writeHeaders(responsecontentcontent_length);
                 response.setStatus(.);
                 response.setHeader(.,
                         InclusiveByteRange.to416HeaderRangeString(content_length));
                 resource.writeTo(out,0,content_length);
                 return;
             }
 
             //  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 =
                     (InclusiveByteRange)ranges.get(0);
                 long singleLength = singleSatisfiableRange.getSize(content_length);
                 writeHeaders(response,content,singleLength                     );
                 response.setStatus(.);
                 response.setHeader(.,
                         singleSatisfiableRange.toHeaderRangeString(content_length));
                 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
                 return;
             }
 
             //  multiple non-overlapping valid ranges cause a multipart
             //  216 response which does not require an overall
             //  content-length header
             //
             writeHeaders(response,content,-1);
             String mimetype=(content.getContentType()==null?null:content.getContentType().toString());
             if (mimetype==null)
                 .warn("Unknown mimetype for "+request.getRequestURI());
             MultiPartOutputStream multi = new MultiPartOutputStream(out);
             response.setStatus(.);
 
             // 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(.)!=null)
                 ctp = "multipart/x-byteranges; boundary=";
             else
                 ctp = "multipart/byteranges; boundary=";
             response.setContentType(ctp+multi.getBoundary());
 
             InputStream in=resource.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 = (InclusiveByteRangeranges.get(i);
                 header[i]=ibr.toHeaderRangeString(content_length);
                 length+=
                     ((i>0)?2:0)+
                     2+multi.getBoundary().length()+2+
                     (mimetype==null?0:..length()+2+mimetype.length())+2+
                     ..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 = (InclusiveByteRangeranges.get(i);
                multi.startPart(mimetype,new String[]{.+": "+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=resource.getInputStream();
                        pos=0;
                    }
                    if (pos<start)
                    {
                        in.skip(start-pos);
                        pos=start;
                    }
                    IO.copy(in,multi,size);
                    pos+=size;
                }
                else
                    // Handle cached resource
                    (resource).writeTo(multi,start,size);
            }
            if (in!=null)
                in.close();
            multi.close();
        }
        return;
    }
    /* ------------------------------------------------------------ */
    protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
    throws IOException
    {        
        if (content.getContentType()!=null && response.getContentType()==null)
            response.setContentType(content.getContentType().toString());
        if (response instanceof Response)
        {
            Response r=(Response)response;
            HttpFields fields = r.getHttpFields();
            if (content.getLastModified()!=null)
                fields.put(.,content.getLastModified());
            else if (content.getResource()!=null)
            {
                long lml=content.getResource().lastModified();
                if (lml!=-1)
                    fields.putDateField(.,lml);
            }
            if (count != -1)
                r.setLongContentLength(count);
            writeOptionHeaders(fields);
            
            if ()
                fields.put(.,content.getETag());
        }
        else
        {
            long lml=content.getResource().lastModified();
            if (lml>=0)
                response.setDateHeader(.,lml);
            if (count != -1)
            {
                if (count<.)
                    response.setContentLength((int)count);
                else
                    response.setHeader(.,Long.toString(count));
            }
            writeOptionHeaders(response);
            if ()
                response.setHeader(.,content.getETag().toString());
        }
    }
    /* ------------------------------------------------------------ */
    protected void writeOptionHeaders(HttpFields fieldsthrows IOException
    {
        if ()
        if (!=null)
            fields.put(.,);
    }
    /* ------------------------------------------------------------ */