All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.Handler Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// 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;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

A Jetty component that handles HTTP requests, of any version (HTTP/1.1, HTTP/2 or HTTP/3). * A {@code Handler} is a {@link Request.Handler} with the addition of {@link LifeCycle} * behaviours, plus variants that allow organizing {@code Handler}s as a tree structure.

*

{@code Handler}s may wrap the {@link Request}, {@link Response} and/or {@link Callback} and * then forward the wrapped instances to their children, so that they see a modified request; * and/or to intercept the read of the request content; and/or intercept the generation of the * response; and/or to intercept the completion of the callback.

*

A {@code Handler} is an {@link Invocable} and implementations must respect * the {@link InvocationType} they declare within calls to * {@link #handle(Request, Response, Callback)}.

*

A minimal tree structure could be:

*
{@code
 * Server
 * `- YourCustomHandler
 * }
*

A more sophisticated tree structure:

*
{@code
 * Server
 * `- GzipHandler
 *    `- ContextHandlerCollection
 *       +- ContextHandler (contextPath="/user")
 *       |  `- YourUserHandler
 *       |- ContextHandler (contextPath="/admin")
 *       |  `- YourAdminHandler
 *       `- DefaultHandler
 * }
*

A simple {@code Handler} implementation could be:

*
{@code
 * class SimpleHandler extends Handler.Abstract.NonBlocking
 * {
 *     @Override
 *     public boolean handle(Request request, Response response, Callback callback)
 *     {
 *         // Implicitly sends a 200 OK response with no content.
 *         callback.succeeded();
 *         return true;
 *     }
 * }
 * }
* *

A more sophisticated example of a {@code Handler} that decides whether to handle * requests based on their URI path:

*
{@code
 * class YourHelloHandler extends Handler.Abstract.NonBlocking
 * {
 *     @Override
 *     public boolean handle(Request request, Response response, Callback callback)
 *     {
 *         if (request.getHttpURI().getPath().startsWith("/yourPath"))
 *         {
 *             // The request is for this Handler
 *             response.setStatus(200);
 *             // The callback is completed when the write is completed.
 *             response.write(true, UTF_8.encode("hello"), callback);
 *             return true;
 *         }
 *         return false;
 *     }
 * }
 * }
*

An example of a {@code Handler} that decides whether to pass the request to * a child:

*
{@code
 * class ConditionalHandler extends Handler.Wrapper
 * {
 *     @Override
 *     public boolean handle(Request request, Response response, Callback callback)
 *     {
 *         if (request.getHttpURI().getPath().startsWith("/yourPath")
 *             return super.handle(request, response, callback);
 *         if (request.getHttpURI().getPath().startsWith("/wrong"))
 *         {
 *             Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400);
 *             return true;
 *         }
 *         return false;
 *     }
 * }
 * }
* * @see Request.Handler */ @ManagedObject public interface Handler extends LifeCycle, Destroyable, Request.Handler { /** * @return the {@code Server} associated with this {@code Handler} */ @ManagedAttribute(value = "The Server instance associated to this Handler", readonly = true) Server getServer(); /** * Set the {@code Server} to associate to this {@code Handler}. * @param server the {@code Server} to associate to this {@code Handler} */ void setServer(Server server); /** *

A {@code Handler} that contains one or more other {@code Handler}s.

* * @see Singleton * @see Collection */ @ManagedObject interface Container extends Handler { /** * @return an immutable collection of {@code Handler}s directly contained by this {@code Handler}. */ @ManagedAttribute(value = "The direct children Handlers of this Container", readonly = true) List getHandlers(); /** * @return an immutable collection of {@code Handler}s descendants of this {@code Handler}. */ default List getDescendants() { return getDescendants(Handler.class); } /** * @param type the class of the descendant {@code Handler} * @param the type of the descendant {@code Handler} * @return an immutable collection of {@code Handler}s of the given type, descendants of this {@code Handler} */ default List getDescendants(Class type) { List handlers = new ArrayList<>(); for (Handler h : getHandlers()) { if (type.isInstance(h)) { @SuppressWarnings("unchecked") T t = (T)h; handlers.add(t); } if (h instanceof Container c) handlers.addAll(c.getDescendants(type)); } return handlers; } /** * @param type the class of the descendant {@code Handler} * @param the type of the descendant{@code Handler} * @return the first {@code Handler} of the given type, descendants of this {@code Handler}, * or null if no such {@code Handler} exist */ default T getDescendant(Class type) { for (Handler h : getHandlers()) { if (type.isInstance(h)) { @SuppressWarnings("unchecked") T t = (T)h; return t; } if (h instanceof Container c) { T t = c.getDescendant(type); if (t != null) return t; } } return null; } /** * @param handler the descendant {@code Handler} * @param type the class of the container {@code Handler} * @param the type of the container {@code Handler} * @return the {@code Handler.Container} descendant of this {@code Handler} * that is the ancestor of the given {@code Handler} */ default T getContainer(Handler handler, Class type) { if (handler == null) return null; for (T container : getDescendants(type)) { if (container.getDescendants(handler.getClass()).contains(handler)) return container; } return null; } } /** *

A {@link Handler.Container} that can contain multiple other {@link Handler}s.

* * @see Sequence for an implementation of {@link Collection}. * @see Singleton */ interface Collection extends Container { /** *

Adds the given {@code Handler} to this collection of {@code Handler}s.

* * @param handler the {@code Handler} to add */ default void addHandler(Handler handler) { List list = new ArrayList<>(getHandlers()); list.add(handler); setHandlers(list); } /** *

Removes the given {@code Handler} from this collection of {@code Handler}s.

* * @param handler the {@code Handler} to remove * @return whether the {@code Handler} was removed */ default boolean removeHandler(Handler handler) { List list = new ArrayList<>(getHandlers()); boolean removed = list.remove(handler); if (removed) setHandlers(list); return removed; } /** *

Adds the {@code Handler} supplied by the given {@code Supplier} * to this collection of {@code Handler}s.

* * @param supplier the {@code Handler} supplier */ default void addHandler(Supplier supplier) { addHandler(supplier.get()); } /** *

Sets the given {@code Handler}s as children of this collection of {@code Handler}s.

*

The list is copied and any subsequent modification to the list does not have any * effect on this {@code Handler}.

*

Any existing children {@code Handler} is removed.

* * @param handlers the {@code Handler} to set as children */ void setHandlers(List handlers); /** *

Similar to {@link #setHandlers(List)}.

* * @param handlers the {@code Handler} to set as children */ default void setHandlers(Handler... handlers) { setHandlers(handlers == null || handlers.length == 0 ? List.of() : List.of(handlers)); } } /** *

A {@link Handler.Container} that can contain one single other {@code Handler}.

*

This is a "singleton" in the sense of {@link Collections#singleton(Object)} and not * in the sense of the singleton pattern of a single instance per JVM.

* * @see Wrapper for an implementation of {@link Singleton}. * @see Collection */ @ManagedObject interface Singleton extends Container { /** * @return the child {@code Handler} */ @ManagedAttribute(value = "The child Handler of this Container", readonly = true) Handler getHandler(); /** * @param handler The {@code Handler} to set as a child */ void setHandler(Handler handler); /** *

Sets the child {@code Handler} supplied by the given {@code Supplier}.

* * @param supplier the {@code Handler} supplier */ default void setHandler(Supplier supplier) { setHandler(supplier.get()); } @Override default List getHandlers() { Handler next = getHandler(); return (next == null) ? Collections.emptyList() : Collections.singletonList(next); } /** *

Inserts the given {@code Handler} (and possible chain of {@code Handler}s) * between this {@code Handler} and its current {@link #getHandler() child}. *

For example, if this {@code Handler} {@code A} has a child {@code B}, * inserting {@code Handler} {@code X} built as a chain {@code Handler}s * {@code X-Y-Z} results in the structure {@code A-X-Y-Z-B}.

* * @param handler the {@code Handler} to insert */ default void insertHandler(Singleton handler) { Singleton tail = handler.getTail(); if (tail.getHandler() != null) throw new IllegalArgumentException("bad tail of inserted wrapper chain"); tail.setHandler(getHandler()); setHandler(handler); } /** * @return the tail {@link Singleton} of a chain of {@link Singleton}s */ default Singleton getTail() { Singleton tail = this; while (tail.getHandler() instanceof Singleton wrapped) tail = wrapped; return tail; } /** *

Utility method to perform sanity checks before updating the given {@code Handler} to * the given {@code Singleton}, typically used in implementations of {@link #setHandler(Handler)}.

*

The sanity checks are:

*
    *
  • Check for the server start state and whether the invocation type is compatible
  • *
  • Check for {@code Handler} loops
  • *
  • Sets the {@code Server} on the {@code Handler}
  • *
  • Update the beans on the {@code Singleton} if it is a {@link ContainerLifeCycle}
  • *
* @param singleton the {@code Singleton} to set the {@code Handler} * @param handler the {@code Handler} to set * @see #checkHandler(Singleton, Handler) * @return The {@code Handler} to set */ static Handler updateHandler(Singleton singleton, Handler handler) { // check state checkHandler(singleton, handler); if (singleton instanceof org.eclipse.jetty.util.component.ContainerLifeCycle container) container.updateBean(singleton.getHandler(), handler); return handler; } /** *

Utility method to perform sanity checks on a {{@link Handler} to be added to * the given {@code Singleton}.

*

The sanity checks are:

*
    *
  • Check for the server start state and whether the invocation type is compatible
  • *
  • Check for {@code Handler} loops
  • *
  • Sets the {@code Server} on the {@code Handler}
  • *
  • Update the beans on the {@code Singleton} if it is a {@link ContainerLifeCycle}
  • *
* * @param singleton the {@code Singleton} to set the {@code Handler} * @param handler the {@code Handler} to set * @return The {@code Handler} to set */ static Handler checkHandler(Singleton singleton, Handler handler) { // check state Server server = singleton.getServer(); // If the collection is changed whilst started, then the risk is that if we switch from NON_BLOCKING to BLOCKING // whilst the execution strategy may have already dispatched the very last available thread, thinking it would // never block, only for it to lose the race and find a newly added BLOCKING handler. if (server != null && server.isStarted() && handler != null) { InvocationType serverInvocationType = server.getInvocationType(); if (serverInvocationType != Invocable.combine(serverInvocationType, handler.getInvocationType()) && serverInvocationType != InvocationType.BLOCKING) throw new IllegalArgumentException("Cannot change invocation type of started server"); } // Check for loops. if (handler == singleton || (handler instanceof Handler.Container container && container.getDescendants().contains(singleton))) throw new IllegalStateException("Handler loop"); if (handler != null && server != null) handler.setServer(server); return handler; } } /** *

An abstract implementation of {@link Handler} that is a {@link ContainerLifeCycle}.

*

The {@link InvocationType} is by default {@link InvocationType#BLOCKING} unless the * {@code NonBlocking} variant is used or a specific {@link InvocationType} is passed to * the constructor.

* * @see NonBlocking */ @ManagedObject abstract class Abstract extends ContainerLifeCycle implements Handler { private static final Logger LOG = LoggerFactory.getLogger(Abstract.class); private final InvocationType _invocationType; private Server _server; /** *

Creates a {@code Handler} with invocation type {@link InvocationType#BLOCKING}.

*/ public Abstract() { this(InvocationType.BLOCKING); } /** *

Creates a {@code Handler} with the given invocation type.

* * @param type the {@link InvocationType} of this {@code Handler} * @see AbstractContainer */ public Abstract(InvocationType type) { _invocationType = type; } @Override @ManagedAttribute(value = "The Server associated with this Handler", readonly = true) public Server getServer() { return _server; } @Override public void setServer(Server server) { if (_server == server) return; if (isStarted()) throw new IllegalStateException(getState()); _server = server; } @Override public InvocationType getInvocationType() { return _invocationType; } @Override protected void doStart() throws Exception { if (LOG.isDebugEnabled()) LOG.debug("starting {}", this); if (_server == null) LOG.warn("No Server set for {}", this); super.doStart(); } @Override protected void doStop() throws Exception { if (LOG.isDebugEnabled()) LOG.debug("stopping {}", this); super.doStop(); } @Override public void destroy() { if (isRunning()) throw new IllegalStateException(getState()); super.destroy(); } /** *

An abstract {@code Handler} with a {@link InvocationType#NON_BLOCKING} * invocation type.

*/ public abstract static class NonBlocking extends Abstract { public NonBlocking() { super(InvocationType.NON_BLOCKING); } } } /** *

A {@link Handler.Abstract} that implements {@link Handler.Container}.

*

An {@link AbstractContainer} may be dynamic, that is allow {@code Handler}s * to be added after it has been started.

*

If this {@link AbstractContainer} is dynamic, then its invocation type * is by default {@link InvocationType#BLOCKING}.

* * @see Abstract */ @ManagedObject abstract class AbstractContainer extends Abstract implements Container { private boolean _dynamic; /** *

Creates an instance that is dynamic.

*/ protected AbstractContainer() { this(true); } /** *

Creates an instance with the given dynamic argument.

* * @param dynamic whether this container is dynamic */ protected AbstractContainer(boolean dynamic) { _dynamic = dynamic; } /** * @return whether this container is dynamic */ @ManagedAttribute("Whether this Handler container is dynamic") public boolean isDynamic() { return _dynamic; } /** * @param dynamic whether this container is dynamic */ public void setDynamic(boolean dynamic) { if (isStarted()) throw new IllegalStateException(getState()); _dynamic = dynamic; } @Override public List getDescendants(Class type) { List list = new ArrayList<>(); expandHandler(this, list, type); return list; } @SuppressWarnings("unchecked") private void expandHandler(Handler handler, List list, Class type) { if (!(handler instanceof Container container)) return; for (Handler child : container.getHandlers()) { if (type == null || type.isInstance(child)) list.add((H)child); expandHandler(child, list, type); } } @Override public T getDescendant(Class type) { return findHandler(this, type); } @SuppressWarnings("unchecked") private H findHandler(Handler handler, Class type) { if (!(handler instanceof Container container)) return null; for (Handler child : container.getHandlers()) { if (type == null || type.isInstance(child)) return (H)child; H descendant = findHandler(child, type); if (descendant != null) return descendant; } return null; } @Override public void setServer(Server server) { super.setServer(server); for (Handler child : getHandlers()) { child.setServer(server); } } @Override public InvocationType getInvocationType() { // Dynamic is always BLOCKING, as a blocking handler can be added at any time. if (_dynamic) return InvocationType.BLOCKING; InvocationType invocationType = InvocationType.NON_BLOCKING; for (Handler child : getHandlers()) invocationType = Invocable.combine(invocationType, child.getInvocationType()); return invocationType; } @SuppressWarnings("unchecked") public static T findContainerOf(Handler.Container root, Class type, Handler handler) { if (root == null || handler == null) return null; List branches = (List)root.getDescendants(type); if (branches != null) { for (Handler.Container container : branches) { List candidates = (List)container.getDescendants(handler.getClass()); if (candidates != null) { for (Handler c : candidates) { if (c == handler) return (T)container; } } } } return null; } } /** *

An implementation of {@link Singleton}, which is a {@link Container} * that wraps one single other {@link Handler}.

*

A {@link Wrapper} may be dynamic, that is allow {@code Handler}s * to be set after it has been started.

*

If this {@link Wrapper} is dynamic, then its invocation type * is by default {@link InvocationType#BLOCKING}.

*/ class Wrapper extends AbstractContainer implements Singleton { private Handler _handler; /** *

Creates a wrapper with no wrapped {@code Handler}.

*/ public Wrapper() { this(null); } /** *

Creates a wrapper with no wrapped {@code Handler} with the given * {@code dynamic} parameter.

* * @param dynamic whether this container is dynamic */ public Wrapper(boolean dynamic) { this(dynamic, null); } /** *

Creates a non-dynamic wrapper of the given {@code Handler}.

* * @param handler the {@code Handler} to wrap */ public Wrapper(Handler handler) { this(false, handler); } /** *

Creates a wrapper with the given dynamic parameter wrapping the * given {@code Handler}.

* * @param dynamic whether this container is dynamic * @param handler the {@code Handler} to wrap */ public Wrapper(boolean dynamic, Handler handler) { super(dynamic); _handler = handler == null ? null : Singleton.checkHandler(this, handler); installBean(_handler); } @Override public Handler getHandler() { return _handler; } @Override public void setHandler(Handler handler) { if (!isDynamic() && isStarted()) throw new IllegalStateException(getState()); _handler = Singleton.updateHandler(this, handler); } @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { Handler next = getHandler(); return next != null && next.handle(request, response, callback); } @Override public InvocationType getInvocationType() { if (isDynamic()) return InvocationType.BLOCKING; Handler next = getHandler(); return next == null ? InvocationType.NON_BLOCKING : next.getInvocationType(); } } /** *

A {@link Handler.Container} that contains an ordered list of children {@link Handler}s * whose {@link Handler#handle(Request, Response, Callback)} method is invoked * in sequence on each child until a child returns {@code true}.

*/ @ManagedObject class Sequence extends AbstractContainer implements Collection { private volatile List _handlers = new ArrayList<>(); /** *

Creates a {@code Sequence} with the given {@code Handler}s.

*

The created sequence is dynamic only if the {@code Handler} * array is {@code null} or empty.

* * @param handlers the {@code Handler}s of this {@code Sequence} */ public Sequence(Handler... handlers) { this(handlers == null || handlers.length == 0, handlers == null ? List.of() : List.of(handlers)); } /** *

Creates a {@code Sequence} with the given {@code Handler}s.

*

The created sequence is dynamic only if the {@code Handler} * list is {@code null} or empty.

* * @param handlers the {@code Handler}s of this {@code Sequence} */ public Sequence(List handlers) { this(handlers == null || handlers.isEmpty(), handlers); } /** *

Creates a {@code Sequence} with the given {@code dynamic} parameter * and the given {@code Handler}s.

* * @param dynamic whether this {@code Sequence} is dynamic * @param handlers the {@code Handler}s of this {@code Sequence} */ public Sequence(boolean dynamic, List handlers) { super(dynamic); setHandlers(handlers); } @Override public boolean handle(Request request, Response response, Callback callback) throws Exception { for (Handler h : _handlers) { if (h.handle(request, response, callback)) return true; } return false; } @Override public List getHandlers() { return _handlers; } @Override public void setHandlers(List handlers) { if (!isDynamic() && isStarted()) throw new IllegalStateException(getState()); List newHandlers = newHandlers(handlers); Server server = getServer(); InvocationType serverInvocationType = server == null ? null : server.getInvocationType(); InvocationType invocationType = InvocationType.NON_BLOCKING; // Check for loops && InvocationType changes. for (Handler handler : newHandlers) { if (handler == null) continue; if (handler == this || (handler instanceof Handler.Container container && container.getDescendants().contains(this))) throw new IllegalStateException("setHandler loop"); invocationType = Invocable.combine(invocationType, handler.getInvocationType()); if (server != null) handler.setServer(server); } // If the collection can be changed dynamically, then the risk is that if we switch from NON_BLOCKING to BLOCKING // whilst the execution strategy may have already dispatched the very last available thread, thinking it would // never block, only for it to lose the race and find a newly added BLOCKING handler. if (isDynamic() && server != null && server.isStarted() && serverInvocationType != invocationType && serverInvocationType != InvocationType.BLOCKING) throw new IllegalArgumentException("Cannot change invocation type of started server"); updateBeans(_handlers, handlers); _handlers = newHandlers; } @Override public InvocationType getInvocationType() { if (isDynamic()) return InvocationType.BLOCKING; InvocationType invocationType = InvocationType.NON_BLOCKING; for (Handler handler : _handlers) invocationType = Invocable.combine(invocationType, handler.getInvocationType()); return invocationType; } protected List newHandlers(List handlers) { return handlers == null ? List.of() : List.copyOf(handlers); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy