1   // ========================================================================
2   // Copyright 1999-2005 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.handler;
16  
17  import java.io.IOException;
18  import java.io.OutputStream;
19  import java.net.MalformedURLException;
20  
21  import javax.servlet.ServletException;
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.mortbay.io.Buffer;
26  import org.mortbay.io.ByteArrayBuffer;
27  import org.mortbay.io.WriterOutputStream;
28  import org.mortbay.jetty.HttpConnection;
29  import org.mortbay.jetty.HttpFields;
30  import org.mortbay.jetty.HttpHeaders;
31  import org.mortbay.jetty.HttpMethods;
32  import org.mortbay.jetty.MimeTypes;
33  import org.mortbay.jetty.Request;
34  import org.mortbay.jetty.Response;
35  import org.mortbay.jetty.handler.ContextHandler.SContext;
36  import org.mortbay.log.Log;
37  import org.mortbay.resource.Resource;
38  import org.mortbay.util.TypeUtil;
39  import org.mortbay.util.URIUtil;
40  
41  
42  /* ------------------------------------------------------------ */
43  /** Resource Handler.
44   * 
45   * This handle will serve static content and handle If-Modified-Since headers.
46   * No caching is done.
47   * Requests that cannot be handled are let pass (Eg no 404's)
48   * 
49   * @author Greg Wilkins (gregw)
50   * @org.apache.xbean.XBean
51   */
52  public class ResourceHandler extends AbstractHandler
53  {
54      ContextHandler _context;
55      Resource _baseResource;
56      String[] _welcomeFiles={"index.html"};
57      MimeTypes _mimeTypes = new MimeTypes();
58      ByteArrayBuffer _cacheControl;
59  
60      /* ------------------------------------------------------------ */
61      public ResourceHandler()
62      {
63      }
64      
65      /* ------------------------------------------------------------ */
66      public void doStart()
67      throws Exception
68      {
69          SContext scontext = ContextHandler.getCurrentContext();
70          _context = (scontext==null?null:scontext.getContextHandler());
71          super.doStart();
72      }
73  
74      /* ------------------------------------------------------------ */
75      /**
76       * @return Returns the resourceBase.
77       */
78      public Resource getBaseResource()
79      {
80          if (_baseResource==null)
81              return null;
82          return _baseResource;
83      }
84  
85      /* ------------------------------------------------------------ */
86      /**
87       * @return Returns the base resource as a string.
88       */
89      public String getResourceBase()
90      {
91          if (_baseResource==null)
92              return null;
93          return _baseResource.toString();
94      }
95  
96      
97      /* ------------------------------------------------------------ */
98      /**
99       * @param base The resourceBase to set.
100      */
101     public void setBaseResource(Resource base) 
102     {
103         _baseResource=base;
104     }
105 
106     /* ------------------------------------------------------------ */
107     /**
108      * @param resourceBase The base resource as a string.
109      */
110     public void setResourceBase(String resourceBase) 
111     {
112         try
113         {
114             setBaseResource(Resource.newResource(resourceBase));
115         }
116         catch (Exception e)
117         {
118             Log.warn(e);
119             throw new IllegalArgumentException(resourceBase);
120         }
121     }
122 
123     /* ------------------------------------------------------------ */
124     /**
125      * @return the cacheControl header to set on all static content.
126      */
127     public String getCacheControl()
128     {
129         return _cacheControl.toString();
130     }
131 
132     /* ------------------------------------------------------------ */
133     /**
134      * @param cacheControl the cacheControl header to set on all static content.
135      */
136     public void setCacheControl(String cacheControl)
137     {
138         _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
139     }
140 
141     /* ------------------------------------------------------------ */
142     /* 
143      */
144     public Resource getResource(String path) throws MalformedURLException
145     {
146         if (path==null || !path.startsWith("/"))
147             throw new MalformedURLException(path);
148         
149         Resource base = _baseResource;
150         if (base==null)
151         {
152             if (_context==null)
153                 return null;            
154             base=_context.getBaseResource();
155             if (base==null)
156                 return null;
157         }
158 
159         try
160         {
161             path=URIUtil.canonicalPath(path);
162             Resource resource=base.addPath(path);
163             return resource;
164         }
165         catch(Exception e)
166         {
167             Log.ignore(e);
168         }
169                     
170         return null;
171     }
172 
173     /* ------------------------------------------------------------ */
174     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
175     {
176         String path_info=request.getPathInfo();
177         if (path_info==null)
178             return null;
179         return getResource(path_info);
180     }
181 
182 
183     /* ------------------------------------------------------------ */
184     public String[] getWelcomeFiles()
185     {
186         return _welcomeFiles;
187     }
188 
189     /* ------------------------------------------------------------ */
190     public void setWelcomeFiles(String[] welcomeFiles)
191     {
192         _welcomeFiles=welcomeFiles;
193     }
194     
195     /* ------------------------------------------------------------ */
196     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
197     {
198         for (int i=0;i<_welcomeFiles.length;i++)
199         {
200             Resource welcome=directory.addPath(_welcomeFiles[i]);
201             if (welcome.exists() && !welcome.isDirectory())
202                 return welcome;
203         }
204 
205         return null;
206     }
207 
208     /* ------------------------------------------------------------ */
209     /* 
210      * @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
211      */
212     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
213     {
214         Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
215         if (base_request.isHandled() || !request.getMethod().equals(HttpMethods.GET))
216             return;
217      
218         Resource resource=getResource(request);
219         
220         if (resource==null || !resource.exists())
221             return;
222 
223         // We are going to server something
224         base_request.setHandled(true);
225         
226         if (resource.isDirectory())
227         {
228             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
229             {
230                 response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH));
231                 return;
232             }
233             resource=getWelcome(resource);
234 
235             if (resource==null || !resource.exists() || resource.isDirectory())
236             {
237                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
238                 return;
239             }
240         }
241         
242         // set some headers
243         long last_modified=resource.lastModified();
244         if (last_modified>0)
245         {
246             long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
247             if (if_modified>0 && last_modified/1000<=if_modified/1000)
248             {
249                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
250                 return;
251             }
252         }
253         
254         Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
255         if (mime==null)
256             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
257         
258         // set the headers
259         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
260 
261         // Send the content
262         OutputStream out =null;
263         try {out = response.getOutputStream();}
264         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
265         
266         // See if a short direct method can be used?
267         if (out instanceof HttpConnection.Output)
268         {
269             // TODO file mapped buffers
270             response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
271             ((HttpConnection.Output)out).sendContent(resource.getInputStream());
272         }
273         else
274         {
275             // Write content normally
276             response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
277             resource.writeTo(out,0,resource.length());
278         }
279     }
280 
281     /* ------------------------------------------------------------ */
282     /** Set the response headers.
283      * This method is called to set the response headers such as content type and content length.
284      * May be extended to add additional headers.
285      * @param response
286      * @param resource
287      * @param mimeType
288      */
289     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
290     {
291         if (mimeType!=null)
292             response.setContentType(mimeType);
293 
294         long length=resource.length();
295         
296         if (response instanceof Response)
297         {
298             HttpFields fields = ((Response)response).getHttpFields();
299 
300             if (length>0)
301                 fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
302                 
303             if (_cacheControl!=null)
304                 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
305         }
306         else
307         {
308             if (length>0)
309                 response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length));
310                 
311             if (_cacheControl!=null)
312                 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
313         }
314         
315     }
316 }