View Javadoc

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