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