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