| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.fcgi.server.proxy; |
| |
| import java.io.IOException; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import javax.servlet.Filter; |
| import javax.servlet.FilterChain; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| /** |
| * Inspired by nginx's try_files functionality. |
| * <p /> |
| * This filter accepts the <code>files</code> init-param as a list of space-separated |
| * file URIs. The special token <code>$path</code> represents the current request URL's |
| * path (the portion after the context path). |
| * <p /> |
| * Typical example of how this filter can be configured is the following: |
| * <pre> |
| * <filter> |
| * <filter-name>try_files</filter-name> |
| * <filter-class>org.eclipse.jetty.fcgi.server.proxy.TryFilesFilter</filter-class> |
| * <init-param> |
| * <param-name>files</param-name> |
| * <param-value>maintenance.html $path index.php?p=$path</param-value> |
| * </init-param> |
| * </filter> |
| * </pre> |
| * For a request such as <code>/context/path/to/resource.ext</code>, this filter will |
| * try to serve the <code>/maintenance.html</code> file if it finds it; failing that, |
| * it will try to serve the <code>/path/to/resource.ext</code> file if it finds it; |
| * failing that it will forward the request to <code>index.php?p=/path/to/resource.ext</code>. |
| * The last file URI specified in the list is therefore the "fallback" to which the request |
| * is forwarded to in case no previous files can be found. |
| * <p /> |
| * The files are resolved using {@link ServletContext#getResource(String)} to make sure |
| * that only files visible to the application are served. |
| * |
| * @see FastCGIProxyServlet |
| */ |
| public class TryFilesFilter implements Filter |
| { |
| public static final String FILES_INIT_PARAM = "files"; |
| |
| private String[] files; |
| |
| @Override |
| public void init(FilterConfig config) throws ServletException |
| { |
| String param = config.getInitParameter(FILES_INIT_PARAM); |
| if (param == null) |
| throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM)); |
| files = param.split(" "); |
| } |
| |
| @Override |
| public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException |
| { |
| HttpServletRequest httpRequest = (HttpServletRequest)request; |
| HttpServletResponse httpResponse = (HttpServletResponse)response; |
| |
| for (int i = 0; i < files.length - 1; ++i) |
| { |
| String file = files[i]; |
| String resolved = resolve(httpRequest, file); |
| |
| URL url = request.getServletContext().getResource(resolved); |
| if (url == null) |
| continue; |
| |
| if (Files.isReadable(toPath(url))) |
| { |
| chain.doFilter(httpRequest, httpResponse); |
| return; |
| } |
| } |
| |
| // The last one is the fallback |
| fallback(httpRequest, httpResponse, chain, files[files.length - 1]); |
| } |
| |
| private Path toPath(URL url) throws IOException |
| { |
| try |
| { |
| return Paths.get(url.toURI()); |
| } |
| catch (URISyntaxException x) |
| { |
| throw new IOException(x); |
| } |
| } |
| |
| protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException |
| { |
| String resolved = resolve(request, fallback); |
| request.getServletContext().getRequestDispatcher(resolved).forward(request, response); |
| } |
| |
| private String resolve(HttpServletRequest request, String value) |
| { |
| String path = request.getServletPath(); |
| String info = request.getPathInfo(); |
| if (info != null) |
| path += info; |
| if (!path.startsWith("/")) |
| path = "/" + path; |
| return value.replaceAll("\\$path", path); |
| } |
| |
| @Override |
| public void destroy() |
| { |
| } |
| } |