View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.fcgi.server.proxy;
20  
21  import java.io.IOException;
22  import java.net.URISyntaxException;
23  import java.net.URL;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import javax.servlet.Filter;
28  import javax.servlet.FilterChain;
29  import javax.servlet.FilterConfig;
30  import javax.servlet.ServletContext;
31  import javax.servlet.ServletException;
32  import javax.servlet.ServletRequest;
33  import javax.servlet.ServletResponse;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  /**
38   * Inspired by nginx's try_files functionality.
39   * <p />
40   * This filter accepts the <code>files</code> init-param as a list of space-separated
41   * file URIs. The special token <code>$path</code> represents the current request URL's
42   * path (the portion after the context path).
43   * <p />
44   * Typical example of how this filter can be configured is the following:
45   * <pre>
46   * &lt;filter&gt;
47   *     &lt;filter-name&gt;try_files&lt;/filter-name&gt;
48   *     &lt;filter-class&gt;org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter&lt;/filter-class&gt;
49   *     &lt;init-param&gt;
50   *         &lt;param-name&gt;files&lt;/param-name&gt;
51   *         &lt;param-value&gt;maintenance.html $path index.php?p=$path&lt;/param-value&gt;
52   *     &lt;/init-param&gt;
53   * &lt;/filter&gt;
54   * </pre>
55   * For a request such as <code>/context/path/to/resource.ext</code>, this filter will
56   * try to serve the <code>/maintenance.html</code> file if it finds it; failing that,
57   * it will try to serve the <code>/path/to/resource.ext</code> file if it finds it;
58   * failing that it will forward the request to <code>index.php?p=/path/to/resource.ext</code>.
59   * The last file URI specified in the list is therefore the "fallback" to which the request
60   * is forwarded to in case no previous files can be found.
61   * <p />
62   * The files are resolved using {@link ServletContext#getResource(String)} to make sure
63   * that only files visible to the application are served.
64   *
65   * @see FastCGIProxyServlet
66   */
67  public class TryFilesFilter implements Filter
68  {
69      public static final String FILES_INIT_PARAM = "files";
70  
71      private String[] files;
72  
73      @Override
74      public void init(FilterConfig config) throws ServletException
75      {
76          String param = config.getInitParameter(FILES_INIT_PARAM);
77          if (param == null)
78              throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM));
79          files = param.split(" ");
80      }
81  
82      @Override
83      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
84      {
85          HttpServletRequest httpRequest = (HttpServletRequest)request;
86          HttpServletResponse httpResponse = (HttpServletResponse)response;
87  
88          for (int i = 0; i < files.length - 1; ++i)
89          {
90              String file = files[i];
91              String resolved = resolve(httpRequest, file);
92  
93              URL url = request.getServletContext().getResource(resolved);
94              if (url == null)
95                  continue;
96  
97              if (Files.isReadable(toPath(url)))
98              {
99                  chain.doFilter(httpRequest, httpResponse);
100                 return;
101             }
102         }
103 
104         // The last one is the fallback
105         fallback(httpRequest, httpResponse, chain, files[files.length - 1]);
106     }
107 
108     private Path toPath(URL url) throws IOException
109     {
110         try
111         {
112             return Paths.get(url.toURI());
113         }
114         catch (URISyntaxException x)
115         {
116             throw new IOException(x);
117         }
118     }
119 
120     protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException
121     {
122         String resolved = resolve(request, fallback);
123         request.getServletContext().getRequestDispatcher(resolved).forward(request, response);
124     }
125 
126     private String resolve(HttpServletRequest request, String value)
127     {
128         String path = request.getServletPath();
129         String info = request.getPathInfo();
130         if (info != null)
131             path += info;
132         if (!path.startsWith("/"))
133             path = "/" + path;
134         return value.replaceAll("\\$path", path);
135     }
136 
137     @Override
138     public void destroy()
139     {
140     }
141 }