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