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 - Introduce PathMappingsHandler #8748

Merged
merged 9 commits into from
Oct 25, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
Expand Down Expand Up @@ -88,6 +89,11 @@ public void reset()
_suffixMap.clear();
}

public Stream<MappedResource<E>> streamResources()
{
return _mappings.stream();
}

public void removeIf(Predicate<MappedResource<E>> predicate)
{
_mappings.removeIf(predicate);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.server.handler;

import java.io.IOException;
import java.util.List;
import java.util.function.Supplier;

import org.eclipse.jetty.http.pathmap.MappedResource;
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.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A Handler that delegates to other handlers through a configured {@link PathMappings}.
*/

public class PathMappingsHandler extends Handler.AbstractContainer
{
private static final Logger LOG = LoggerFactory.getLogger(PathMappingsHandler.class);

private final PathMappings<Handler> mappings = new PathMappings<>();
joakime marked this conversation as resolved.
Show resolved Hide resolved

@Override
public void addHandler(Handler handler)
{
throw new UnsupportedOperationException("Arbitrary addHandler() not supported, use addMapping() instead");
}

gregw marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void addHandler(Supplier<Handler> supplier)
{
throw new UnsupportedOperationException("Arbitrary addHandler() not supported, use addMapping() instead");
}

@Override
public List<Handler> getHandlers()
{
return mappings.streamResources().map(MappedResource::getResource).toList();
}

public void addMapping(PathSpec pathSpec, Handler handler)
{
if (isStarted())
throw new IllegalStateException("Cannot add mapping: " + this);

// check that self isn't present
if (handler == this || handler instanceof Handler.Container container && container.getDescendants().contains(this))
throw new IllegalStateException("Unable to addHandler of self: " + handler);

// check existing mappings
for (MappedResource<Handler> entry : mappings)
{
Handler entryHandler = entry.getResource();

if (entryHandler == this ||
entryHandler == handler ||
(entryHandler instanceof Handler.Container container && container.getDescendants().contains(this)))
throw new IllegalStateException("addMapping loop detected: " + handler);
}

mappings.put(pathSpec, handler);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this return an old handler that was replaced? If so, then that needs to be removed as a bean and probably returned from this method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's no replacement, the mappings are a Set or Index.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened Issue #8764 to address this later

addBean(handler);
}

@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this, mappings);
}

@Override
public Request.Processor handle(Request request) throws Exception
{
String pathInContext = request.getPathInContext();
MatchedResource<Handler> matchedResource = mappings.getMatched(pathInContext);
if (matchedResource == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No match on pathInContext of {}", pathInContext);
return null;
}
if (LOG.isDebugEnabled())
LOG.debug("Matched pathInContext of {} to {} -> {}", pathInContext, matchedResource.getPathSpec(), matchedResource.getResource());
return matchedResource.getResource().handle(request);
joakime marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ContextHandlerTest
Expand Down Expand Up @@ -586,6 +587,31 @@ public void process(Request request, Response response, Callback callback)
assertThat(result.get(), equalTo("OK"));
}

@Test
public void testSetHandlerLoopSelf()
{
ContextHandler contextHandlerA = new ContextHandler();
assertThrows(IllegalStateException.class, () -> contextHandlerA.setHandler(contextHandlerA));
}

@Test
public void testSetHandlerLoopDeepWrapper()
{
ContextHandler contextHandlerA = new ContextHandler();
Handler.Wrapper handlerWrapper = new Handler.Wrapper();
contextHandlerA.setHandler(handlerWrapper);
assertThrows(IllegalStateException.class, () -> handlerWrapper.setHandler(contextHandlerA));
}

@Test
public void testAddHandlerLoopDeep()
{
ContextHandler contextHandlerA = new ContextHandler();
Handler.Collection handlerCollection = new Handler.Collection();
contextHandlerA.setHandler(handlerCollection);
assertThrows(IllegalStateException.class, () -> handlerCollection.addHandler(contextHandlerA));
}

private static class ScopeListener implements ContextHandler.ContextScopeListener
{
private static final Request NULL = new Request.Wrapper(null);
Expand Down
Loading