Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetty 12 - Alternate TryPathsHandler based on Request.Processor existence #8781

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,110 @@
import java.util.List;

import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;

/**
* <p>Inspired by nginx's {@code try_files} functionality.</p>
* <p> This handler can be configured with a list of URI paths.
* The special token {@code $path} represents the current request URI
* path (the portion after the context path).</p>
*
* <p> This handler can be configured with a list of rewrite URI paths.
* The special token {@code $path} represents the current request
* {@code pathInContext} (the portion after the context path).</p>
*
* <p>Typical example of how this handler can be configured is the following:</p>
* <pre>{@code
* TryPathsHandler tryPaths = new TryPathsHandler();
* tryPaths.setPaths("/maintenance.html", "$path", "/index.php?p=$path");
* TryPathsHandler tryPathsHandler = new TryPathsHandler();
* tryPathsHandler.setPaths("/maintenance.html", "$path", "/index.php?p=$path");
*
* PathMappingsHandler pathMappingsHandler = new PathMappingsHandler();
* tryPathsHandler.setHandler(pathMappingsHandler);
*
* pathMappingsHandler.addMapping(new ServletPathSpec("*.php"), new PHPHandler());
* pathMappingsHandler.addMapping(new ServletPathSpec("/"), new ResourceHandler());
* }</pre>
* <p>For a request such as {@code /context/path/to/resource.ext}, this
* handler will try to serve the {@code /maintenance.html} file if it finds
* it; failing that, it will try to serve the {@code /path/to/resource.ext}
* file if it finds it; failing that it will forward the request to
* {@code /index.php?p=/path/to/resource.ext} to the next handler.</p>
* <p>The last URI path specified in the list is therefore the "fallback" to
* which the request is forwarded to in case no previous files can be found.</p>
* <p>The file paths are resolved against {@link Context#getBaseResource()}
* to make sure that only files visible to the application are served.</p>
*
* <p>For a request such as {@code /context/path/to/resource.ext}:</p>
* <ul>
* <li>This handler rewrites the request {@code pathInContext} to
* {@code /maintenance.html} and forwards the request to the next handler,
* where it matches the {@code /} mapping, hitting the {@code ResourceHandler}
* that serves the file if it exists.</li>
* <li>Otherwise, this handler rewrites the request {@code pathInContext} to
* {@code /path/to/resource.ext} and forwards the request to the next handler,
* where it matches the {@code /} mapping, hitting the {@code ResourceHandler}
* that serves the file if it exists.</li>
* <li>Otherwise, this handler rewrites the request {@code pathInContext} to
* {@code /index.php?p=/path/to/resource.ext} and forwards the request to
* the next handler, where it matches the {@code *.php} mapping, hitting
* the {@code PHPHandler}.</li>
* </ul>
*
* <p>The original path and query may be stored as request attributes,
* under the names specified by {@link #setOriginalPathAttribute(String)}
* and {@link #setOriginalQueryAttribute(String)}.</p>
*/
public class TryPathsHandler extends Handler.Wrapper
{
private String originalPathAttribute;
private String originalQueryAttribute;
private List<String> paths;

/**
* @return the attribute name of the original request path
*/
public String getOriginalPathAttribute()
{
return originalPathAttribute;
}

/**
* <p>Sets the request attribute name to use to
* retrieve the original request path.</p>
*
* @param originalPathAttribute the attribute name of the original
* request path
*/
public void setOriginalPathAttribute(String originalPathAttribute)
{
this.originalPathAttribute = originalPathAttribute;
}

/**
* @return the attribute name of the original request query
*/
public String getOriginalQueryAttribute()
{
return originalQueryAttribute;
}

/**
* <p>Sets the request attribute name to use to
* retrieve the original request query.</p>
*
* @param originalQueryAttribute the attribute name of the original
* request query
*/
public void setOriginalQueryAttribute(String originalQueryAttribute)
{
this.originalQueryAttribute = originalQueryAttribute;
}

/**
* @return the rewrite URI paths
*/
public List<String> getPaths()
{
return paths;
}

/**
* <p>Sets a list of rewrite URI paths.</p>
* The special token {@code $path} represents the current request
* {@code pathInContext} (the portion after the context path).</p>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing open <p>?

*
* @param paths the rewrite URI paths
*/
public void setPaths(List<String> paths)
{
this.paths = paths;
Expand All @@ -54,27 +128,15 @@ public void setPaths(List<String> paths)
@Override
public Request.Processor handle(Request request) throws Exception
{
String interpolated = interpolate(request, "$path");
Resource rootResource = request.getContext().getBaseResource();
if (rootResource != null)
for (String path : paths)
{
for (String path : paths)
{
interpolated = interpolate(request, path);
Resource resource = rootResource.resolve(interpolated);
if (resource != null && resource.exists())
break;
}
String interpolated = interpolate(request, path);
Request.WrapperProcessor result = new Request.WrapperProcessor(new TryPathsRequest(request, interpolated));
Request.Processor childProcessor = super.handle(result);
if (childProcessor != null)
return result.wrapProcessor(childProcessor);
}
Request.WrapperProcessor result = new Request.WrapperProcessor(new TryPathsRequest(request, interpolated));
return result.wrapProcessor(super.handle(result));
}

private Request.Processor fallback(Request request) throws Exception
{
String fallback = paths.isEmpty() ? "$path" : paths.get(paths.size() - 1);
String interpolated = interpolate(request, fallback);
return super.handle(new TryPathsRequest(request, interpolated));
return null;
}

private String interpolate(Request request, String value)
Expand All @@ -83,14 +145,37 @@ private String interpolate(Request request, String value)
return value.replace("$path", path);
}

private static class TryPathsRequest extends Request.Wrapper
private class TryPathsRequest extends Request.Wrapper
{
private final HttpURI _uri;

public TryPathsRequest(Request wrapped, String pathInContext)
public TryPathsRequest(Request wrapped, String newPathQuery)
{
super(wrapped);
_uri = Request.newHttpURIFrom(wrapped, URIUtil.canonicalPath(pathInContext));

HttpURI originalURI = wrapped.getHttpURI();

String originalPathAttribute = getOriginalPathAttribute();
if (originalPathAttribute != null)
setAttribute(originalPathAttribute, Request.getPathInContext(wrapped));
String originalQueryAttribute = getOriginalQueryAttribute();
if (originalQueryAttribute != null)
setAttribute(originalQueryAttribute, originalURI.getQuery());

String originalContextPath = Request.getContextPath(wrapped);
HttpURI.Mutable rewrittenURI = HttpURI.build(originalURI);
int queryIdx = newPathQuery.indexOf('?');
if (queryIdx >= 0)
{
String path = newPathQuery.substring(0, queryIdx);
rewrittenURI.path(URIUtil.addPaths(originalContextPath, path));
rewrittenURI.query(newPathQuery.substring(queryIdx + 1));
}
else
{
rewrittenURI.path(URIUtil.addPaths(originalContextPath, newPathQuery));
}
_uri = rewrittenURI.asImmutable();
}

@Override
Expand Down
Loading