View Javadoc

    1   // ========================================================================
    2   // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.servlet;
    16
    17  import java.io.File;
    18  import java.io.IOException;
    19  import java.io.InputStream;
    20  import java.io.OutputStream;
    21  import java.net.MalformedURLException;
    22  import java.util.Enumeration;
    23  import java.util.List;
    24
    25  import javax.servlet.RequestDispatcher;
    26  import javax.servlet.ServletContext;
    27  import javax.servlet.ServletException;
    28  import javax.servlet.UnavailableException;
    29  import javax.servlet.http.HttpServlet;
    30  import javax.servlet.http.HttpServletRequest;
    31  import javax.servlet.http.HttpServletResponse;
    32
    33  import org.mortbay.io.Buffer;
    34  import org.mortbay.io.ByteArrayBuffer;
    35  import org.mortbay.io.WriterOutputStream;
    36  import org.mortbay.io.nio.DirectNIOBuffer;
    37  import org.mortbay.io.nio.IndirectNIOBuffer;
    38  import org.mortbay.io.nio.NIOBuffer;
    39  import org.mortbay.jetty.Connector;
    40  import org.mortbay.jetty.HttpConnection;
    41  import org.mortbay.jetty.HttpContent;
    42  import org.mortbay.jetty.HttpFields;
    43  import org.mortbay.jetty.HttpHeaderValues;
    44  import org.mortbay.jetty.HttpHeaders;
    45  import org.mortbay.jetty.HttpMethods;
    46  import org.mortbay.jetty.InclusiveByteRange;
    47  import org.mortbay.jetty.MimeTypes;
    48  import org.mortbay.jetty.ResourceCache;
    49  import org.mortbay.jetty.Response;
    50  import org.mortbay.jetty.handler.ContextHandler;
    51  import org.mortbay.jetty.nio.NIOConnector;
    52  import org.mortbay.log.Log;
    53  import org.mortbay.resource.Resource;
    54  import org.mortbay.resource.ResourceFactory;
    55  import org.mortbay.util.IO;
    56  import org.mortbay.util.MultiPartOutputStream;
    57  import org.mortbay.util.TypeUtil;
    58  import org.mortbay.util.URIUtil;
    59
    60
    61
    62  /* ------------------------------------------------------------ */
    63  /** The default servlet.
    64   * This servlet, normally mapped to /, provides the handling for static
    65   * content, OPTION and TRACE methods for the context.
    66   * The following initParameters are supported, these can be set either
    67   * on the servlet itself or as ServletContext initParameters with a prefix
    68   * of org.mortbay.jetty.servlet.Default. :
    69   * <PRE>
    70   *   acceptRanges     If true, range requests and responses are
    71   *                    supported
    72   *
    73   *   dirAllowed       If true, directory listings are returned if no
    74   *                    welcome file is found. Else 403 Forbidden.
    75   *
    76   *   redirectWelcome  If true, welcome files are redirected rather than
    77   *                    forwarded to.
    78   *
    79   *   gzip             If set to true, then static content will be served as
    80   *                    gzip content encoded if a matching resource is
    81   *                    found ending with ".gz"
    82   *
    83   *  resourceBase      Set to replace the context resource base
    84   *
    85   *  relativeResourceBase
    86   *                    Set with a pathname relative to the base of the
    87   *                    servlet context root. Useful for only serving static content out
    88   *                    of only specific subdirectories.
    89   *
    90   *  aliases           If True, aliases of resources are allowed (eg. symbolic
    91   *                    links and caps variations). May bypass security constraints.
    92   *
    93   *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
    94   *  maxCachedFileSize The maximum size of a file to cache
    95   *  maxCachedFiles    The maximum number of files to cache
    96   *  cacheType         Set to "bio", "nio" or "both" to determine the type resource cache.
    97   *                    A bio cached buffer may be used by nio but is not as efficient as an
    98   *                    nio buffer.  An nio cached buffer may not be used by bio.
    99   *
    100  *  useFileMappedBuffer
    101  *                    If set to true, it will use mapped file buffer to serve static content
    102  *                    when using NIO connector. Setting this value to false means that
    103  *                    a direct buffer will be used instead of a mapped file buffer.
    104  *                    By default, this is set to true.
    105  *
    106  *  cacheControl      If set, all static content will have this value set as the cache-control
    107  *                    header.
    108  *
    109  *
    110  * </PRE>
    111  *
    112  *
    113  * @author Greg Wilkins (gregw)
    114  * @author Nigel Canonizado
    115  */
    116 public class DefaultServlet extends HttpServlet implements ResourceFactory
    117 {
    118     private ContextHandler.SContext _context;
    119
    120     private boolean _acceptRanges=true;
    121     private boolean _dirAllowed=true;
    122     private boolean _redirectWelcome=false;
    123     private boolean _gzip=true;
    124
    125     private Resource _resourceBase;
    126     private NIOResourceCache _nioCache;
    127     private ResourceCache _bioCache;
    128
    129     private MimeTypes _mimeTypes;
    130     private String[] _welcomes;
    131     private boolean _aliases=false;
    132     private boolean _useFileMappedBuffer=false;
    133     ByteArrayBuffer _cacheControl;
    134
    135
    136     /* ------------------------------------------------------------ */
    137     public void init()
    138         throws UnavailableException
    139     {
    140         ServletContext config=getServletContext();
    141         _context = (ContextHandler.SContext)config;
    142         _mimeTypes = _context.getContextHandler().getMimeTypes();
    143
    144         _welcomes = _context.getContextHandler().getWelcomeFiles();
    145         if (_welcomes==null)
    146             _welcomes=new String[] {"index.jsp","index.html"};
    147
    148         _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
    149         _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
    150         _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
    151         _gzip=getInitBoolean("gzip",_gzip);
    152
    153         _aliases=getInitBoolean("aliases",_aliases);
    154         _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
    155
    156         String rrb = getInitParameter("relativeResourceBase");
    157         if (rrb!=null)
    158         {
    159             try
    160             {
    161                 _resourceBase = _context.getContextHandler().getResource(URIUtil.SLASH).addPath(rrb);
    162             }
    163             catch (Exception e)
    164             {
    165                 Log.warn(Log.EXCEPTION,e);
    166                 throw new UnavailableException(e.toString());
    167             }
    168         }
    169
    170         String rb=getInitParameter("resourceBase");
    171         if (rrb != null && rb != null)
    172             throw new  UnavailableException("resourceBase & relativeResourceBase");
    173
    174         if (rb!=null)
    175         {
    176             try{_resourceBase=Resource.newResource(rb);}
    177             catch (Exception e)
    178             {
    179                 Log.warn(Log.EXCEPTION,e);
    180                 throw new UnavailableException(e.toString());
    181             }
    182         }
    183
    184         String t=getInitParameter("cacheControl");
    185         if (t!=null)
    186             _cacheControl=new ByteArrayBuffer(t);
    187
    188         try
    189         {
    190             if (_resourceBase==null)
    191                 _resourceBase = _context.getContextHandler().getResource(URIUtil.SLASH);
    192
    193             String cache_type =getInitParameter("cacheType");
    194             int max_cache_size=getInitInt("maxCacheSize", -2);
    195             int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
    196             int max_cached_files=getInitInt("maxCachedFiles", -2);
    197
    198             if (cache_type==null || "nio".equals(cache_type)|| "both".equals(cache_type))
    199             {
    200                 if (max_cache_size==-2 || max_cache_size>0)
    201                 {
    202                     _nioCache=new NIOResourceCache(_mimeTypes);
    203                     if (max_cache_size>0)
    204                         _nioCache.setMaxCacheSize(max_cache_size);
    205                     if (max_cached_file_size>=-1)
    206                         _nioCache.setMaxCachedFileSize(max_cached_file_size);
    207                     if (max_cached_files>=-1)
    208                         _nioCache.setMaxCachedFiles(max_cached_files);
    209                     _nioCache.start();
    210                 }
    211             }
    212             if ("bio".equals(cache_type)|| "both".equals(cache_type))
    213             {
    214                 if (max_cache_size==-2 || max_cache_size>0)
    215                 {
    216                     _bioCache=new ResourceCache(_mimeTypes);
    217                     if (max_cache_size>0)
    218                         _bioCache.setMaxCacheSize(max_cache_size);
    219                     if (max_cached_file_size>=-1)
    220                         _bioCache.setMaxCachedFileSize(max_cached_file_size);
    221                     if (max_cached_files>=-1)
    222                         _bioCache.setMaxCachedFiles(max_cached_files);
    223                     _bioCache.start();
    224                 }
    225             }
    226             if (_nioCache==null)
    227                 _bioCache=null;
    228
    229         }
    230         catch (Exception e)
    231         {
    232             Log.warn(Log.EXCEPTION,e);
    233             throw new UnavailableException(e.toString());
    234         }
    235
    236         if (Log.isDebugEnabled()) Log.debug("resource base = "+_resourceBase);
    237     }
    238
    239     /* ------------------------------------------------------------ */
    240     public String getInitParameter(String name)
    241     {
    242         String value=getServletContext().getInitParameter("org.mortbay.jetty.servlet.Default."+name);
    243 	if (value==null)
    244 	    value=super.getInitParameter(name);
    245 	return value;
    246     }
    247
    248     /* ------------------------------------------------------------ */
    249     private boolean getInitBoolean(String name, boolean dft)
    250     {
    251         String value=getInitParameter(name);
    252         if (value==null || value.length()==0)
    253             return dft;
    254         return (value.startsWith("t")||
    255                 value.startsWith("T")||
    256                 value.startsWith("y")||
    257                 value.startsWith("Y")||
    258                 value.startsWith("1"));
    259     }
    260
    261     /* ------------------------------------------------------------ */
    262     private int getInitInt(String name, int dft)
    263     {
    264         String value=getInitParameter(name);
    265 	if (value==null)
    266             value=getInitParameter(name);
    267         if (value!=null && value.length()>0)
    268             return Integer.parseInt(value);
    269         return dft;
    270     }
    271
    272     /* ------------------------------------------------------------ */
    273     /** get Resource to serve.
    274      * Map a path to a resource. The default implementation calls
    275      * HttpContext.getResource but derived servlets may provide
    276      * their own mapping.
    277      * @param pathInContext The path to find a resource for.
    278      * @return The resource to serve.
    279      */
    280     public Resource getResource(String pathInContext)
    281     {
    282         if (_resourceBase==null)
    283             return null;
    284         Resource r=null;
    285         try
    286         {
    287             r = _resourceBase.addPath(pathInContext);
    288             if (!_aliases && r.getAlias()!=null)
    289             {
    290                 if (r.exists())
    291                     Log.warn("Aliased resource: "+r+"=="+r.getAlias());
    292                 return null;
    293             }
    294             if (Log.isDebugEnabled()) Log.debug("RESOURCE="+r);
    295         }
    296         catch (IOException e)
    297         {
    298             Log.ignore(e);
    299         }
    300         return r;
    301     }
    302
    303     /* ------------------------------------------------------------ */
    304     protected void doGet(HttpServletRequest request, HttpServletResponse response)
    305     	throws ServletException, IOException
    306     {
    307         String servletPath=null;
    308         String pathInfo=null;
    309         Enumeration reqRanges = null;
    310         Boolean included =(Boolean)request.getAttribute(Dispatcher.__INCLUDE_JETTY);
    311         if (included!=null && included.booleanValue())
    312         {
    313             servletPath=(String)request.getAttribute(Dispatcher.__INCLUDE_SERVLET_PATH);
    314             pathInfo=(String)request.getAttribute(Dispatcher.__INCLUDE_PATH_INFO);
    315             if (servletPath==null)
    316             {
    317                 servletPath=request.getServletPath();
    318                 pathInfo=request.getPathInfo();
    319             }
    320         }
    321         else
    322         {
    323             included=Boolean.FALSE;
    324             servletPath=request.getServletPath();
    325             pathInfo=request.getPathInfo();
    326
    327             // Is this a range request?
    328             reqRanges = request.getHeaders(HttpHeaders.RANGE);
    329             if (reqRanges!=null && !reqRanges.hasMoreElements())
    330                 reqRanges=null;
    331         }
    332
    333         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
    334         boolean endsWithSlash=pathInContext.endsWith(URIUtil.SLASH);
    335
    336         // Can we gzip this request?
    337         String pathInContextGz=null;
    338         boolean gzip=false;
    339         if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
    340         {
    341             String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING);
    342             if (accept!=null && accept.indexOf("gzip")>=0)
    343                 gzip=true;
    344         }
    345
    346         // Find the resource and content
    347         Resource resource=null;
    348         HttpContent content=null;
    349
    350         Connector connector = HttpConnection.getCurrentConnection().getConnector();
    351         ResourceCache cache=(connector instanceof NIOConnector) ?_nioCache:_bioCache;
    352         try
    353         {
    354             // Try gzipped content first
    355             if (gzip)
    356             {
    357                 pathInContextGz=pathInContext+".gz";
    358                 resource=getResource(pathInContextGz);
    359
    360                 if (resource==null || !resource.exists()|| resource.isDirectory())
    361                 {
    362                     gzip=false;
    363                     pathInContextGz=null;
    364                 }
    365                 else if (cache!=null)
    366                 {
    367                     content=cache.lookup(pathInContextGz,resource);
    368                     if (content!=null)
    369                         resource=content.getResource();
    370                 }
    371
    372                 if (resource==null || !resource.exists()|| resource.isDirectory())
    373                 {
    374                     gzip=false;
    375                     pathInContextGz=null;
    376                 }
    377             }
    378
    379             // find resource
    380             if (!gzip)
    381             {
    382                 if (cache==null)
    383                     resource=getResource(pathInContext);
    384                 else
    385                 {
    386                     content=cache.lookup(pathInContext,this);
    387
    388                     if (content!=null)
    389                         resource=content.getResource();
    390                     else
    391                         resource=getResource(pathInContext);
    392                 }
    393             }
    394
    395             if (Log.isDebugEnabled())
    396                 Log.debug("resource="+resource+(content!=null?" content":""));
    397
    398             // Handle resource
    399             if (resource==null || !resource.exists())
    400                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
    401             else if (!resource.isDirectory())
    402             {
    403                 // ensure we have content
    404                 if (content==null)
    405                     content=new UnCachedContent(resource);
    406
    407                 if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
    408                 {
    409                     if (gzip)
    410                     {
    411                        response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip");
    412                        String mt=_context.getMimeType(pathInContext);
    413                        if (mt!=null)
    414                            response.setContentType(mt);
    415                     }
    416                     sendData(request,response,included.booleanValue(),resource,content,reqRanges);
    417                 }
    418             }
    419             else
    420             {
    421                 String welcome=null;
    422
    423                 if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.mortbay.jetty.nullPathInfo")!=null))
    424                 {
    425                     StringBuffer buf=request.getRequestURL();
    426                     int param=buf.lastIndexOf(";");
    427                     if (param<0)
    428                         buf.append('/');
    429                     else
    430                         buf.insert(param,'/');
    431                     String q=request.getQueryString();
    432                     if (q!=null&&q.length()!=0)
    433                     {
    434                         buf.append('?');
    435                         buf.append(q);
    436                     }
    437                     response.setContentLength(0);
    438                     response.sendRedirect(response.encodeRedirectURL(buf.toString()));
    439                 }
    440                 // else look for a welcome file
    441                 else if (null!=(welcome=getWelcomeFile(resource)))
    442                 {
    443                     String ipath=URIUtil.addPaths(pathInContext,welcome);
    444                     if (_redirectWelcome)
    445                     {
    446                         // Redirect to the index
    447                         response.setContentLength(0);
    448                         String q=request.getQueryString();
    449                         if (q!=null&&q.length()!=0)
    450                             response.sendRedirect(URIUtil.addPaths( _context.getContextPath(),ipath)+"?"+q);
    451                         else
    452                             response.sendRedirect(URIUtil.addPaths( _context.getContextPath(),ipath));
    453                     }
    454                     else
    455                     {
    456                         // Forward to the index
    457                         RequestDispatcher dispatcher=request.getRequestDispatcher(ipath);
    458                         if (dispatcher!=null)
    459                         {
    460                             if (included.booleanValue())
    461                                 dispatcher.include(request,response);
    462                             else
    463                             {
    464                                 request.setAttribute("org.mortbay.jetty.welcome",ipath);
    465                                 dispatcher.forward(request,response);
    466                             }
    467                         }
    468                     }
    469                 }
    470                 else
    471                 {
    472                     content=new UnCachedContent(resource);
    473                     if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
    474                         sendDirectory(request,response,resource,pathInContext.length()>1);
    475                 }
    476             }
    477         }
    478         catch(IllegalArgumentException e)
    479         {
    480             Log.warn(Log.EXCEPTION,e);
    481             if(!response.isCommitted())
    482                 response.sendError(500, e.getMessage());
    483         }
    484         finally
    485         {
    486             if (content!=null)
    487                 content.release();
    488             else if (resource!=null)
    489                 resource.release();
    490         }
    491
    492     }
    493
    494     /* ------------------------------------------------------------ */
    495     protected void doPost(HttpServletRequest request, HttpServletResponse response)
    496         throws ServletException, IOException
    497     {
    498         doGet(request,response);
    499     }
    500
    501     /* ------------------------------------------------------------ */
    502     /* (non-Javadoc)
    503      * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
    504      */
    505     protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    506     {
    507         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    508     }
    509
    510     /* ------------------------------------------------------------ */
    511     /**
    512      * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
    513      * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
    514      * If the resource is not a directory, or no matching file is found, then <code>null</code> is returned.
    515      * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
    516      * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
    517      * @param resource
    518      * @return The name of the matching welcome file.
    519      * @throws IOException
    520      * @throws MalformedURLException
    521      */
    522     private String getWelcomeFile(Resource resource) throws MalformedURLException, IOException
    523     {
    524         if (!resource.isDirectory() || _welcomes==null)
    525             return null;
    526
    527         for (int i=0;i<_welcomes.length;i++)
    528         {
    529             Resource welcome=resource.addPath(_welcomes[i]);
    530             if (welcome.exists())
    531                 return _welcomes[i];
    532         }
    533
    534         return null;
    535     }
    536
    537     /* ------------------------------------------------------------ */
    538     /* Check modification date headers.
    539      */
    540     protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
    541     throws IOException
    542     {
    543         try
    544         {
    545             if (!request.getMethod().equals(HttpMethods.HEAD) )
    546             {
    547                 String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
    548                 if (ifms!=null)
    549                 {
    550                     if (content!=null)
    551                     {
    552                         Buffer mdlm=content.getLastModified();
    553                         if (mdlm!=null)
    554                         {
    555                             if (ifms.equals(mdlm.toString()))
    556                             {
    557                                 response.reset();
    558                                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    559                                 response.flushBuffer();
    560                                 return false;
    561                             }
    562                         }
    563                     }
    564
    565                     long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
    566                     if (ifmsl!=-1)
    567                     {
    568                         if (resource.lastModified()/1000 <= ifmsl/1000)
    569                         {
    570                             response.reset();
    571                             response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
    572                             response.flushBuffer();
    573                             return false;
    574                         }
    575                     }
    576                 }
    577
    578                 // Parse the if[un]modified dates and compare to resource
    579                 long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
    580
    581                 if (date!=-1)
    582                 {
    583                     if (resource.lastModified()/1000 > date/1000)
    584                     {
    585                         response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
    586                         return false;
    587                     }
    588                 }
    589
    590             }
    591         }
    592         catch(IllegalArgumentException iae)
    593         {
    594             if(!response.isCommitted())
    595                 response.sendError(400, iae.getMessage());
    596             throw iae;
    597         }
    598         return true;
    599     }
    600
    601
    602     /* ------------------------------------------------------------------- */
    603     protected void sendDirectory(HttpServletRequest request,
    604                                  HttpServletResponse response,
    605                                  Resource resource,
    606                                  boolean parent)
    607     throws IOException
    608     {
    609         if (!_dirAllowed)
    610         {
    611             response.sendError(HttpServletResponse.SC_FORBIDDEN);
    612             return;
    613         }
    614
    615         byte[] data=null;
    616         String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
    617         String dir = resource.getListHTML(base,parent);
    618         if (dir==null)
    619         {
    620             response.sendError(HttpServletResponse.SC_FORBIDDEN,
    621             "No directory");
    622             return;
    623         }
    624
    625         data=dir.getBytes("UTF-8");
    626         response.setContentType("text/html; charset=UTF-8");
    627         response.setContentLength(data.length);
    628         response.getOutputStream().write(data);
    629     }
    630
    631     /* ------------------------------------------------------------ */
    632     protected void sendData(HttpServletRequest request,
    633                             HttpServletResponse response,
    634                             boolean include,
    635                             Resource resource,
    636                             HttpContent content,
    637                             Enumeration reqRanges)
    638     throws IOException
    639     {
    640         long content_length=resource.length();
    641
    642         // Get the output stream (or writer)
    643         OutputStream out =null;
    644         try{out = response.getOutputStream();}
    645         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
    646
    647         if ( reqRanges == null || !reqRanges.hasMoreElements())
    648         {
    649             //  if there were no ranges, send entire entity
    650             if (include)
    651             {
    652                 resource.writeTo(out,0,content_length);
    653             }
    654             else
    655             {
    656                 // See if a short direct method can be used?
    657                 if (out instanceof HttpConnection.Output)
    658                 {
    659                     if (_cacheControl!=null)
    660                     {
    661                         if (response instanceof Response)
    662                             ((Response)response).getHttpFields().put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
    663                         else
    664                             response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
    665                     }
    666                     ((HttpConnection.Output)out).sendContent(content);
    667                 }
    668                 else
    669                 {
    670
    671                     // Write content normally
    672                     writeHeaders(response,content,content_length);
    673                     resource.writeTo(out,0,content_length);
    674                 }
    675             }
    676         }
    677         else
    678         {
    679             // Parse the satisfiable ranges
    680             List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
    681
    682             //  if there are no satisfiable ranges, send 416 response
    683             if (ranges==null || ranges.size()==0)
    684             {
    685                 writeHeaders(response, content, content_length);
    686                 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
    687                 response.setHeader(HttpHeaders.CONTENT_RANGE,
    688                         InclusiveByteRange.to416HeaderRangeString(content_length));
    689                 resource.writeTo(out,0,content_length);
    690                 return;
    691             }
    692
    693
    694             //  if there is only a single valid range (must be satisfiable
    695             //  since were here now), send that range with a 216 response
    696             if ( ranges.size()== 1)
    697             {
    698                 InclusiveByteRange singleSatisfiableRange =
    699                     (InclusiveByteRange)ranges.get(0);
    700                 long singleLength = singleSatisfiableRange.getSize(content_length);
    701                 writeHeaders(response,content,singleLength                     );
    702                 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    703                 response.setHeader(HttpHeaders.CONTENT_RANGE,
    704                         singleSatisfiableRange.toHeaderRangeString(content_length));
    705                 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
    706                 return;
    707             }
    708
    709
    710             //  multiple non-overlapping valid ranges cause a multipart
    711             //  216 response which does not require an overall
    712             //  content-length header
    713             //
    714             writeHeaders(response,content,-1);
    715             String mimetype=content.getContentType().toString();
    716             MultiPartOutputStream multi = new MultiPartOutputStream(out);
    717             response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
    718
    719             // If the request has a "Request-Range" header then we need to
    720             // send an old style multipart/x-byteranges Content-Type. This
    721             // keeps Netscape and acrobat happy. This is what Apache does.
    722             String ctp;
    723             if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null)
    724                 ctp = "multipart/x-byteranges; boundary=";
    725             else
    726                 ctp = "multipart/byteranges; boundary=";
    727             response.setContentType(ctp+multi.getBoundary());
    728
    729             InputStream in=resource.getInputStream();
    730             long pos=0;
    731
    732             for (int i=0;i<ranges.size();i++)
    733             {
    734                 InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
    735                 String header=HttpHeaders.CONTENT_RANGE+": "+
    736                 ibr.toHeaderRangeString(content_length);
    737                 multi.startPart(mimetype,new String[]{header});
    738
    739                 long start=ibr.getFirst(content_length);
    740                 long size=ibr.getSize(content_length);
    741                 if (in!=null)
    742                 {
    743                     // Handle non cached resource
    744                     if (start<pos)
    745                     {
    746                         in.close();
    747                         in=resource.getInputStream();
    748                         pos=0;
    749                     }
    750                     if (pos<start)
    751                     {
    752                         in.skip(start-pos);
    753                         pos=start;
    754                     }
    755                     IO.copy(in,multi,size);
    756                     pos+=size;
    757                 }
    758                 else
    759                     // Handle cached resource
    760                     (resource).writeTo(multi,start,size);
    761
    762             }
    763             if (in!=null)
    764                 in.close();
    765             multi.close();
    766         }
    767         return;
    768     }
    769
    770     /* ------------------------------------------------------------ */
    771     protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
    772         throws IOException
    773     {
    774         if (content.getContentType()!=null)
    775             response.setContentType(content.getContentType().toString());
    776
    777         if (response instanceof Response)
    778         {
    779             Response r=(Response)response;
    780             HttpFields fields = r.getHttpFields();
    781
    782             if (content.getLastModified()!=null)
    783                 fields.put(HttpHeaders.LAST_MODIFIED_BUFFER,content.getLastModified(),content.getResource().lastModified());
    784             else if (content.getResource()!=null)
    785             {
    786                 long lml=content.getResource().lastModified();
    787                 if (lml!=-1)
    788                     fields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER,lml);
    789             }
    790
    791             if (count != -1)
    792                 r.setLongContentLength(count);
    793
    794             if (_acceptRanges)
    795                 fields.put(HttpHeaders.ACCEPT_RANGES_BUFFER,HttpHeaderValues.BYTES_BUFFER);
    796
    797             if (_cacheControl!=null)
    798                 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
    799
    800         }
    801         else
    802         {
    803             long lml=content.getResource().lastModified();
    804             if (lml>=0)
    805                 response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml);
    806
    807             if (count != -1)
    808             {
    809                 if (count<Integer.MAX_VALUE)
    810                     response.setContentLength((int)count);
    811                 else
    812                     response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(count));
    813             }
    814
    815             if (_acceptRanges)
    816                 response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes");
    817
    818             if (_cacheControl!=null)
    819                 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
    820         }
    821     }
    822
    823     /* ------------------------------------------------------------ */
    824     /*
    825      * @see javax.servlet.Servlet#destroy()
    826      */
    827     public void destroy()
    828     {
    829         try
    830         {
    831             if (_nioCache!=null)
    832                 _nioCache.stop();
    833             if (_bioCache!=null)
    834                 _bioCache.stop();
    835         }
    836         catch(Exception e)
    837         {
    838             Log.warn(Log.EXCEPTION,e);
    839         }
    840         finally
    841         {
    842             super.destroy();
    843         }
    844     }
    845
    846     /* ------------------------------------------------------------ */
    847     /* ------------------------------------------------------------ */
    848     /* ------------------------------------------------------------ */
    849     private class UnCachedContent implements HttpContent
    850     {
    851         Resource _resource;
    852
    853         UnCachedContent(Resource resource)
    854         {
    855             _resource=resource;
    856         }
    857
    858         /* ------------------------------------------------------------ */
    859         public Buffer getContentType()
    860         {
    861             return _mimeTypes.getMimeByExtension(_resource.toString());
    862         }
    863
    864         /* ------------------------------------------------------------ */
    865         public Buffer getLastModified()
    866         {
    867             return null;
    868         }
    869
    870         /* ------------------------------------------------------------ */
    871         public Buffer getBuffer()
    872         {
    873             return null;
    874         }
    875
    876         /* ------------------------------------------------------------ */
    877         public long getContentLength()
    878         {
    879             return _resource.length();
    880         }
    881
    882         /* ------------------------------------------------------------ */
    883         public InputStream getInputStream() throws IOException
    884         {
    885             return _resource.getInputStream();
    886         }
    887
    888         /* ------------------------------------------------------------ */
    889         public Resource getResource()
    890         {
    891             return _resource;
    892         }
    893
    894         /* ------------------------------------------------------------ */
    895         public void release()
    896         {
    897             _resource.release();
    898             _resource=null;
    899         }
    900
    901     }
    902
    903     /* ------------------------------------------------------------ */
    904     /* ------------------------------------------------------------ */
    905     class NIOResourceCache extends ResourceCache
    906     {
    907         /* ------------------------------------------------------------ */
    908         public NIOResourceCache(MimeTypes mimeTypes)
    909         {
    910             super(mimeTypes);
    911         }
    912
    913         /* ------------------------------------------------------------ */
    914         protected void fill(Content content) throws IOException
    915         {
    916             Buffer buffer=null;
    917             Resource resource=content.getResource();
    918             long length=resource.length();
    919
    920             if (_useFileMappedBuffer && resource.getFile()!=null)
    921             {
    922                 File file = resource.getFile();
    923                 if (file != null)
    924                     buffer = new DirectNIOBuffer(file);
    925             }
    926             else
    927             {
    928                 InputStream is = resource.getInputStream();
    929                 try
    930                 {
    931                     Connector connector = HttpConnection.getCurrentConnection().getConnector();
    932                     buffer = ((NIOConnector)connector).getUseDirectBuffers()?
    933                             (NIOBuffer)new DirectNIOBuffer((int)length):
    934                             (NIOBuffer)new IndirectNIOBuffer((int)length);
    935
    936                 }
    937                 catch(OutOfMemoryError e)
    938                 {
    939                     Log.warn(e.toString());
    940                     Log.debug(e);
    941                     buffer = new IndirectNIOBuffer((int) length);
    942                 }
    943                 buffer.readFrom(is,(int)length);
    944                 is.close();
    945             }
    946             content.setBuffer(buffer);
    947         }
    948     }
    949 }