View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.io.OutputStream;
23  import java.net.MalformedURLException;
24  
25  import javax.servlet.RequestDispatcher;
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  
30  import org.eclipse.jetty.http.HttpFields;
31  import org.eclipse.jetty.http.HttpHeader;
32  import org.eclipse.jetty.http.HttpMethod;
33  import org.eclipse.jetty.http.HttpStatus;
34  import org.eclipse.jetty.http.MimeTypes;
35  import org.eclipse.jetty.io.WriterOutputStream;
36  import org.eclipse.jetty.server.Request;
37  import org.eclipse.jetty.server.Response;
38  import org.eclipse.jetty.server.handler.ContextHandler.Context;
39  import org.eclipse.jetty.util.URIUtil;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  import org.eclipse.jetty.util.resource.Resource;
43  
44  
45  /* ------------------------------------------------------------ */
46  /** Resource Handler.
47   *
48   * This handle will serve static content and handle If-Modified-Since headers.
49   * No caching is done.
50   * Requests for resources that do not exist are let pass (Eg no 404's).
51   *
52   *
53   * @org.apache.xbean.XBean
54   */
55  public class ResourceHandler extends HandlerWrapper
56  {
57      private static final Logger LOG = Log.getLogger(ResourceHandler.class);
58  
59      ContextHandler _context;
60      Resource _baseResource;
61      Resource _defaultStylesheet;
62      Resource _stylesheet;
63      String[] _welcomeFiles={"index.html"};
64      MimeTypes _mimeTypes = new MimeTypes();
65      String _cacheControl;
66      boolean _directory;
67      boolean _etags;
68  
69      /* ------------------------------------------------------------ */
70      public ResourceHandler()
71      {
72  
73      }
74  
75      /* ------------------------------------------------------------ */
76      public MimeTypes getMimeTypes()
77      {
78          return _mimeTypes;
79      }
80  
81      /* ------------------------------------------------------------ */
82      public void setMimeTypes(MimeTypes mimeTypes)
83      {
84          _mimeTypes = mimeTypes;
85      }
86  
87      /* ------------------------------------------------------------ */
88      /** Get the directory option.
89       * @return true if directories are listed.
90       */
91      public boolean isDirectoriesListed()
92      {
93          return _directory;
94      }
95  
96      /* ------------------------------------------------------------ */
97      /** Set the directory.
98       * @param directory true if directories are listed.
99       */
100     public void setDirectoriesListed(boolean directory)
101     {
102         _directory = directory;
103     }
104 
105     /* ------------------------------------------------------------ */
106     /**
107      * @return True if ETag processing is done
108      */
109     public boolean isEtags()
110     {
111         return _etags;
112     }
113 
114     /* ------------------------------------------------------------ */
115     /**
116      * @param etags True if ETag processing is done
117      */
118     public void setEtags(boolean etags)
119     {
120         _etags = etags;
121     }
122 
123     /* ------------------------------------------------------------ */
124     @Override
125     public void doStart()
126     throws Exception
127     {
128         Context scontext = ContextHandler.getCurrentContext();
129         _context = (scontext==null?null:scontext.getContextHandler());
130 
131         super.doStart();
132     }
133 
134     /* ------------------------------------------------------------ */
135     /**
136      * @return Returns the resourceBase.
137      */
138     public Resource getBaseResource()
139     {
140         if (_baseResource==null)
141             return null;
142         return _baseResource;
143     }
144 
145     /* ------------------------------------------------------------ */
146     /**
147      * @return Returns the base resource as a string.
148      */
149     public String getResourceBase()
150     {
151         if (_baseResource==null)
152             return null;
153         return _baseResource.toString();
154     }
155 
156 
157     /* ------------------------------------------------------------ */
158     /**
159      * @param base The resourceBase to set.
160      */
161     public void setBaseResource(Resource base)
162     {
163         _baseResource=base;
164     }
165 
166     /* ------------------------------------------------------------ */
167     /**
168      * @param resourceBase The base resource as a string.
169      */
170     public void setResourceBase(String resourceBase)
171     {
172         try
173         {
174             setBaseResource(Resource.newResource(resourceBase));
175         }
176         catch (Exception e)
177         {
178             LOG.warn(e.toString());
179             LOG.debug(e);
180             throw new IllegalArgumentException(resourceBase);
181         }
182     }
183 
184     /* ------------------------------------------------------------ */
185     /**
186      * @return Returns the stylesheet as a Resource.
187      */
188     public Resource getStylesheet()
189     {
190     	if(_stylesheet != null)
191     	{
192     	    return _stylesheet;
193     	}
194     	else
195     	{
196     	    if(_defaultStylesheet == null)
197     	    {
198     	        try
199     	        {
200     	            _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
201     	        }
202     	        catch(IOException e)
203     	        {
204     	            LOG.warn(e.toString());
205     	            LOG.debug(e);
206     	        }
207     	    }
208     	    return _defaultStylesheet;
209     	}
210     }
211 
212     /* ------------------------------------------------------------ */
213     /**
214      * @param stylesheet The location of the stylesheet to be used as a String.
215      */
216     public void setStylesheet(String stylesheet)
217     {
218         try
219         {
220             _stylesheet = Resource.newResource(stylesheet);
221             if(!_stylesheet.exists())
222             {
223                 LOG.warn("unable to find custom stylesheet: " + stylesheet);
224                 _stylesheet = null;
225             }
226         }
227     	catch(Exception e)
228     	{
229     		LOG.warn(e.toString());
230             LOG.debug(e);
231             throw new IllegalArgumentException(stylesheet.toString());
232     	}
233     }
234 
235     /* ------------------------------------------------------------ */
236     /**
237      * @return the cacheControl header to set on all static content.
238      */
239     public String getCacheControl()
240     {
241         return _cacheControl.toString();
242     }
243 
244     /* ------------------------------------------------------------ */
245     /**
246      * @param cacheControl the cacheControl header to set on all static content.
247      */
248     public void setCacheControl(String cacheControl)
249     {
250         _cacheControl=cacheControl;
251     }
252 
253     /* ------------------------------------------------------------ */
254     /*
255      */
256     public Resource getResource(String path) throws MalformedURLException
257     {
258         if (path==null || !path.startsWith("/"))
259             throw new MalformedURLException(path);
260 
261         Resource base = _baseResource;
262         if (base==null)
263         {
264             if (_context==null)
265                 return null;
266             base=_context.getBaseResource();
267             if (base==null)
268                 return null;
269         }
270 
271         try
272         {
273             path=URIUtil.canonicalPath(path);
274             return base.addPath(path);
275         }
276         catch(Exception e)
277         {
278             LOG.ignore(e);
279         }
280 
281         return null;
282     }
283 
284     /* ------------------------------------------------------------ */
285     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
286     {
287         String servletPath;
288         String pathInfo;
289         Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
290         if (included != null && included.booleanValue())
291         {
292             servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
293             pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
294 
295             if (servletPath == null && pathInfo == null)
296             {
297                 servletPath = request.getServletPath();
298                 pathInfo = request.getPathInfo();
299             }
300         }
301         else
302         {
303             servletPath = request.getServletPath();
304             pathInfo = request.getPathInfo();
305         }
306 
307         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
308         return getResource(pathInContext);
309     }
310 
311 
312     /* ------------------------------------------------------------ */
313     public String[] getWelcomeFiles()
314     {
315         return _welcomeFiles;
316     }
317 
318     /* ------------------------------------------------------------ */
319     public void setWelcomeFiles(String[] welcomeFiles)
320     {
321         _welcomeFiles=welcomeFiles;
322     }
323 
324     /* ------------------------------------------------------------ */
325     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
326     {
327         for (int i=0;i<_welcomeFiles.length;i++)
328         {
329             Resource welcome=directory.addPath(_welcomeFiles[i]);
330             if (welcome.exists() && !welcome.isDirectory())
331                 return welcome;
332         }
333 
334         return null;
335     }
336 
337     /* ------------------------------------------------------------ */
338     /*
339      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
340      */
341     @Override
342     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
343     {
344         if (baseRequest.isHandled())
345             return;
346 
347         boolean skipContentBody = false;
348 
349         if(!HttpMethod.GET.is(request.getMethod()))
350         {
351             if(!HttpMethod.HEAD.is(request.getMethod()))
352             {
353                 //try another handler
354                 super.handle(target, baseRequest, request, response);
355                 return;
356             }
357             skipContentBody = true;
358         }
359 
360         Resource resource = getResource(request);
361 
362         if (resource==null || !resource.exists())
363         {
364             if (target.endsWith("/jetty-dir.css"))
365             {
366                 resource = getStylesheet();
367                 if (resource==null)
368                     return;
369                 response.setContentType("text/css");
370             }
371             else
372             {
373                 //no resource - try other handlers
374                 super.handle(target, baseRequest, request, response);
375                 return;
376             }
377         }
378 
379         // We are going to serve something
380         baseRequest.setHandled(true);
381 
382         if (resource.isDirectory())
383         {
384             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
385             {
386                 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
387                 return;
388             }
389 
390             Resource welcome=getWelcome(resource);
391             if (welcome!=null && welcome.exists())
392                 resource=welcome;
393             else
394             {
395                 doDirectory(request,response,resource);
396                 baseRequest.setHandled(true);
397                 return;
398             }
399         }
400 
401         // set some headers
402         long last_modified=resource.lastModified();
403         String etag=null;
404         if (_etags)
405         {
406             // simple handling of only a single etag
407             String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
408             etag=resource.getWeakETag();
409             if (ifnm!=null && resource!=null && ifnm.equals(etag))
410             {
411                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
412                 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
413                 return;
414             }
415         }
416         
417         
418         if (last_modified>0)
419         {
420             long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
421             if (if_modified>0 && last_modified/1000<=if_modified/1000)
422             {
423                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
424                 return;
425             }
426         }
427 
428         String mime=_mimeTypes.getMimeByExtension(resource.toString());
429         if (mime==null)
430             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
431 
432         // set the headers
433         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
434         response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
435         if (_etags)
436             baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
437         
438         if(skipContentBody)
439             return;
440         // Send the content
441         OutputStream out =null;
442         try {out = response.getOutputStream();}
443         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
444 
445         // See if a short direct method can be used?
446         /* TODO file mapped buffers
447         if (out instanceof HttpOutput)
448         {
449             // TODO file mapped buffers
450             ((HttpOutput)out).send(resource.getInputStream());
451         }
452         else*/
453         {
454             // Write content normally
455             resource.writeTo(out,0,resource.length());
456         }
457     }
458 
459     /* ------------------------------------------------------------ */
460     protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
461         throws IOException
462     {
463         if (_directory)
464         {
465             String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
466             response.setContentType("text/html; charset=UTF-8");
467             response.getWriter().println(listing);
468         }
469         else
470             response.sendError(HttpStatus.FORBIDDEN_403);
471     }
472 
473     /* ------------------------------------------------------------ */
474     /** Set the response headers.
475      * This method is called to set the response headers such as content type and content length.
476      * May be extended to add additional headers.
477      * @param response
478      * @param resource
479      * @param mimeType
480      */
481     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
482     {
483         if (mimeType!=null)
484             response.setContentType(mimeType);
485 
486         long length=resource.length();
487 
488         if (response instanceof Response)
489         {
490             HttpFields fields = ((Response)response).getHttpFields();
491 
492             if (length>0)
493                 fields.putLongField(HttpHeader.CONTENT_LENGTH,length);
494 
495             if (_cacheControl!=null)
496                 fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
497         }
498         else
499         {
500             if (length>0)
501                 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
502 
503             if (_cacheControl!=null)
504                 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.toString());
505         }
506 
507     }
508 }