Skip to content

Commit

Permalink
Issue #11494 - PathMappingsHandler exposes PathSpec and Context based…
Browse files Browse the repository at this point in the history
… on PathSpec. (#12738)

* Issue #11494 - PathMappingsHandler exposes PathSpec and Context based on PathSpec.
  • Loading branch information
joakime authored Jan 27, 2025
1 parent 91ca121 commit 730f063
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

import org.eclipse.jetty.http.MimeTypes;
Expand Down Expand Up @@ -165,4 +166,110 @@ static String getPathInContext(String encodedContextPath, String encodedPath)
return null;
return encodedPath.substring(encodedContextPath.length());
}

class Wrapper implements Context
{
private final Context _wrapped;

public Wrapper(Context context)
{
_wrapped = context;
}

@Override
public <T> T decorate(T o)
{
return _wrapped.decorate(o);
}

@Override
public void destroy(Object o)
{
_wrapped.destroy(o);
}

@Override
public String getContextPath()
{
return _wrapped.getContextPath();
}

@Override
public ClassLoader getClassLoader()
{
return _wrapped.getClassLoader();
}

@Override
public Resource getBaseResource()
{
return _wrapped.getBaseResource();
}

@Override
public Request.Handler getErrorHandler()
{
return _wrapped.getErrorHandler();
}

@Override
public List<String> getVirtualHosts()
{
return _wrapped.getVirtualHosts();
}

@Override
public MimeTypes getMimeTypes()
{
return _wrapped.getMimeTypes();
}

@Override
public void execute(Runnable task)
{
_wrapped.execute(task);
}

@Override
public Object removeAttribute(String name)
{
return _wrapped.removeAttribute(name);
}

@Override
public Object setAttribute(String name, Object attribute)
{
return _wrapped.setAttribute(name, attribute);
}

@Override
public Object getAttribute(String name)
{
return _wrapped.getAttribute(name);
}

@Override
public Set<String> getAttributeNameSet()
{
return _wrapped.getAttributeNameSet();
}

@Override
public void run(Runnable task)
{
_wrapped.run(task);
}

@Override
public void run(Runnable task, Request request)
{
_wrapped.run(task, request);
}

@Override
public File getTempDirectory()
{
return _wrapped.getTempDirectory();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,50 @@
package org.eclipse.jetty.server.handler;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.MatchedPath;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A Handler that delegates to other handlers through a configured {@link PathMappings}.
* <p>A Handler that delegates to other handlers through a configured {@link PathMappings}.</p>
*
* {@code
* PathMappingHandler pathMappingsHandler = new PathMappingsHandler();
* pathMappingsHandler.addMapping(new ServletPathSpec("/"), new MyRootHandler());
* pathMappingsHandler.addMapping(new ServletPathSpec("/index.html"), new MyIndexHandler());
* pathMappingsHandler.addMapping(new ServletPathSpec("/static/*"), new ResourceHandler());
* pathMappingsHandler.addMapping(new RegexPathSpec("/rest/.*\\.api$"), new RestHandler());
* pathMappingsHandler.addMapping(new UriTemplatePathSpec("/chat/{channel}/{mode}"), new ChatHandler());
* }
*
* <p>
* Request handling can access the {@link PathSpec} used via the {@code Request#getAttribute} on
* the name {@link #PATHSPEC_ATTR}
* </p>
*/
public class PathMappingsHandler extends Handler.AbstractContainer
{
private static final Logger LOG = LoggerFactory.getLogger(PathMappingsHandler.class);
public static final String PATHSPEC_ATTR = PathMappingsHandler.class.getName() + ".pathSpec";

private final PathMappings<Handler> mappings = new PathMappings<>();

Expand All @@ -56,6 +77,12 @@ public List<Handler> getHandlers()
return mappings.streamResources().map(MappedResource::getResource).toList();
}

/**
* Add a mapping of a {@link PathSpec} to a {@link Handler}.
*
* @param pathSpec the PathSpec to match against incoming Requests.
* @param handler the handler for requests that match the incoming PathSpec.
*/
public void addMapping(PathSpec pathSpec, Handler handler)
{
Objects.requireNonNull(pathSpec, "PathSpec cannot be null");
Expand Down Expand Up @@ -111,11 +138,120 @@ public boolean handle(Request request, Response response, Callback callback) thr
return false;
}
Handler handler = matchedResource.getResource();
PathSpec pathSpec = matchedResource.getPathSpec();
if (LOG.isDebugEnabled())
LOG.debug("Matched {} to {} -> {}", pathInContext, matchedResource.getPathSpec(), handler);
boolean handled = handler.handle(request, response, callback);

Request pathSpecRequest = newPathSpecRequest(request, pathSpec);
boolean handled = handler.handle(pathSpecRequest, response, callback);
if (LOG.isDebugEnabled())
LOG.debug("Handled {} {} by {}", handled, pathInContext, handler);
return handled;
}

protected Request newPathSpecRequest(Request request, PathSpec pathSpec)
{
return new PathSpecRequest(request, pathSpec);
}

/**
* <p>
* A custom {@link Request.Wrapper} that provides a {@link Context} based on the results
* of a {@link PathSpec}.
* </p>
*
* <p>
* Also provides the {@link PathSpec} used for this Request in the Attribute with
* the name {@link PathMappingsHandler#PATHSPEC_ATTR}
* </p>
*/
public static class PathSpecRequest extends Request.Wrapper
{
private final PathSpec pathSpec;
private final Context context;
private final MatchedPath matchedPath;

public PathSpecRequest(Request request, PathSpec pathSpec)
{
super(request);
this.pathSpec = pathSpec;
matchedPath = pathSpec.matched(request.getHttpURI().getCanonicalPath());
setAttribute(PathSpec.class.getName(), this.pathSpec);
this.context = new Context.Wrapper(request.getContext())
{
@Override
public String getContextPath()
{
// Start with wrapper context path, and add onto it.
String contextPath = getWrapped().getContext().getContextPath();

if (pathSpec instanceof ServletPathSpec servletPathSpec)
{
return appendContextPath(contextPath, servletPathSpec.getPrefix());
}
else
{
return appendContextPath(contextPath, matchedPath.getPathMatch());
}
}

private String appendContextPath(String contextPath1, String contextPath2)
{
if (StringUtil.isBlank(contextPath2))
return contextPath1;

if (contextPath2.charAt(0) != '/')
{
if (contextPath1.endsWith("/"))
return contextPath1 + contextPath2;
else
return contextPath1 + "/" + contextPath2;
}
else
{
if (contextPath1.equals("/"))
return contextPath2;
if (contextPath1.endsWith("/"))
return contextPath1.substring(contextPath1.length() - 1) + contextPath2;
else
return contextPath1 + contextPath2;
}
}

@Override
public String getPathInContext(String canonicallyEncodedPath)
{
if (pathSpec instanceof ServletPathSpec)
{
return Context.getPathInContext(getContextPath(), canonicallyEncodedPath);
}

String pathInfo = matchedPath.getPathInfo();
return pathInfo == null ? "" : pathInfo;
}
};
}

@Override
public Object getAttribute(String name)
{
if (name.equals(PATHSPEC_ATTR))
return pathSpec;
return super.getAttribute(name);
}

@Override
public Set<String> getAttributeNameSet()
{
Set<String> names = new HashSet<>(super.getAttributeNameSet());
names.add(PATHSPEC_ATTR);
return names;
}

@Override
public Context getContext()
{
return context;
}
}
}
Loading

0 comments on commit 730f063

Please sign in to comment.