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