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