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