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.server.handler;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.List;
25  
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  
30  import org.eclipse.jetty.http.CompressedContentFormat;
31  import org.eclipse.jetty.http.HttpHeader;
32  import org.eclipse.jetty.http.HttpMethod;
33  import org.eclipse.jetty.http.MimeTypes;
34  import org.eclipse.jetty.http.PreEncodedHttpField;
35  import org.eclipse.jetty.server.Request;
36  import org.eclipse.jetty.server.ResourceContentFactory;
37  import org.eclipse.jetty.server.ResourceService;
38  import org.eclipse.jetty.server.ResourceService.WelcomeFactory;
39  import org.eclipse.jetty.server.handler.ContextHandler.Context;
40  import org.eclipse.jetty.util.URIUtil;
41  import org.eclipse.jetty.util.log.Log;
42  import org.eclipse.jetty.util.log.Logger;
43  import org.eclipse.jetty.util.resource.Resource;
44  import org.eclipse.jetty.util.resource.ResourceFactory;
45  
46  /* ------------------------------------------------------------ */
47  /**
48   * Resource Handler.
49   *
50   * This handle will serve static content and handle If-Modified-Since headers. No caching is done. Requests for resources that do not exist are let pass (Eg no
51   * 404's).
52   *
53   *
54   */
55  public class ResourceHandler extends HandlerWrapper implements ResourceFactory,WelcomeFactory
56  {
57      private static final Logger LOG = Log.getLogger(ResourceHandler.class);
58  
59      Resource _baseResource;
60      ContextHandler _context;
61      Resource _defaultStylesheet;
62      MimeTypes _mimeTypes;
63      private final ResourceService _resourceService;
64      Resource _stylesheet;
65      String[] _welcomes = { "index.html" };
66  
67      /* ------------------------------------------------------------ */
68      public ResourceHandler(ResourceService resourceService)
69      {
70          _resourceService=resourceService;
71      }
72  
73      /* ------------------------------------------------------------ */
74      public ResourceHandler()
75      {
76          this(new ResourceService()
77          {
78              @Override
79              protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException
80              {
81              }
82          });
83          _resourceService.setGzipEquivalentFileExtensions(new ArrayList<>(Arrays.asList(new String[] { ".svgz" })));
84      }
85  
86  
87      /* ------------------------------------------------------------ */
88      @Override
89      public String getWelcomeFile(String pathInContext)
90      {
91          if (_welcomes == null)
92              return null;
93  
94          String welcome_servlet = null;
95          for (int i = 0; i < _welcomes.length; i++)
96          {
97              String welcome_in_context = URIUtil.addPaths(pathInContext,_welcomes[i]);
98              Resource welcome = getResource(welcome_in_context);
99              if (welcome != null && welcome.exists())
100                 return _welcomes[i];
101         }
102         return welcome_servlet;
103     }
104 
105     
106     /* ------------------------------------------------------------ */
107     @Override
108     public void doStart() throws Exception
109     {
110         Context scontext = ContextHandler.getCurrentContext();
111         _context = (scontext == null?null:scontext.getContextHandler());
112         _mimeTypes = _context == null?new MimeTypes():_context.getMimeTypes();
113 
114         _resourceService.setContentFactory(new ResourceContentFactory(this,_mimeTypes,_resourceService.getPrecompressedFormats()));
115         _resourceService.setWelcomeFactory(this);
116 
117         super.doStart();
118     }
119 
120     /* ------------------------------------------------------------ */
121     /**
122      * @return Returns the resourceBase.
123      */
124     public Resource getBaseResource()
125     {
126         if (_baseResource == null)
127             return null;
128         return _baseResource;
129     }
130 
131     /* ------------------------------------------------------------ */
132     /**
133      * @return the cacheControl header to set on all static content.
134      */
135     public String getCacheControl()
136     {
137         return _resourceService.getCacheControl().getValue();
138     }
139 
140     /* ------------------------------------------------------------ */
141     /**
142      * @return file extensions that signify that a file is gzip compressed. Eg ".svgz"
143      */
144     public List<String> getGzipEquivalentFileExtensions()
145     {
146         return _resourceService.getGzipEquivalentFileExtensions();
147     }
148 
149     /* ------------------------------------------------------------ */
150     public MimeTypes getMimeTypes()
151     {
152         return _mimeTypes;
153     }
154 
155     /* ------------------------------------------------------------ */
156     /**
157      * Get the minimum content length for async handling.
158      * 
159      * @return The minimum size in bytes of the content before asynchronous handling is used, or -1 for no async handling or 0 (default) for using
160      *         {@link HttpServletResponse#getBufferSize()} as the minimum length.
161      */
162     @Deprecated
163     public int getMinAsyncContentLength()
164     {
165         return -1;
166     }
167 
168     /* ------------------------------------------------------------ */
169     /**
170      * Get minimum memory mapped file content length.
171      * 
172      * @return the minimum size in bytes of a file resource that will be served using a memory mapped buffer, or -1 (default) for no memory mapped buffers.
173      */
174     @Deprecated
175     public int getMinMemoryMappedContentLength()
176     {
177         return -1;
178     }
179 
180     /* ------------------------------------------------------------ */
181     /*
182      */
183     @Override
184     public Resource getResource(String path)
185     {
186         if (LOG.isDebugEnabled())
187             LOG.debug("{} getResource({})",_context == null?_baseResource:_context,_baseResource,path);
188 
189         if (path == null || !path.startsWith("/"))
190             return null;
191 
192         try
193         {
194             Resource r = null;
195 
196             if (_baseResource != null)
197             {
198                 path = URIUtil.canonicalPath(path);
199                 r = _baseResource.addPath(path);
200 
201                 if (r != null && r.isAlias() && (_context == null || !_context.checkAlias(path,r)))
202                 {
203                     if (LOG.isDebugEnabled())
204                         LOG.debug("resource={} alias={}",r,r.getAlias());
205                     return null;
206                 }
207             }
208             else if (_context != null)
209                 r = _context.getResource(path);
210 
211             if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css"))
212                 r = getStylesheet();
213 
214             return r;
215         }
216         catch (Exception e)
217         {
218             LOG.debug(e);
219         }
220 
221         return null;
222     }
223 
224     /* ------------------------------------------------------------ */
225     /**
226      * @return Returns the base resource as a string.
227      */
228     public String getResourceBase()
229     {
230         if (_baseResource == null)
231             return null;
232         return _baseResource.toString();
233     }
234 
235     /* ------------------------------------------------------------ */
236     /**
237      * @return Returns the stylesheet as a Resource.
238      */
239     public Resource getStylesheet()
240     {
241         if (_stylesheet != null)
242         {
243             return _stylesheet;
244         }
245         else
246         {
247             if (_defaultStylesheet == null)
248             {
249                 _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
250             }
251             return _defaultStylesheet;
252         }
253     }
254 
255     /* ------------------------------------------------------------ */
256     public String[] getWelcomeFiles()
257     {
258         return _welcomes;
259     }
260 
261     /* ------------------------------------------------------------ */
262     /*
263      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
264      */
265     @Override
266     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
267     {
268         if (baseRequest.isHandled())
269             return;
270 
271         if (!HttpMethod.GET.is(request.getMethod()))
272         {
273             if (!HttpMethod.HEAD.is(request.getMethod()))
274             {
275                 // try another handler
276                 super.handle(target,baseRequest,request,response);
277                 return;
278             }
279         }
280 
281         _resourceService.doGet(request,response);
282 
283         if (response.isCommitted())
284             baseRequest.setHandled(true);
285         else
286             // no resource - try other handlers
287             super.handle(target,baseRequest,request,response);
288     }
289 
290     /* ------------------------------------------------------------ */
291     /**
292      * @return If true, range requests and responses are supported
293      */
294     public boolean isAcceptRanges()
295     {
296         return _resourceService.isAcceptRanges();
297     }
298 
299     /**
300      * @return If true, directory listings are returned if no welcome file is found. Else 403 Forbidden.
301      */
302     public boolean isDirAllowed()
303     {
304         return _resourceService.isDirAllowed();
305     }
306 
307     /* ------------------------------------------------------------ */
308     /**
309      * Get the directory option.
310      * 
311      * @return true if directories are listed.
312      */
313     public boolean isDirectoriesListed()
314     {
315         return _resourceService.isDirAllowed();
316     }
317 
318     /* ------------------------------------------------------------ */
319     /**
320      * @return True if ETag processing is done
321      */
322     public boolean isEtags()
323     {
324         return _resourceService.isEtags();
325     }
326     
327     /* ------------------------------------------------------------ */
328     /**
329      * @return If set to true, then static content will be served as gzip content encoded if a matching resource is found ending with ".gz"
330      */
331     @Deprecated
332     public boolean isGzip()
333     {
334         for (CompressedContentFormat formats : _resourceService.getPrecompressedFormats())
335         {
336             if (CompressedContentFormat.GZIP._encoding.equals(formats._encoding))
337                 return true;
338         }
339         return false;
340     }
341 
342     /**
343      * @return Precompressed resources formats that can be used to serve compressed variant of resources.
344      */
345     public CompressedContentFormat[] getPrecompressedFormats()
346     {
347         return _resourceService.getPrecompressedFormats();
348     }
349 
350     /* ------------------------------------------------------------ */
351     /**
352      * @return true, only the path info will be applied to the resourceBase
353      */
354     public boolean isPathInfoOnly()
355     {
356         return _resourceService.isPathInfoOnly();
357     }
358 
359     /* ------------------------------------------------------------ */
360     /**
361      * @return If true, welcome files are redirected rather than forwarded to.
362      */
363     public boolean isRedirectWelcome()
364     {
365         return _resourceService.isRedirectWelcome();
366     }
367 
368     /* ------------------------------------------------------------ */
369     /**
370      * @param acceptRanges If true, range requests and responses are supported
371      */
372     public void setAcceptRanges(boolean acceptRanges)
373     {
374         _resourceService.setAcceptRanges(acceptRanges);
375     }
376 
377     /* ------------------------------------------------------------ */
378     /**
379      * @param base The resourceBase to server content from. If null the
380      * context resource base is used.
381      */
382     public void setBaseResource(Resource base)
383     {
384         _baseResource = base;
385     }
386 
387     /* ------------------------------------------------------------ */
388     /**
389      * @param cacheControl
390      *            the cacheControl header to set on all static content.
391      */
392     public void setCacheControl(String cacheControl)
393     {
394         _resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cacheControl));
395     }
396 
397     /**
398      * @param dirAllowed
399      *            If true, directory listings are returned if no welcome file is found. Else 403 Forbidden.
400      */
401     public void setDirAllowed(boolean dirAllowed)
402     {
403         _resourceService.setDirAllowed(dirAllowed);
404     }
405 
406     /* ------------------------------------------------------------ */
407     /**
408      * Set the directory.
409      * 
410      * @param directory
411      *            true if directories are listed.
412      */
413     public void setDirectoriesListed(boolean directory)
414     {
415         _resourceService.setDirAllowed(directory);
416     }
417 
418     /* ------------------------------------------------------------ */
419     /**
420      * @param etags
421      *            True if ETag processing is done
422      */
423     public void setEtags(boolean etags)
424     {
425         _resourceService.setEtags(etags);
426     }
427 
428     /* ------------------------------------------------------------ */
429     /**
430      * @param gzip
431      *            If set to true, then static content will be served as gzip content encoded if a matching resource is found ending with ".gz"
432      */
433     @Deprecated
434     public void setGzip(boolean gzip)
435     {
436         setPrecompressedFormats(gzip?new CompressedContentFormat[]{CompressedContentFormat.GZIP}:new CompressedContentFormat[0]);
437     }
438 
439     /* ------------------------------------------------------------ */
440     /**
441      * @param gzipEquivalentFileExtensions file extensions that signify that a file is gzip compressed. Eg ".svgz"
442      */
443     public void setGzipEquivalentFileExtensions(List<String> gzipEquivalentFileExtensions)
444     {
445         _resourceService.setGzipEquivalentFileExtensions(gzipEquivalentFileExtensions);
446     }
447 
448     /**
449      * @param precompressedFormats
450      *            The list of precompresed formats to serve in encoded format if matching resource found.
451      *            For example serve gzip encoded file if ".gz" suffixed resource is found.
452      */
453     public void setPrecompressedFormats(CompressedContentFormat[] precompressedFormats)
454     {
455         _resourceService.setPrecompressedFormats(precompressedFormats);
456     }
457 
458     /* ------------------------------------------------------------ */
459     public void setMimeTypes(MimeTypes mimeTypes)
460     {
461         _mimeTypes = mimeTypes;
462     }
463 
464     /* ------------------------------------------------------------ */
465     /**
466      * Set the minimum content length for async handling.
467      * 
468      * @param minAsyncContentLength
469      *            The minimum size in bytes of the content before asynchronous handling is used, or -1 for no async handling or 0 for using
470      *            {@link HttpServletResponse#getBufferSize()} as the minimum length.
471      */
472     @Deprecated
473     public void setMinAsyncContentLength(int minAsyncContentLength)
474     {
475     }
476 
477     /* ------------------------------------------------------------ */
478     /**
479      * Set minimum memory mapped file content length.
480      * 
481      * @param minMemoryMappedFileSize
482      *            the minimum size in bytes of a file resource that will be served using a memory mapped buffer, or -1 for no memory mapped buffers.
483      */
484     @Deprecated
485     public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
486     {
487     }
488 
489     /**
490      * @param pathInfoOnly
491      *            true, only the path info will be applied to the resourceBase
492      */
493     public void setPathInfoOnly(boolean pathInfoOnly)
494     {
495         _resourceService.setPathInfoOnly(pathInfoOnly);
496     }
497 
498     /**
499      * @param redirectWelcome
500      *            If true, welcome files are redirected rather than forwarded to.
501      */
502     public void setRedirectWelcome(boolean redirectWelcome)
503     {
504         _resourceService.setRedirectWelcome(redirectWelcome);
505     }
506 
507     /* ------------------------------------------------------------ */
508     /**
509      * @param resourceBase
510      *            The base resource as a string.
511      */
512     public void setResourceBase(String resourceBase)
513     {
514         try
515         {
516             setBaseResource(Resource.newResource(resourceBase));
517         }
518         catch (Exception e)
519         {
520             LOG.warn(e.toString());
521             LOG.debug(e);
522             throw new IllegalArgumentException(resourceBase);
523         }
524     }
525 
526     /* ------------------------------------------------------------ */
527     /**
528      * @param stylesheet
529      *            The location of the stylesheet to be used as a String.
530      */
531     public void setStylesheet(String stylesheet)
532     {
533         try
534         {
535             _stylesheet = Resource.newResource(stylesheet);
536             if (!_stylesheet.exists())
537             {
538                 LOG.warn("unable to find custom stylesheet: " + stylesheet);
539                 _stylesheet = null;
540             }
541         }
542         catch (Exception e)
543         {
544             LOG.warn(e.toString());
545             LOG.debug(e);
546             throw new IllegalArgumentException(stylesheet);
547         }
548     }
549 
550     /* ------------------------------------------------------------ */
551     public void setWelcomeFiles(String[] welcomeFiles)
552     {
553         _welcomes = welcomeFiles;
554     }
555 
556 }