View Javadoc

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