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 }