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