org.eclipse.jetty.server.handler.ConditionalHandler Maven / Gradle / Ivy
Show all versions of jetty-server Show documentation
//
// ========================================================================
// Copyright (c) 1995 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.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.PathSpecSet;
import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.InetAddressPattern;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link Handler.Wrapper} that conditionally handles a {@link Request}.
* The conditions are implemented by {@link IncludeExclude}s of:
*
* - A HTTP method name, which can be efficiently matched
* - A {@link PathSpec} or string representation, which can be efficiently matched.
* - An arbitrary {@link Predicate} taking the {@link Request}, which is matched in a linear test of all predicates.
*
*
* If the conditions are met, the abstract {@link #onConditionsMet(Request, Response, Callback)} method will be invoked,
* otherwise the {@link #onConditionsNotMet(Request, Response, Callback)} method will be invoked. Implementations may call
* the {@link #nextHandler(Request, Response, Callback)} method to call the wrapped handler.
*
* A typical usage is to extend the {@link Abstract} sub class and provide an implementation of
* {@link #onConditionsMet(Request, Response, Callback)} and {@link #onConditionsNotMet(Request, Response, Callback)}:
* {@code
* public class MyOptionalHandler extends ConditionalHandler.Abstract
* {
* @Override
* public boolean onConditionsMet(Request request, Response response, Callback callback)
* {
* response.getHeaders().add("Test", "My Optional Handling");
* return nextHandle(request, response, callback);
* }
*
* @Override
* public boolean onConditionsNoMet(Request request, Response response, Callback callback)
* {
* return false;
* }
* }
* }
*
* If the conditions added to {@code MyOptionalHandler} are met, then the {@link #onConditionsMet(Request, Response, Callback)}
* method is called and a response header added before invoking {@link #nextHandler(Request, Response, Callback)}, otherwise
* the {@link #onConditionsNotMet(Request, Response, Callback)} is called, which returns false to indicate no more handling.
*
* Alternatively, one of the concrete subclasses may be used. These implementations conditionally provide a specific
* action in their {@link #onConditionsMet(Request, Response, Callback)} methods:
*
* - {@link DontHandle} - If the conditions are met, terminate further handling by returning {@code false}
* - {@link Reject} - If the conditions are met, reject the request with a {@link HttpStatus#FORBIDDEN_403} (or other status code) response.
* - {@link SkipNext} - If the conditions are met, then the {@link #getHandler() next handler} is skipped and the
* {@link Singleton#getHandler() following hander} invoked instead.
*
* Otherwise, if their conditions are not met, these subclasses are all extension of the abstract {@link ElseNext} subclass,
* that implements {@link #onConditionsNotMet(Request, Response, Callback)} to call {@link #nextHandler(Request, Response, Callback)}.
* Thus their specific behaviour is not applied and the handling continues with the next handler.
*
* These concrete handlers are ideal for retrofitting conditional behavior. For example, if an application handler was
* found to not correctly handle the {@code OPTIONS} method for the path "/secret/*", it could be protected as follows:
* {@code
* Server server = new Server();
* ApplicationHandler application = new ApplicationHandler();
* server.setHandler(application);
*
* ConditionalHandler reject = new ConditionalHandler.Reject(403); // or DontHandle
* reject.includeMethod("OPTIONS");
* reject.includePath("/secret/*");
* server.insertHandler(reject);
* }
*
* Another example, in an application comprised of several handlers, one of which is a wrapping handler whose behavior
* needs to be skipped for "POST" requests, then it could be achieved as follows:
* {@code
* Server server = new Server();
* ApplicationWrappingHandler wrappingHandler = new ApplicationWrappingHandler();
* ApplicationHandler applicationHandler = new ApplicationHandler();
* server.setHandler(wrappingHandler);
* filter.setHandler(applicationHandler);
*
* ConditionalHandler skipNext = new ConditionalHandler.SkipNext();
* skipNext.includeMethod("POST");
* skipNext.setHandler(wrappingHandler);
* server.setHandler(skipNext);
* }
* Note that a better solution, if possible, would be for the {@code ApplicationFilterHandler} and/or
* {@code ApplicationHandler} handlers to extend {@code ConditionalHandler}.
*/
public abstract class ConditionalHandler extends Handler.Wrapper
{
private static final Logger LOG = LoggerFactory.getLogger(ConditionalHandler.class);
private final IncludeExclude _methods = new IncludeExclude<>();
private final IncludeExclude _pathSpecs = new IncludeExclude<>(PathSpecSet.class);
private final IncludeExcludeSet, Request> _predicates = new IncludeExcludeSet<>(PredicateSet.class);
private Predicate _handlePredicate;
private ConditionalHandler()
{
this(false, null);
}
private ConditionalHandler(Handler nextHandler)
{
this(false, nextHandler);
}
private ConditionalHandler(boolean dynamic, Handler nextHandler)
{
super(dynamic, nextHandler);
}
/**
* Clear all inclusions and exclusions.
*/
public void clear()
{
if (isStarted())
throw new IllegalStateException(getState());
_methods.clear();
_pathSpecs.clear();
_predicates.clear();
}
IncludeExclude getMethods()
{
// Used only for testing
return _methods;
}
IncludeExclude getPathSpecs()
{
// Used only for testing
return _pathSpecs;
}
IncludeExcludeSet, Request> getPredicates()
{
// Used only for testing
return _predicates;
}
/**
* Include {@link Request#getMethod() method}s in the conditions to be met
* @param methods The exact case-sensitive method name
*/
public void includeMethod(String... methods)
{
if (isStarted())
throw new IllegalStateException(getState());
_methods.include(methods);
}
/**
* Exclude {@link Request#getMethod() method}s in the conditions to be met
* @param methods The exact case-sensitive method name
*/
public void excludeMethod(String... methods)
{
if (isStarted())
throw new IllegalStateException(getState());
_methods.exclude(methods);
}
/**
* Include {@link PathSpec}s in the conditions to be met
* @param paths The {@link PathSpec}s that are tested against the {@link Request#getPathInContext(Request) pathInContext}.
*/
public void include(PathSpec... paths)
{
if (isStarted())
throw new IllegalStateException(getState());
for (PathSpec p : paths)
((PathSpecSet)_pathSpecs.getIncluded()).add(p);
}
/**
* Exclude {@link PathSpec}s in the conditions to be met
* @param paths The {@link PathSpec}s that are tested against the {@link Request#getPathInContext(Request) pathInContext}.
*/
public void exclude(PathSpec... paths)
{
if (isStarted())
throw new IllegalStateException(getState());
for (PathSpec p : paths)
((PathSpecSet)_pathSpecs.getExcluded()).add(p);
}
/**
* Include {@link PathSpec}s in the conditions to be met
* @param paths String representations of {@link PathSpec}s that are
* tested against the {@link Request#getPathInContext(Request) pathInContext}.
*/
public void includePath(String... paths)
{
if (isStarted())
throw new IllegalStateException(getState());
_pathSpecs.include(paths);
}
/**
* Exclude {@link PathSpec} in the conditions to be met
* @param paths String representations of {@link PathSpec}s that are
* tested against the {@link Request#getPathInContext(Request) pathInContext}.
*/
public void excludePath(String... paths)
{
if (isStarted())
throw new IllegalStateException(getState());
_pathSpecs.exclude(paths);
}
/**
* Include {@link InetAddressPattern}s in the conditions to be met
* @param patterns {@link InetAddressPattern}s that are
* tested against the {@link ConnectionMetaData#getRemoteSocketAddress() getRemoteSocketAddress()} of
* {@link Request#getConnectionMetaData()}.
*/
public void include(InetAddressPattern... patterns)
{
if (isStarted())
throw new IllegalStateException(getState());
for (InetAddressPattern p : patterns)
_predicates.include(new InetAddressPatternPredicate(p));
}
/**
* Include {@link InetAddressPattern}s in the conditions to be met
* @param patterns String representations of {@link InetAddressPattern}s that are
* tested against the {@link ConnectionMetaData#getRemoteSocketAddress() getRemoteSocketAddress()} of
* {@link Request#getConnectionMetaData()}.
*/
public void includeInetAddressPattern(String... patterns)
{
for (String p : patterns)
include(InetAddressPattern.from(p));
}
/**
* Exclude {@link InetAddressPattern}s in the conditions to be met
* @param patterns {@link InetAddressPattern}s that are
* tested against the {@link ConnectionMetaData#getRemoteSocketAddress() getRemoteSocketAddress()} of
* {@link Request#getConnectionMetaData()}.
*/
public void exclude(InetAddressPattern... patterns)
{
if (isStarted())
throw new IllegalStateException(getState());
for (InetAddressPattern p : patterns)
_predicates.exclude(new InetAddressPatternPredicate(p));
}
/**
* Exclude {@link InetAddressPattern} in the conditions to be met
* @param patterns String representations of {@link InetAddressPattern}s that are
* tested against the {@link ConnectionMetaData#getRemoteSocketAddress() getRemoteSocketAddress()} of
* {@link Request#getConnectionMetaData()}.
*/
public void excludeInetAddressPattern(String... patterns)
{
for (String p : patterns)
exclude(InetAddressPattern.from(p));
}
/**
* {@link IncludeExclude#include(Object) Include} arbitrary {@link Predicate}s in the conditions.
* @param predicates {@link Predicate}s that are tested against the {@link Request}.
* This method is optimized so that a passed {@link MethodPredicate} or {@link PathSpecPredicate} is
* converted to a more efficient {@link #includeMethod(String...)} or {@link #include(PathSpec...)} respectively.
*/
@SafeVarargs
public final void include(Predicate... predicates)
{
if (isStarted())
throw new IllegalStateException(getState());
for (Predicate p : predicates)
{
if (p instanceof MethodPredicate methodPredicate)
includeMethod(methodPredicate._method);
else if (p instanceof PathSpecPredicate pathSpecPredicate)
include(pathSpecPredicate._pathSpec);
else
_predicates.include(p);
}
}
/**
* {@link IncludeExclude#exclude(Object) Exclude} arbitrary {@link Predicate}s in the conditions.
* @param predicates {@link Predicate}s that are tested against the {@link Request}.
* This method is optimized so that a passed {@link MethodPredicate} or {@link PathSpecPredicate} is
* converted to a more efficient {@link #excludeMethod(String...)} or {@link #exclude(PathSpec...)} respectively.
*/
@SafeVarargs
public final void exclude(Predicate... predicates)
{
if (isStarted())
throw new IllegalStateException(getState());
for (Predicate p : predicates)
{
if (p instanceof MethodPredicate methodPredicate)
excludeMethod(methodPredicate._method);
else if (p instanceof PathSpecPredicate pathSpecPredicate)
exclude(pathSpecPredicate._pathSpec);
else
_predicates.exclude(p);
}
}
private boolean testMethods(Request request)
{
return _methods.test(request.getMethod());
}
private boolean testPathSpecs(Request request)
{
return _pathSpecs.test(Request.getPathInContext(request));
}
private boolean testPredicates(Request request)
{
return _predicates.test(request);
}
@Override
protected void doStart() throws Exception
{
_handlePredicate = TypeUtil.truePredicate();
if (!_methods.isEmpty())
_handlePredicate = _handlePredicate.and(this::testMethods);
if (!_pathSpecs.isEmpty())
_handlePredicate = _handlePredicate.and(this::testPathSpecs);
if (!_predicates.isEmpty())
_handlePredicate = _handlePredicate.and(this::testPredicates);
super.doStart();
}
public final boolean handle(Request request, Response response, Callback callback) throws Exception
{
if (_handlePredicate.test(request))
return onConditionsMet(request, response, callback);
return onConditionsNotMet(request, response, callback);
}
/**
* Handle a request that has met the conditions.
* Typically, the implementation will provide optional handling and then call the
* {@link #nextHandler(Request, Response, Callback)} method to continue handling.
* @param request The request to handle
* @param response The response to generate
* @param callback The callback for completion
* @return True if this handler will complete the callback
* @throws Exception If there is a problem handling
* @see Handler#handle(Request, Response, Callback)
*/
protected abstract boolean onConditionsMet(Request request, Response response, Callback callback) throws Exception;
/**
* This method is called when the request has not met the conditions and is not to
* be handled by this handler.
* Implementations may return false; send an error response; or handle the request differently.
* @param request The request to handle
* @param response The response to generate
* @param callback The callback for completion
* @return True if this handler will complete the callback
* @throws Exception If there is a problem handling
* @see Handler#handle(Request, Response, Callback)
*/
protected abstract boolean onConditionsNotMet(Request request, Response response, Callback callback) throws Exception;
/**
* Handle a request by invoking the {@link #handle(Request, Response, Callback)} method of the
* {@link #getHandler() next Handler}.
* @param request The request to handle
* @param response The response to generate
* @param callback The callback for completion
* @return True if this handler will complete the callback
* @throws Exception If there is a problem handling
* @see Handler#handle(Request, Response, Callback)
*/
protected boolean nextHandler(Request request, Response response, Callback callback) throws Exception
{
return super.handle(request, response, callback);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent,
new DumpableCollection("included methods", _methods.getIncluded()),
new DumpableCollection("included paths", _pathSpecs.getIncluded()),
new DumpableCollection("included predicates", _predicates.getIncluded()),
new DumpableCollection("excluded methods", _methods.getExcluded()),
new DumpableCollection("excluded paths", _pathSpecs.getExcluded()),
new DumpableCollection("excluded predicates", _predicates.getExcluded())
);
}
/**
* Create a {@link Predicate} over {@link Request} built from the {@link Predicate#and(Predicate) and} of one or more of:
* - {@link ConnectorPredicate}
* - {@link InetAddressPatternPredicate}
* - {@link MethodPredicate}
* - {@link PathSpecPredicate}
*
* @param connectorName The connector name or {@code null}
* @param inetAddressPattern An {@link InetAddressPattern} string or {@code null}
* @param method A {@link org.eclipse.jetty.http.HttpMethod} name or {@code null}
* @param pathSpec A {@link PathSpec} string or {@code null}
* @return the combined {@link Predicate} over {@link Request}
*/
public static Predicate from(String connectorName, String inetAddressPattern, String method, String pathSpec)
{
return from(connectorName, InetAddressPattern.from(inetAddressPattern), method, pathSpec == null ? null : PathSpec.from(pathSpec));
}
/**
* Create a {@link Predicate} over {@link Request} built from the {@link Predicate#and(Predicate) and} of one or more of:
* - {@link TypeUtil#truePredicate()}
* - {@link ConnectorPredicate}
* - {@link InetAddressPatternPredicate}
* - {@link MethodPredicate}
* - {@link PathSpecPredicate}
*
* @param connectorName The connector name or {@code null}
* @param inetAddressPattern An {@link InetAddressPattern} or {@code null}
* @param method A {@link org.eclipse.jetty.http.HttpMethod} name or {@code null}
* @param pathSpec A {@link PathSpec} or {@code null}
* @return the combined {@link Predicate} over {@link Request}
*/
public static Predicate from(String connectorName, InetAddressPattern inetAddressPattern, String method, PathSpec pathSpec)
{
Predicate predicate = TypeUtil.truePredicate();
if (connectorName != null)
predicate = predicate.and(new ConnectorPredicate(connectorName));
if (inetAddressPattern != null)
predicate = predicate.and(new InetAddressPatternPredicate(inetAddressPattern));
if (method != null)
predicate = predicate.and(new MethodPredicate(method));
if (pathSpec != null)
predicate = predicate.and(new PathSpecPredicate(pathSpec));
return predicate;
}
/**
* A Set of {@link Predicate} over {@link Request} optimized for use by {@link IncludeExclude}.
*/
public static class PredicateSet extends AbstractSet> implements Set>, Predicate
{
private final ArrayList> _predicates = new ArrayList<>();
@Override
public boolean add(Predicate predicate)
{
if (_predicates.contains(predicate))
return false;
return _predicates.add(predicate);
}
@Override
public boolean remove(Object o)
{
return _predicates.remove(o);
}
@Override
public Iterator> iterator()
{
return _predicates.iterator();
}
@Override
public int size()
{
return _predicates.size();
}
@Override
public boolean test(Request request)
{
if (request == null)
return false;
for (Predicate predicate : _predicates)
{
if (predicate.test(request))
return true;
}
return false;
}
}
/**
* A {@link Predicate} over {@link Request} that tests the {@link Connector#getName() name} of the
* {@link ConnectionMetaData#getConnector() connector} obtained from {@link Request#getConnectionMetaData()}
*/
public static class ConnectorPredicate implements Predicate
{
private final String _connector;
public ConnectorPredicate(String connector)
{
this._connector = Objects.requireNonNull(connector);
}
@Override
public boolean test(Request request)
{
return _connector.equals(request.getConnectionMetaData().getConnector().getName());
}
@Override
public int hashCode()
{
return _connector.hashCode();
}
@Override
public boolean equals(Object obj)
{
return obj instanceof ConnectorPredicate other && _connector.equals(other._connector);
}
@Override
public String toString()
{
return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _connector);
}
}
/**
* A {@link Predicate} over {@link Request} that tests an {@link InetAddressPattern}
* against the {@link ConnectionMetaData#getRemoteSocketAddress() getRemoteSocketAddress()} of
* {@link Request#getConnectionMetaData()}.
*/
public static class InetAddressPatternPredicate implements Predicate
{
public static InetAddress getInetAddress(SocketAddress socketAddress)
{
if (socketAddress instanceof InetSocketAddress inetSocketAddress)
{
if (inetSocketAddress.isUnresolved())
{
try
{
return InetAddress.getByName(inetSocketAddress.getHostString());
}
catch (UnknownHostException e)
{
if (LOG.isTraceEnabled())
LOG.trace("ignored", e);
return null;
}
}
return inetSocketAddress.getAddress();
}
return null;
}
private final InetAddressPattern _pattern;
public InetAddressPatternPredicate(InetAddressPattern pattern)
{
_pattern = pattern;
}
@Override
public boolean test(Request request)
{
return _pattern.test(getInetAddress(request.getConnectionMetaData().getRemoteSocketAddress()));
}
@Override
public int hashCode()
{
return _pattern.hashCode();
}
@Override
public boolean equals(Object other)
{
return other instanceof InetAddressPatternPredicate inetAddressPatternPredicate && _pattern.equals(inetAddressPatternPredicate._pattern);
}
@Override
public String toString()
{
return "%s@%x{%s}".formatted(getClass().getSimpleName(), hashCode(), _pattern);
}
}
/**
* A {@link Predicate} over {@link Request} that tests {@link Request#getMethod() method} name.
* Using predicates in less efficient than using {@link ConditionalHandler#includeMethod(String...)}
* and {@link ConditionalHandler#excludeMethod(String...)}, so this predicate should only be used
* if necessary to combine with other predicates.
*/
public static class MethodPredicate implements Predicate
{
private final String _method;
public MethodPredicate(String method)
{
_method = Objects.requireNonNull(method);
}
@Override
public boolean test(Request request)
{
return _method.equals(request.getMethod());
}
@Override
public int hashCode()
{
return _method.hashCode();
}
@Override
public boolean equals(Object obj)
{
return obj instanceof MethodPredicate other && _method.equals(other._method);
}
@Override
public String toString()
{
return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _method);
}
}
/**
* A {@link Predicate} over {@link Request} that tests a {@link PathSpec} against
* the {@link Request#getPathInContext(Request) pathInContext}.
* Using predicates in less efficient than using {@link ConditionalHandler#include(PathSpec...)}
* and {@link ConditionalHandler#exclude(PathSpec...)}, so this predicate should only be used
* if necessary to combine with other predicates.
*/
public static class PathSpecPredicate implements Predicate
{
private final PathSpec _pathSpec;
public PathSpecPredicate(PathSpec pathSpec)
{
_pathSpec = Objects.requireNonNull(pathSpec);
}
@Override
public boolean test(Request request)
{
return _pathSpec.matches(Request.getPathInContext(request));
}
@Override
public int hashCode()
{
return _pathSpec.hashCode();
}
@Override
public boolean equals(Object obj)
{
return obj instanceof PathSpecPredicate other && _pathSpec.equals(other._pathSpec);
}
@Override
public String toString()
{
return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _pathSpec);
}
}
/**
* An Abstract {@link ConditionalHandler}. Implementations must provide
* both {@link #onConditionsMet(Request, Response, Callback)} and
* {@link #onConditionsNotMet(Request, Response, Callback)} implementations.
*/
public abstract static class Abstract extends ConditionalHandler
{
protected Abstract()
{
}
protected Abstract(Handler nextHandler)
{
super(nextHandler);
}
protected Abstract(boolean dynamic, Handler nextHandler)
{
super(dynamic, nextHandler);
}
}
/**
* An abstract implementation of {@link ConditionalHandler} that, if conditions are not met, will call
* the {@link #nextHandler(Request, Response, Callback)} from {@link #onConditionsNotMet(Request, Response, Callback)}.
* Implementations must provide an {@link #onConditionsMet(Request, Response, Callback)} to provide the
* handling for when conditions are met.
*/
public abstract static class ElseNext extends ConditionalHandler
{
public ElseNext()
{
this(null);
}
public ElseNext(Handler handler)
{
super(handler);
}
@Override
protected boolean onConditionsNotMet(Request request, Response response, Callback callback) throws Exception
{
return nextHandler(request, response, callback);
}
}
/**
* An implementation of {@link ConditionalHandler} that, if conditions are met, will not do any further
* handling by returning {@code false} from {@link #onConditionsMet(Request, Response, Callback)}.
* Otherwise, the {@link #nextHandler(Request, Response, Callback) next handler} will be invoked.
*/
public static class DontHandle extends ConditionalHandler.ElseNext
{
public DontHandle()
{
super();
}
public DontHandle(Handler handler)
{
super(handler);
}
@Override
protected boolean onConditionsMet(Request request, Response response, Callback callback) throws Exception
{
return false;
}
}
/**
* An implementation of {@link ConditionalHandler} that, if conditions are met, will reject
* the request by sending a response (by default a {@link HttpStatus#FORBIDDEN_403}).
* Otherwise, the {@link #nextHandler(Request, Response, Callback) next handler} will be invoked.
*/
public static class Reject extends ConditionalHandler.ElseNext
{
private final int _status;
public Reject()
{
this(null, HttpStatus.FORBIDDEN_403);
}
public Reject(int status)
{
this(null, status);
}
public Reject(Handler handler)
{
this(handler, HttpStatus.FORBIDDEN_403);
}
public Reject(Handler handler, int status)
{
super(handler);
if (status < 200 || status > 999)
throw new IllegalArgumentException("bad status");
_status = status;
}
@Override
protected boolean onConditionsMet(Request request, Response response, Callback callback) throws Exception
{
Response.writeError(request, response, callback, _status);
return true;
}
}
/**
* An implementation of {@link ConditionalHandler} that, if conditions are met, will skip the next {@link Handler} by
* invoking its {@link Singleton#getHandler() next Handler}.
* Otherwise, the {@link #nextHandler(Request, Response, Callback) next handler} will be invoked.
*/
public static class SkipNext extends ConditionalHandler.ElseNext
{
public SkipNext()
{
super();
}
public SkipNext(Handler handler)
{
super(handler);
}
@Override
protected boolean onConditionsMet(Request request, Response response, Callback callback) throws Exception
{
if (!(getHandler() instanceof Singleton nextHandler))
return false;
Handler nextNext = nextHandler.getHandler();
return nextNext != null && nextNext.handle(request, response, callback);
}
}
}