View Javadoc

1   // ========================================================================
2   // Copyright (c) 1999-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.server.handler;
15  
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.net.MalformedURLException;
19  
20  import javax.servlet.ServletException;
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  
24  import org.eclipse.jetty.http.HttpFields;
25  import org.eclipse.jetty.http.HttpHeaders;
26  import org.eclipse.jetty.http.HttpMethods;
27  import org.eclipse.jetty.http.HttpStatus;
28  import org.eclipse.jetty.http.MimeTypes;
29  import org.eclipse.jetty.io.Buffer;
30  import org.eclipse.jetty.io.ByteArrayBuffer;
31  import org.eclipse.jetty.io.WriterOutputStream;
32  import org.eclipse.jetty.server.HttpConnection;
33  import org.eclipse.jetty.server.Request;
34  import org.eclipse.jetty.server.Response;
35  import org.eclipse.jetty.server.handler.ContextHandler.Context;
36  import org.eclipse.jetty.util.TypeUtil;
37  import org.eclipse.jetty.util.URIUtil;
38  import org.eclipse.jetty.util.log.Log;
39  import org.eclipse.jetty.util.resource.FileResource;
40  import org.eclipse.jetty.util.resource.Resource;
41  
42  
43  /* ------------------------------------------------------------ */
44  /** Resource Handler.
45   * 
46   * This handle will serve static content and handle If-Modified-Since headers.
47   * No caching is done.
48   * Requests that cannot be handled are let pass (Eg no 404's)
49   * 
50   * 
51   * @org.apache.xbean.XBean
52   */
53  public class ResourceHandler extends AbstractHandler
54  {
55      ContextHandler _context;
56      Resource _baseResource;
57      String[] _welcomeFiles={"index.html"};
58      MimeTypes _mimeTypes = new MimeTypes();
59      ByteArrayBuffer _cacheControl;
60      boolean _aliases;
61  
62      /* ------------------------------------------------------------ */
63      public ResourceHandler()
64      {
65      }
66  
67      /* ------------------------------------------------------------ */
68      public MimeTypes getMimeTypes()
69      {
70          return _mimeTypes;
71      }
72  
73      /* ------------------------------------------------------------ */
74      public void setMimeTypes(MimeTypes mimeTypes)
75      {
76          _mimeTypes = mimeTypes;
77      }
78  
79      /* ------------------------------------------------------------ */
80      /**
81       * @return True if resource aliases are allowed.
82       */
83      public boolean isAliases()
84      {
85          return _aliases;
86      }
87  
88      /* ------------------------------------------------------------ */
89      /**
90       * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
91       * Allowing aliases can significantly increase security vulnerabilities.
92       * If this handler is deployed inside a ContextHandler, then the 
93       * {@link ContextHandler#isAliases()} takes precedent.
94       * @param aliases True if aliases are supported.
95       */
96      public void setAliases(boolean aliases)
97      {
98          _aliases = aliases;
99      }
100 
101     /* ------------------------------------------------------------ */
102     public void doStart()
103     throws Exception
104     {
105         Context scontext = ContextHandler.getCurrentContext();
106         _context = (scontext==null?null:scontext.getContextHandler());
107         
108         if (_context!=null)
109             _aliases=_context.isAliases();
110         
111         if (!_aliases && !FileResource.getCheckAliases())
112             throw new IllegalStateException("Alias checking disabled");
113         
114         super.doStart();
115     }
116 
117     /* ------------------------------------------------------------ */
118     /**
119      * @return Returns the resourceBase.
120      */
121     public Resource getBaseResource()
122     {
123         if (_baseResource==null)
124             return null;
125         return _baseResource;
126     }
127 
128     /* ------------------------------------------------------------ */
129     /**
130      * @return Returns the base resource as a string.
131      */
132     public String getResourceBase()
133     {
134         if (_baseResource==null)
135             return null;
136         return _baseResource.toString();
137     }
138 
139     
140     /* ------------------------------------------------------------ */
141     /**
142      * @param base The resourceBase to set.
143      */
144     public void setBaseResource(Resource base) 
145     {
146         _baseResource=base;
147     }
148 
149     /* ------------------------------------------------------------ */
150     /**
151      * @param resourceBase The base resource as a string.
152      */
153     public void setResourceBase(String resourceBase) 
154     {
155         try
156         {
157             setBaseResource(Resource.newResource(resourceBase));
158         }
159         catch (Exception e)
160         {
161             Log.warn(e);
162             throw new IllegalArgumentException(resourceBase);
163         }
164     }
165 
166     /* ------------------------------------------------------------ */
167     /**
168      * @return the cacheControl header to set on all static content.
169      */
170     public String getCacheControl()
171     {
172         return _cacheControl.toString();
173     }
174 
175     /* ------------------------------------------------------------ */
176     /**
177      * @param cacheControl the cacheControl header to set on all static content.
178      */
179     public void setCacheControl(String cacheControl)
180     {
181         _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
182     }
183 
184     /* ------------------------------------------------------------ */
185     /* 
186      */
187     public Resource getResource(String path) throws MalformedURLException
188     {
189         if (path==null || !path.startsWith("/"))
190             throw new MalformedURLException(path);
191         
192         Resource base = _baseResource;
193         if (base==null)
194         {
195             if (_context==null)
196                 return null;            
197             base=_context.getBaseResource();
198             if (base==null)
199                 return null;
200         }
201 
202         try
203         {
204             path=URIUtil.canonicalPath(path);
205             return base.addPath(path);
206         }
207         catch(Exception e)
208         {
209             Log.ignore(e);
210         }
211                     
212         return null;
213     }
214 
215     /* ------------------------------------------------------------ */
216     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
217     {
218         String path_info=request.getPathInfo();
219         if (path_info==null)
220             return null;
221         return getResource(path_info);
222     }
223 
224 
225     /* ------------------------------------------------------------ */
226     public String[] getWelcomeFiles()
227     {
228         return _welcomeFiles;
229     }
230 
231     /* ------------------------------------------------------------ */
232     public void setWelcomeFiles(String[] welcomeFiles)
233     {
234         _welcomeFiles=welcomeFiles;
235     }
236     
237     /* ------------------------------------------------------------ */
238     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
239     {
240         for (int i=0;i<_welcomeFiles.length;i++)
241         {
242             Resource welcome=directory.addPath(_welcomeFiles[i]);
243             if (welcome.exists() && !welcome.isDirectory())
244                 return welcome;
245         }
246 
247         return null;
248     }
249 
250     /* ------------------------------------------------------------ */
251     /* 
252      * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
253      */
254     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
255     {
256         if (baseRequest.isHandled())
257             return;
258         
259         boolean skipContentBody = false;
260         if(!HttpMethods.GET.equals(request.getMethod()))
261         {
262             if(!HttpMethods.HEAD.equals(request.getMethod()))
263                 return;
264             skipContentBody = true;
265         }
266      
267         Resource resource=getResource(request);
268         
269         if (resource==null || !resource.exists())
270             return;
271         if (!_aliases && resource.getAlias()!=null)
272         {
273             Log.info(resource+" aliased to "+resource.getAlias());
274             return;
275         }
276 
277         // We are going to server something
278         baseRequest.setHandled(true);
279         
280         if (resource.isDirectory())
281         {
282             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
283             {
284                 response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH));
285                 return;
286             }
287             
288             Resource welcome=getWelcome(resource);
289             if (welcome!=null && welcome.exists())
290                 resource=welcome;
291             else
292             {
293                 doDirectory(request,response,resource);
294                 baseRequest.setHandled(true);
295                 return;
296             }
297         }
298         
299         // set some headers
300         long last_modified=resource.lastModified();
301         if (last_modified>0)
302         {
303             long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
304             if (if_modified>0 && last_modified/1000<=if_modified/1000)
305             {
306                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
307                 return;
308             }
309         }
310         
311         Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
312         if (mime==null)
313             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
314         
315         // set the headers
316         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
317         response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
318         if(skipContentBody)
319             return;
320         // Send the content
321         OutputStream out =null;
322         try {out = response.getOutputStream();}
323         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
324         
325         // See if a short direct method can be used?
326         if (out instanceof HttpConnection.Output)
327         {
328             // TODO file mapped buffers
329             ((HttpConnection.Output)out).sendContent(resource.getInputStream());
330         }
331         else
332         {
333             // Write content normally
334             resource.writeTo(out,0,resource.length());
335         }
336     }
337 
338     /* ------------------------------------------------------------ */
339     protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
340         throws IOException
341     {
342         response.sendError(HttpStatus.FORBIDDEN_403);
343     }
344     
345     /* ------------------------------------------------------------ */
346     /** Set the response headers.
347      * This method is called to set the response headers such as content type and content length.
348      * May be extended to add additional headers.
349      * @param response
350      * @param resource
351      * @param mimeType
352      */
353     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
354     {
355         if (mimeType!=null)
356             response.setContentType(mimeType);
357 
358         long length=resource.length();
359         
360         if (response instanceof Response)
361         {
362             HttpFields fields = ((Response)response).getHttpFields();
363 
364             if (length>0)
365                 fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
366                 
367             if (_cacheControl!=null)
368                 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
369         }
370         else
371         {
372             if (length>0)
373                 response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length));
374                 
375             if (_cacheControl!=null)
376                 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
377         }
378         
379     }
380 }