View Javadoc
1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.servlet;
20  
21  import java.io.IOException;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.StringTokenizer;
26  
27  import javax.servlet.ServletContext;
28  import javax.servlet.ServletException;
29  import javax.servlet.UnavailableException;
30  import javax.servlet.http.HttpServlet;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.eclipse.jetty.http.CompressedContentFormat;
35  import org.eclipse.jetty.http.HttpContent;
36  import org.eclipse.jetty.http.HttpHeader;
37  import org.eclipse.jetty.http.MimeTypes;
38  import org.eclipse.jetty.http.PreEncodedHttpField;
39  import org.eclipse.jetty.http.pathmap.MappedResource;
40  import org.eclipse.jetty.server.CachedContentFactory;
41  import org.eclipse.jetty.server.ResourceContentFactory;
42  import org.eclipse.jetty.server.ResourceService;
43  import org.eclipse.jetty.server.ResourceService.WelcomeFactory;
44  import org.eclipse.jetty.server.handler.ContextHandler;
45  import org.eclipse.jetty.util.URIUtil;
46  import org.eclipse.jetty.util.log.Log;
47  import org.eclipse.jetty.util.log.Logger;
48  import org.eclipse.jetty.util.resource.Resource;
49  import org.eclipse.jetty.util.resource.ResourceFactory;
50  
51  
52  /** 
53   * The default servlet.
54   * <p>
55   * This servlet, normally mapped to /, provides the handling for static
56   * content, OPTION and TRACE methods for the context.
57   * The following initParameters are supported, these can be set either
58   * on the servlet itself or as ServletContext initParameters with a prefix
59   * of org.eclipse.jetty.servlet.Default. :
60   * <pre>
61   *  acceptRanges      If true, range requests and responses are
62   *                    supported
63   *
64   *  dirAllowed        If true, directory listings are returned if no
65   *                    welcome file is found. Else 403 Forbidden.
66   *
67   *  welcomeServlets   If true, attempt to dispatch to welcome files
68   *                    that are servlets, but only after no matching static
69   *                    resources could be found. If false, then a welcome
70   *                    file must exist on disk. If "exact", then exact
71   *                    servlet matches are supported without an existing file.
72   *                    Default is true.
73   *
74   *                    This must be false if you want directory listings,
75   *                    but have index.jsp in your welcome file list.
76   *
77   *  redirectWelcome   If true, welcome files are redirected rather than
78   *                    forwarded to.
79   *
80   *  gzip              If set to true, then static content will be served as
81   *                    gzip content encoded if a matching resource is
82   *                    found ending with ".gz" (default false) 
83   *                    (deprecated: use precompressed)
84   *                    
85   *  precompressed     If set to a comma separated list of encoding types (that may be
86   *                    listed in a requests Accept-Encoding header) to file
87   *                    extension mappings to look for and serve. For example:
88   *                    "br=.br,gzip=.gz,bzip2=.bz".
89   *                    If set to a boolean True, then a default set of compressed formats
90   *                    will be used, otherwise no precompressed formats.
91   *
92   *  resourceBase      Set to replace the context resource base
93   *
94   *  resourceCache     If set, this is a context attribute name, which the servlet
95   *                    will use to look for a shared ResourceCache instance.
96   *
97   *  relativeResourceBase
98   *                    Set with a pathname relative to the base of the
99   *                    servlet context root. Useful for only serving static content out
100  *                    of only specific subdirectories.
101  *
102  *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
103  *
104  *  stylesheet	      Set with the location of an optional stylesheet that will be used
105  *                    to decorate the directory listing html.
106  *
107  *  etags             If True, weak etags will be generated and handled.
108  *
109  *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
110  *  maxCachedFileSize The maximum size of a file to cache
111  *  maxCachedFiles    The maximum number of files to cache
112  *
113  *  useFileMappedBuffer
114  *                    If set to true, it will use mapped file buffer to serve static content
115  *                    when using NIO connector. Setting this value to false means that
116  *                    a direct buffer will be used instead of a mapped file buffer.
117  *                    This is set to false by default by this class, but may be overridden
118  *                    by eg webdefault.xml 
119  *
120  *  cacheControl      If set, all static content will have this value set as the cache-control
121  *                    header.
122  *                    
123  *  otherGzipFileExtensions
124  *                    Other file extensions that signify that a file is already compressed. Eg ".svgz"
125  *
126  *
127  * </pre>
128  *
129  */
130 public class DefaultServlet extends HttpServlet implements ResourceFactory, WelcomeFactory
131 {
132     private static final Logger LOG = Log.getLogger(DefaultServlet.class);
133 
134     private static final long serialVersionUID = 4930458713846881193L;    
135 
136     private final ResourceService _resourceService;
137     private ServletContext _servletContext;
138     private ContextHandler _contextHandler;
139 
140     private boolean _welcomeServlets=false;
141     private boolean _welcomeExactServlets=false;
142 
143     private Resource _resourceBase;
144     private CachedContentFactory _cache;
145 
146     private MimeTypes _mimeTypes;
147     private String[] _welcomes;
148     private Resource _stylesheet;
149     private boolean _useFileMappedBuffer=false;
150     private String _relativeResourceBase;
151     private ServletHandler _servletHandler;
152     private ServletHolder _defaultHolder;
153 
154     /* ------------------------------------------------------------ */
155     public DefaultServlet(ResourceService resourceService)
156     {
157         _resourceService=resourceService;
158     }
159 
160     /* ------------------------------------------------------------ */
161     public DefaultServlet()
162     {
163         this(new ResourceService());
164     }
165     
166     /* ------------------------------------------------------------ */
167     @Override
168     public void init()
169     throws UnavailableException
170     {
171         _servletContext=getServletContext();
172         _contextHandler = initContextHandler(_servletContext);
173 
174         _mimeTypes = _contextHandler.getMimeTypes();
175 
176         _welcomes = _contextHandler.getWelcomeFiles();
177         if (_welcomes==null)
178             _welcomes=new String[] {"index.html","index.jsp"};
179 
180         _resourceService.setAcceptRanges(getInitBoolean("acceptRanges",_resourceService.isAcceptRanges()));
181         _resourceService.setDirAllowed(getInitBoolean("dirAllowed",_resourceService.isDirAllowed()));
182         _resourceService.setRedirectWelcome(getInitBoolean("redirectWelcome",_resourceService.isRedirectWelcome()));
183         _resourceService.setPrecompressedFormats(parsePrecompressedFormats(getInitParameter("precompressed"), getInitBoolean("gzip", false)));
184         _resourceService.setPathInfoOnly(getInitBoolean("pathInfoOnly",_resourceService.isPathInfoOnly()));
185         _resourceService.setEtags(getInitBoolean("etags",_resourceService.isEtags()));
186         
187         if ("exact".equals(getInitParameter("welcomeServlets")))
188         {
189             _welcomeExactServlets=true;
190             _welcomeServlets=false;
191         }
192         else
193             _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
194 
195         _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
196 
197         _relativeResourceBase = getInitParameter("relativeResourceBase");
198 
199         String rb=getInitParameter("resourceBase");
200         if (rb!=null)
201         {
202             if (_relativeResourceBase!=null)
203                 throw new  UnavailableException("resourceBase & relativeResourceBase");
204             try{_resourceBase=_contextHandler.newResource(rb);}
205             catch (Exception e)
206             {
207                 LOG.warn(Log.EXCEPTION,e);
208                 throw new UnavailableException(e.toString());
209             }
210         }
211 
212         String css=getInitParameter("stylesheet");
213         try
214         {
215             if(css!=null)
216             {
217                 _stylesheet = Resource.newResource(css);
218                 if(!_stylesheet.exists())
219                 {
220                     LOG.warn("!" + css);
221                     _stylesheet = null;
222                 }
223             }
224             if(_stylesheet == null)
225             {
226                 _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
227             }
228         }
229         catch(Exception e)
230         {
231             LOG.warn(e.toString());
232             LOG.debug(e);
233         }
234 
235         int encodingHeaderCacheSize = getInitInt("encodingHeaderCacheSize", -1);
236         if (encodingHeaderCacheSize >= 0)
237             _resourceService.setEncodingCacheSize(encodingHeaderCacheSize);
238 
239         String cc=getInitParameter("cacheControl");
240         if (cc!=null)
241             _resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cc));
242         
243         
244         String resourceCache = getInitParameter("resourceCache");
245         int max_cache_size=getInitInt("maxCacheSize", -2);
246         int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
247         int max_cached_files=getInitInt("maxCachedFiles", -2);
248         if (resourceCache!=null)
249         {
250             if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
251                 LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
252             if (_relativeResourceBase!=null || _resourceBase!=null)
253                 throw new UnavailableException("resourceCache specified with resource bases");
254             _cache=(CachedContentFactory)_servletContext.getAttribute(resourceCache);
255         }
256 
257         try
258         {
259             if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
260             {
261                 _cache = new CachedContentFactory(null,this,_mimeTypes,_useFileMappedBuffer,_resourceService.isEtags(),_resourceService.getPrecompressedFormats());
262                 if (max_cache_size>=0)
263                     _cache.setMaxCacheSize(max_cache_size);
264                 if (max_cached_file_size>=-1)
265                     _cache.setMaxCachedFileSize(max_cached_file_size);
266                 if (max_cached_files>=-1)
267                     _cache.setMaxCachedFiles(max_cached_files);
268                 _servletContext.setAttribute(resourceCache==null?"resourceCache":resourceCache,_cache);
269             }
270         }
271         catch (Exception e)
272         {
273             LOG.warn(Log.EXCEPTION,e);
274             throw new UnavailableException(e.toString());
275         }
276 
277         HttpContent.ContentFactory contentFactory=_cache;
278         if (contentFactory==null)
279         {
280             contentFactory=new ResourceContentFactory(this,_mimeTypes,_resourceService.getPrecompressedFormats());
281             if (resourceCache!=null)
282                 _servletContext.setAttribute(resourceCache,contentFactory);
283         }
284         _resourceService.setContentFactory(contentFactory);
285         _resourceService.setWelcomeFactory(this);
286         
287         List<String> gzip_equivalent_file_extensions = new ArrayList<String>();
288         String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
289         if (otherGzipExtensions != null)
290         {
291             //comma separated list
292             StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
293             while (tok.hasMoreTokens())
294             {
295                 String s = tok.nextToken().trim();
296                 gzip_equivalent_file_extensions.add((s.charAt(0)=='.'?s:"."+s));
297             }
298         }
299         else
300         {
301             //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
302             gzip_equivalent_file_extensions.add(".svgz");
303         }
304         _resourceService.setGzipEquivalentFileExtensions(gzip_equivalent_file_extensions);
305 
306 
307         _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
308         for (ServletHolder h :_servletHandler.getServlets())
309             if (h.getServletInstance()==this)
310                 _defaultHolder=h;
311 
312         if (LOG.isDebugEnabled())
313             LOG.debug("resource base = "+_resourceBase);
314     }
315 
316     private CompressedContentFormat[] parsePrecompressedFormats(String precompressed, boolean gzip)
317     {
318         List<CompressedContentFormat> ret = new ArrayList<>();
319         if (precompressed != null && precompressed.indexOf('=') > 0)
320         {
321             for (String pair : precompressed.split(","))
322             {
323                 String[] setting = pair.split("=");
324                 String encoding = setting[0].trim();
325                 String extension = setting[1].trim();
326                 ret.add(new CompressedContentFormat(encoding,extension));
327                 if (gzip && !ret.contains(CompressedContentFormat.GZIP))
328                     ret.add(CompressedContentFormat.GZIP);
329             }
330         }
331         else if (precompressed != null)
332         {
333             if (Boolean.parseBoolean(precompressed))
334             {
335                 ret.add(CompressedContentFormat.BR);
336                 ret.add(CompressedContentFormat.GZIP);
337             }
338         }
339         else if (gzip)
340         {
341             // gzip handling is for backwards compatibility with older Jetty
342             ret.add(CompressedContentFormat.GZIP);
343         }
344         return ret.toArray(new CompressedContentFormat[ret.size()]);
345     }
346 
347     /**
348      * Compute the field _contextHandler.<br>
349      * In the case where the DefaultServlet is deployed on the HttpService it is likely that
350      * this method needs to be overwritten to unwrap the ServletContext facade until we reach
351      * the original jetty's ContextHandler.
352      * @param servletContext The servletContext of this servlet.
353      * @return the jetty's ContextHandler for this servletContext.
354      */
355     protected ContextHandler initContextHandler(ServletContext servletContext)
356     {
357         ContextHandler.Context scontext=ContextHandler.getCurrentContext();
358         if (scontext==null)
359         {
360             if (servletContext instanceof ContextHandler.Context)
361                 return ((ContextHandler.Context)servletContext).getContextHandler();
362             else
363                 throw new IllegalArgumentException("The servletContext " + servletContext + " " +
364                     servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
365         }
366         else
367             return ContextHandler.getCurrentContext().getContextHandler();
368     }
369 
370     /* ------------------------------------------------------------ */
371     @Override
372     public String getInitParameter(String name)
373     {
374         String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
375         if (value==null)
376             value=super.getInitParameter(name);
377         return value;
378     }
379 
380     /* ------------------------------------------------------------ */
381     private boolean getInitBoolean(String name, boolean dft)
382     {
383         String value=getInitParameter(name);
384         if (value==null || value.length()==0)
385             return dft;
386         return (value.startsWith("t")||
387                 value.startsWith("T")||
388                 value.startsWith("y")||
389                 value.startsWith("Y")||
390                 value.startsWith("1"));
391     }
392 
393     /* ------------------------------------------------------------ */
394     private int getInitInt(String name, int dft)
395     {
396         String value=getInitParameter(name);
397         if (value==null)
398             value=getInitParameter(name);
399         if (value!=null && value.length()>0)
400             return Integer.parseInt(value);
401         return dft;
402     }
403 
404     /* ------------------------------------------------------------ */
405     /** get Resource to serve.
406      * Map a path to a resource. The default implementation calls
407      * HttpContext.getResource but derived servlets may provide
408      * their own mapping.
409      * @param pathInContext The path to find a resource for.
410      * @return The resource to serve.
411      */
412     @Override
413     public Resource getResource(String pathInContext)
414     {
415         Resource r=null;
416         if (_relativeResourceBase!=null)
417             pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
418 
419         try
420         {
421             if (_resourceBase!=null)
422             {
423                 r = _resourceBase.addPath(pathInContext);
424                 if (!_contextHandler.checkAlias(pathInContext,r))
425                     r=null;
426             }
427             else if (_servletContext instanceof ContextHandler.Context)
428             {
429                 r = _contextHandler.getResource(pathInContext);
430             }
431             else
432             {
433                 URL u = _servletContext.getResource(pathInContext);
434                 r = _contextHandler.newResource(u);
435             }
436 
437             if (LOG.isDebugEnabled())
438                 LOG.debug("Resource "+pathInContext+"="+r);
439         }
440         catch (IOException e)
441         {
442             LOG.ignore(e);
443         }
444 
445         if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
446             r=_stylesheet;
447 
448         return r;
449     }
450 
451     /* ------------------------------------------------------------ */
452     @Override
453     protected void doGet(HttpServletRequest request, HttpServletResponse response)
454     throws ServletException, IOException
455     {
456         _resourceService.doGet(request,response);
457     }
458 
459     /* ------------------------------------------------------------ */
460     @Override
461     protected void doPost(HttpServletRequest request, HttpServletResponse response)
462     throws ServletException, IOException
463     {
464         doGet(request,response);
465     }
466 
467     /* ------------------------------------------------------------ */
468     /* (non-Javadoc)
469      * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
470      */
471     @Override
472     protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
473     {
474         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
475     }
476 
477     /* ------------------------------------------------------------ */
478     @Override
479     protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
480     throws ServletException, IOException
481     {
482         resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
483     }
484 
485     /* ------------------------------------------------------------ */
486     /*
487      * @see javax.servlet.Servlet#destroy()
488      */
489     @Override
490     public void destroy()
491     {
492         if (_cache!=null)
493             _cache.flushCache();
494         super.destroy();
495     }
496 
497     /* ------------------------------------------------------------ */
498     @Override
499     public String getWelcomeFile(String pathInContext)
500     {
501         if (_welcomes==null)
502             return null;
503 
504         String welcome_servlet=null;
505         for (int i=0;i<_welcomes.length;i++)
506         {
507             String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
508             Resource welcome=getResource(welcome_in_context);
509             if (welcome!=null && welcome.exists())
510                 return _welcomes[i];
511 
512             if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
513             {
514                 MappedResource<ServletHolder> entry=_servletHandler.getHolderEntry(welcome_in_context);
515                 if (entry!=null && entry.getResource()!=_defaultHolder &&
516                         (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context))))
517                     welcome_servlet=welcome_in_context;
518 
519             }
520         }
521         return welcome_servlet;
522     }
523 }