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