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

org.eclipse.jetty.server.Request 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.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.MultiPartCompliance;
import org.eclipse.jetty.http.MultiPartConfig;
import org.eclipse.jetty.http.Trailers;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.internal.CompletionStreamWrapper;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

The representation of an HTTP request, for any protocol version (HTTP/1.1, HTTP/2, HTTP/3).

*

The typical idiom to read request content is the following:

*
{@code
 * public boolean handle(Request request, Response response, Callback callback)
 * {
 *     // Reject requests not appropriate for this handler.
 *     if (!request.getHttpURI().getPath().startsWith("/yourPath"))
 *         return false;
 *
 *     while (true)
 *     {
 *         Content.Chunk chunk = request.read();
 *         if (chunk == null)
 *         {
 *             // The chunk is not currently available, demand to be called back.
 *             request.demand(() -> handle(request, response, callback));
 *             return true;
 *         }
 *
 *         if (Content.Chunk.isError(chunk))
 *         {
 *             Throwable failure = error.getCause();
 *
 *             // Handle errors.
 *             // If the chunk is not last, then the error can be ignored and reading can be tried again.
 *             // Otherwise, if the chunk is last, or we do not wish to ignore a non-last error, then
 *             // mark the handling as complete, either generating a custom
 *             // response and succeeding the callback, or failing the callback.
 *             callback.failed(failure);
 *             return true;
 *         }
 *
 *         if (chunk instanceof Trailers trailers)
 *         {
 *             HttpFields fields = trailers.getTrailers();
 *
 *             // Handle trailers.
 *
 *             // Generate a response.
 *
 *             // Mark the handling as complete.
 *             callback.succeeded();
 *
 *             return true;
 *         }
 *
 *         // Normal chunk, process it.
 *         processChunk(chunk);
 *         // Release the content after processing.
 *         chunk.release();
 *
 *         // Reached end-of-file?
 *         if (chunk.isLast())
 *         {
 *             // Generate a response.
 *
 *             // Mark the handling as complete.
 *             callback.succeeded();
 *
 *             return true;
 *         }
 *     }
 * }
 * }
*/ public interface Request extends Attributes, Content.Source { Logger LOG = LoggerFactory.getLogger(Request.class); String COOKIE_ATTRIBUTE = Request.class.getCanonicalName() + ".Cookies"; List DEFAULT_LOCALES = List.of(Locale.getDefault()); /** * an ID unique within the lifetime scope of the {@link ConnectionMetaData#getId()}). * This may be a protocol ID (e.g. HTTP/2 stream ID) or it may be unrelated to the protocol. * * @see HttpStream#getId() */ String getId(); /** * @return the {@link Components} to be used with this {@code Request}. */ Components getComponents(); /** * @return the {@code ConnectionMetaData} associated to this {@code Request} */ ConnectionMetaData getConnectionMetaData(); /** * @return the HTTP method of this {@code Request} */ String getMethod(); /** * @return the HTTP URI of this {@code Request} * @see #getContextPath(Request) * @see #getPathInContext(Request) */ HttpURI getHttpURI(); /** * Get the {@link Context} associated with this {@code Request}. *

Note that a {@code Request} should always have an associated {@link Context} since if the * {@code Request} is not being handled by a {@link org.eclipse.jetty.server.handler.ContextHandler} then * the {@link Context} from {@link Server#getContext()} will be used. * @return the {@link Context} associated with this {@code Request}. Never {@code null}. * @see org.eclipse.jetty.server.handler.ContextHandler * @see Server#getContext() */ Context getContext(); /** * Get the context path of this {@code Request}. * This is equivalent to {@code request.getContext().getContextPath()}. * * @param request The request to get the context path from. * @return The encoded context path of the {@link Context} or {@code null}. * @see #getContext() * @see Context#getContextPath() * @see Server#getContext() */ static String getContextPath(Request request) { return request.getContext().getContextPath(); } /** *

Returns the canonically encoded path of the URI, scoped to the current context.

*

For example, when the request has a {@link Context} with {@code contextPath=/ctx} and the request's * {@link HttpURI} canonical path is {@code canonicalPath=/ctx/foo}, then {@code pathInContext=/foo}.

* * @return The part of the canonically encoded path of the URI after any context path prefix has been removed. * @see HttpURI#getCanonicalPath() * @see Context#getContextPath() */ static String getPathInContext(Request request) { return request.getContext().getPathInContext(request.getHttpURI().getCanonicalPath()); } /** * @return the HTTP headers of this {@code Request} */ HttpFields getHeaders(); /** * {@inheritDoc} * @param demandCallback the demand callback to invoke when there is a content chunk available. * @see Content.Source#demand(Runnable) */ @Override void demand(Runnable demandCallback); /** * @return the HTTP trailers of this {@code Request}, or {@code null} if they are not present */ HttpFields getTrailers(); /** *

Get the millisecond timestamp at which the request was created, obtained with {@link System#currentTimeMillis()}. * This method should be used for wall clock time, rather than {@link #getHeadersNanoTime()}, * which is appropriate for measuring latencies.

* @return The timestamp that the request was received/created in milliseconds */ static long getTimeStamp(Request request) { return System.currentTimeMillis() - NanoTime.millisSince(request.getHeadersNanoTime()); } /** *

Get the nanoTime at which the request arrived to a connector, obtained via {@link System#nanoTime()}. * This method can be used when measuring latencies.

* @return The nanoTime at which the request was received/created in nanoseconds */ long getBeginNanoTime(); /** *

Get the nanoTime at which the request headers were parsed, obtained via {@link System#nanoTime()}. * This method can be used when measuring latencies.

* @return The nanoTime at which the request was ready in nanoseconds */ long getHeadersNanoTime(); // TODO: see above. boolean isSecure(); /** * {@inheritDoc} *

In addition, the returned {@link Content.Chunk} may be a * {@link Trailers} instance, in case of request content trailers.

*/ @Override Content.Chunk read(); /** * Consume any available content. This bypasses any request wrappers to process the content in * {@link Request#read()} and reads directly from the {@link HttpStream}. This reads until * there is no content currently available, or it reaches EOF. * The {@link HttpConfiguration#setMaxUnconsumedRequestContentReads(int)} configuration can be used * to configure how many reads will be attempted by this method. * @return true if the content was fully consumed. */ boolean consumeAvailable(); /** *

Pushes the given {@code resource} to the client.

* * @param resource the resource to push * @throws UnsupportedOperationException if the push functionality is not supported * @see ConnectionMetaData#isPushSupported() */ default void push(MetaData.Request resource) { throw new UnsupportedOperationException(); } /** *

Adds a listener for idle timeouts.

*

The listener is a predicate function that should return {@code true} to indicate * that the idle timeout should be handled by the container as a fatal failure * (see {@link #addFailureListener(Consumer)}); or {@code false} to ignore that specific * timeout and for another timeout to occur after another idle period.

*

Idle timeout listeners are only invoked if there are no pending * {@link #demand(Runnable)} or {@link Response#write(boolean, ByteBuffer, Callback)} * operations.

*

Listeners are processed in the same order they are added, and the first that * returns {@code true} stops the processing of subsequent listeners, which are * therefore not invoked.

* * @param onIdleTimeout the idle timeout listener as a predicate function * @see #addFailureListener(Consumer) */ void addIdleTimeoutListener(Predicate onIdleTimeout); /** *

Adds a listener for asynchronous fatal failures.

*

When a listener is called, the effects of the failure have already taken place:

*
    *
  • Pending {@link #demand(Runnable)} have been woken up.
  • *
  • Calls to {@link #read()} will return the {@code Throwable} failure.
  • *
  • Pending and new {@link Response#write(boolean, ByteBuffer, Callback)} calls * will be failed by calling {@link Callback#failed(Throwable)} on the callback * passed to {@link Response#write(boolean, ByteBuffer, Callback)}.
  • *
*

Listeners are processed in the same order they are added.

* * @param onFailure the failure listener as a consumer function * @see #addIdleTimeoutListener(Predicate) */ void addFailureListener(Consumer onFailure); TunnelSupport getTunnelSupport(); /** * Add a {@link HttpStream.Wrapper} to the current {@link HttpStream}. * @param wrapper A function that wraps the passed stream. * @see #addCompletionListener(Request, Consumer) */ void addHttpStreamWrapper(Function wrapper); /** *

Adds a completion listener that is an optimized equivalent to overriding the * {@link HttpStream#succeeded()} and {@link HttpStream#failed(Throwable)} methods of a * {@link HttpStream.Wrapper} created by a call to {@link #addHttpStreamWrapper(Function)}.

*

Because adding completion listeners relies on {@link HttpStream} wrapping, * the completion listeners are invoked in reverse order they are added.

*

In the case of a failure, the {@link Throwable} cause is passed to the listener, but unlike * {@link #addFailureListener(Consumer)} listeners, which are called when the failure occurs, completion * listeners are called only once the {@link HttpStream} is completed at the very end of processing.

* * @param listener A {@link Consumer} of {@link Throwable} to call when the request handling is complete. * The listener is passed a {@code null} {@link Throwable} on success. * @see #addHttpStreamWrapper(Function) */ static void addCompletionListener(Request request, Consumer listener) { request.addHttpStreamWrapper(stream -> { if (stream instanceof CompletionStreamWrapper completionStreamWrapper) return completionStreamWrapper.addListener(listener); return new CompletionStreamWrapper(stream, listener); }); } /** *

Get a {@link Session} associated with the request. * Sessions may not be supported by a given configuration, in which case * {@code null} will be returned.

* @param create True if the session should be created for the request. * @return The session associated with the request or {@code null}. */ Session getSession(boolean create); /** * Returns a copy of the request that throws {@link UnsupportedOperationException} * from all mutative methods. * @return a copy of the request */ static Request asReadOnly(Request request) { return new Request.Wrapper(request) { @Override public void addHttpStreamWrapper(Function wrapper) { throw new UnsupportedOperationException(); } @Override public Content.Chunk read() { throw new UnsupportedOperationException(); } @Override public void demand(Runnable demandCallback) { throw new UnsupportedOperationException(); } @Override public void fail(Throwable failure) { throw new UnsupportedOperationException(); } }; } static String getHostName(InetSocketAddress inetSocketAddress) { if (inetSocketAddress.isUnresolved()) return inetSocketAddress.getHostString(); InetAddress address = inetSocketAddress.getAddress(); String result = address == null ? inetSocketAddress.getHostString() : address.getHostAddress(); return HostPort.normalizeHost(result); } static String getLocalAddr(Request request) { if (request == null) return null; SocketAddress local = request.getConnectionMetaData().getLocalSocketAddress(); if (local instanceof InetSocketAddress inetSocketAddress) return getHostName(inetSocketAddress); return local == null ? null : local.toString(); } static int getLocalPort(Request request) { if (request == null) return -1; SocketAddress local = request.getConnectionMetaData().getLocalSocketAddress(); if (local instanceof InetSocketAddress) return ((InetSocketAddress)local).getPort(); return -1; } static String getRemoteAddr(Request request) { if (request == null) return null; SocketAddress remote = request.getConnectionMetaData().getRemoteSocketAddress(); if (remote instanceof InetSocketAddress inetSocketAddress) return getHostName(inetSocketAddress); return remote == null ? null : remote.toString(); } static int getRemotePort(Request request) { if (request == null) return -1; SocketAddress remote = request.getConnectionMetaData().getRemoteSocketAddress(); if (remote instanceof InetSocketAddress) return ((InetSocketAddress)remote).getPort(); return -1; } /** * Get the logical name the request was sent to, which may be from the authority of the * request; the configured server authority; the actual network name of the server; * @param request The request to get the server name of * @return The logical server name or null if it cannot be determined. */ static String getServerName(Request request) { if (request == null) return null; HttpURI uri = request.getHttpURI(); if (uri.hasAuthority()) return HostPort.normalizeHost(uri.getHost()); HostPort authority = request.getConnectionMetaData().getServerAuthority(); if (authority != null) return authority.getHost(); return null; } /** * Get the logical port a request was received on, which may be from the authority of the request; the * configured server authority; the default port for the scheme; or the actual network port. * @param request The request to get the port of * @return The port for the request if it can be determined, otherwise -1 */ static int getServerPort(Request request) { if (request == null) return -1; // Does the request have an explicit port? HttpURI uri = request.getHttpURI(); if (uri.hasAuthority() && uri.getPort() > 0) return uri.getPort(); // Is there a configured server authority? HostPort authority = request.getConnectionMetaData().getHttpConfiguration().getServerAuthority(); if (authority != null && authority.getPort() > 0) return authority.getPort(); // Is there a scheme with a default port? HttpScheme scheme = HttpScheme.CACHE.get(request.getHttpURI().getScheme()); if (scheme != null && scheme.getDefaultPort() > 0) return scheme.getDefaultPort(); // Is there a local port? SocketAddress local = request.getConnectionMetaData().getLocalSocketAddress(); if (local instanceof InetSocketAddress inetSocketAddress && inetSocketAddress.getPort() > 0) return inetSocketAddress.getPort(); return -1; } static List getLocales(Request request) { HttpFields fields = request.getHeaders(); if (fields == null) return DEFAULT_LOCALES; List acceptable = fields.getQualityCSV(HttpHeader.ACCEPT_LANGUAGE); // return sorted list of locals, with known locales in quality order before unknown locales in quality order return switch (acceptable.size()) { case 0 -> DEFAULT_LOCALES; case 1 -> List.of(Locale.forLanguageTag(acceptable.get(0))); default -> { List locales = acceptable.stream().map(Locale::forLanguageTag).toList(); List known = locales.stream().filter(MimeTypes::isKnownLocale).toList(); if (known.size() == locales.size()) yield locales; // All locales are known List unknown = locales.stream().filter(l -> !MimeTypes.isKnownLocale(l)).toList(); locales = new ArrayList<>(known); locales.addAll(unknown); yield locales; // List of known locales before unknown locales } }; } static InputStream asInputStream(Request request) { return Content.Source.asInputStream(request); } /** * Get a {@link Charset} from the request {@link HttpHeader#CONTENT_TYPE}, if any. * @param request The request. * @return A {@link Charset} or null * @throws IllegalCharsetNameException If the charset name is illegal * @throws UnsupportedCharsetException If no support for the charset is available */ static Charset getCharset(Request request) throws IllegalCharsetNameException, UnsupportedCharsetException { String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); return Objects.requireNonNullElse(request.getContext().getMimeTypes(), MimeTypes.DEFAULTS).getCharset(contentType); } static Fields extractQueryParameters(Request request) { String query = request.getHttpURI().getQuery(); if (StringUtil.isBlank(query)) return Fields.EMPTY; Fields fields = new Fields(true); if (StringUtil.isNotBlank(query)) UrlEncoded.decodeUtf8To(query, fields); return fields; } static Fields extractQueryParameters(Request request, Charset charset) { Fields fields = new Fields(true); String query = request.getHttpURI().getQuery(); if (StringUtil.isNotBlank(query)) UrlEncoded.decodeTo(query, fields::add, charset); return fields; } static Fields getParameters(Request request) throws Exception { return getParametersAsync(request).get(); } static CompletableFuture getParametersAsync(Request request) { Fields queryFields = Request.extractQueryParameters(request); CompletableFuture contentFields = FormFields.from(request); return contentFields.thenApply(formFields -> Fields.combine(queryFields, formFields)); } @SuppressWarnings("unchecked") static List getCookies(Request request) { return CookieCache.getCookies(request); } /** *

Get a {@link MultiPartConfig.Builder} given a {@link Request} and a location.

* *

If the location is null this will extract the {@link Context} temp directory from the request. * The {@code maxHeaderSize}, {@link MultiPartCompliance}, {@link ComplianceViolation.Listener} * are also extracted from the request. Additional settings can be configured through the * {@link MultiPartConfig.Builder} which is returned.

* * @param request the request. * @param location the temp directory location, or null to use the context default. * @return a {@link MultiPartConfig} with settings extracted from the request. */ static MultiPartConfig.Builder getMultiPartConfig(Request request, Path location) { HttpChannel httpChannel = HttpChannel.from(request); HttpConfiguration httpConfiguration = request.getConnectionMetaData().getHttpConfiguration(); MultiPartCompliance multiPartCompliance = httpConfiguration.getMultiPartCompliance(); ComplianceViolation.Listener complianceViolationListener = httpChannel.getComplianceViolationListener(); int maxHeaderSize = httpConfiguration.getRequestHeaderSize(); if (location == null) location = request.getContext().getTempDirectory().toPath(); return new MultiPartConfig.Builder() .location(location) .maxHeadersSize(maxHeaderSize) .complianceMode(multiPartCompliance) .violationListener(complianceViolationListener); } /** * Generate a proper "Location" header for redirects. * * @param request the request the redirect should be based on (needed when relative locations are provided, so that * server name, scheme, port can be built out properly) * @param location the location URL to redirect to (can be a relative path) * @return the full redirect "Location" URL (including scheme, host, port, path, etc...) * @deprecated use {@link Response#toRedirectURI(Request, String)} */ @Deprecated static String toRedirectURI(Request request, String location) { return Response.toRedirectURI(request, location); } /** * This interface will be detected by the {@link #wrap(Request, HttpURI)} static method to wrap the request * changing its target to a given path. If a {@link Request} implements this interface it can * be obtained with the {@link Request#as(Request, Class)} method. * @see #serveAs(Request, HttpURI) */ interface ServeAs extends Request { /** * Wraps a request but changes the uri so that it can be served to a different target. * @param request the original request. * @param uri the uri of the new target. * @return the request wrapped to the new target. */ Request wrap(Request request, HttpURI uri); } /** * Return a request with its {@link HttpURI} changed to the supplied target. * If the passed request or any of the requests that it wraps implements {@link ServeAs} then * {@link ServeAs#wrap(Request, HttpURI)} will be used to do the wrap, * otherwise a simple {@link Request.Wrapper} may be returned. * @param request the original request. * @param uri the new URI to target. * @return the possibly wrapped request to target the new URI. */ static Request serveAs(Request request, HttpURI uri) { if (request.getHttpURI().equals(uri)) return request; ServeAs serveAs = Request.as(request, ServeAs.class); if (serveAs != null) return serveAs.wrap(request, uri); return new Request.Wrapper(request) { @Override public HttpURI getHttpURI() { return uri; } }; } /** *

A handler for an HTTP request and response.

*

The handling typically involves reading the request content (if any) and producing a response.

*/ @ManagedObject @FunctionalInterface interface Handler extends Invocable { /** *

Invoked to handle the passed HTTP request and response.

*

The request is accepted by returning true, then handling must be concluded by * completing the passed callback. The handling may be asynchronous, i.e. this method may return true and * complete the given callback later, possibly from a different thread. If this method returns false, * then the callback must not be invoked and any mutation on the response reversed.

*

Exceptions thrown by this method may be subsequently handled by an error {@link Request.Handler}, * if present, otherwise a default HTTP 500 error is generated and the * callback completed while writing the error response.

*

The simplest implementation is:

*
         * public boolean handle(Request request, Response response, Callback callback)
         * {
         *     callback.succeeded();
         *     return true;
         * }
         * 
*

A HelloWorld implementation is:

*
         * public boolean handle(Request request, Response response, Callback callback)
         * {
         *     response.write(true, ByteBuffer.wrap("Hello World\n".getBytes(StandardCharsets.UTF_8)), callback);
         *     return true;
         * }
         * 
* * @param request the HTTP request to handle * @param response the HTTP response to handle * @param callback the callback to complete when the handling is complete * @return True if and only if the request will be handled, a response generated and the callback eventually called. * This may occur within the scope of the call to this method, or asynchronously some time later. If false * is returned, then this method must not generate a response, nor complete the callback. * @throws Exception if there is a failure during the handling. Catchers cannot assume that the callback will be * called and thus should attempt to complete the request as if a false had been returned. * @see AbortException */ boolean handle(Request request, Response response, Callback callback) throws Exception; @Override @ManagedAttribute("The InvocationType of this Handler") default InvocationType getInvocationType() { return InvocationType.BLOCKING; } /** * A marker {@link Exception} that can be passed the {@link Callback#failed(Throwable)} of the {@link Callback} * passed in {@link #handle(Request, Response, Callback)}, to cause request handling to be aborted. For HTTP/1 * an abort is handled with a {@link EndPoint#close()}, for later versions of HTTP, a reset message will be sent. */ class AbortException extends Exception { public AbortException() { super(); } public AbortException(String message) { super(message); } public AbortException(String message, Throwable cause) { super(message, cause); } public AbortException(Throwable cause) { super(cause); } } } /** *

A wrapper for {@code Request} instances.

*/ class Wrapper implements Request { /** * Implementation note: {@link Request.Wrapper} does not extend from {@link Attributes.Wrapper} * as {@link #getWrapped()} would either need to be implemented as {@code return (Request)getWrapped()} * which would require a cast from one interface type to another, spoiling the JVM's * {@code secondary_super_cache}, or by storing the same {@code _wrapped} object in two fields * (one in {@link Attributes.Wrapper} as type {@link Attributes} and one in {@link Request.Wrapper} as * type {@link Request}) to save the costly cast from interface type to another. */ private final Request _request; public Wrapper(Request wrapped) { _request = Objects.requireNonNull(wrapped); } @Override public String getId() { return getWrapped().getId(); } @Override public Components getComponents() { return getWrapped().getComponents(); } @Override public ConnectionMetaData getConnectionMetaData() { return getWrapped().getConnectionMetaData(); } @Override public String getMethod() { return getWrapped().getMethod(); } @Override public HttpURI getHttpURI() { return getWrapped().getHttpURI(); } @Override public Context getContext() { return getWrapped().getContext(); } @Override public HttpFields getHeaders() { return getWrapped().getHeaders(); } @Override public HttpFields getTrailers() { return getWrapped().getTrailers(); } @Override public long getBeginNanoTime() { return getWrapped().getBeginNanoTime(); } @Override public long getHeadersNanoTime() { return getWrapped().getHeadersNanoTime(); } @Override public boolean isSecure() { return getWrapped().isSecure(); } @Override public long getLength() { return getWrapped().getLength(); } @Override public Content.Chunk read() { return getWrapped().read(); } @Override public boolean consumeAvailable() { return getWrapped().consumeAvailable(); } @Override public void demand(Runnable demandCallback) { getWrapped().demand(demandCallback); } @Override public void fail(Throwable failure) { getWrapped().fail(failure); } @Override public void push(MetaData.Request resource) { getWrapped().push(resource); } @Override public void addIdleTimeoutListener(Predicate onIdleTimeout) { getWrapped().addIdleTimeoutListener(onIdleTimeout); } @Override public void addFailureListener(Consumer onFailure) { getWrapped().addFailureListener(onFailure); } @Override public TunnelSupport getTunnelSupport() { return getWrapped().getTunnelSupport(); } @Override public void addHttpStreamWrapper(Function wrapper) { getWrapped().addHttpStreamWrapper(wrapper); } @Override public Session getSession(boolean create) { return getWrapped().getSession(create); } @Override public Object removeAttribute(String name) { return getWrapped().removeAttribute(name); } @Override public Object setAttribute(String name, Object attribute) { return getWrapped().setAttribute(name, attribute); } @Override public Object getAttribute(String name) { return getWrapped().getAttribute(name); } @Override public Set getAttributeNameSet() { return getWrapped().getAttributeNameSet(); } @Override public Map asAttributeMap() { return getWrapped().asAttributeMap(); } @Override public void clearAttributes() { getWrapped().clearAttributes(); } public Request getWrapped() { return _request; } @Override public String toString() { return "%s@%x{%s}".formatted(getClass().getSimpleName(), hashCode(), getWrapped()); } } @SuppressWarnings("unchecked") static T as(Request request, Class type) { while (request != null) { if (type.isInstance(request)) return (T)request; request = request instanceof Request.Wrapper wrapper ? wrapper.getWrapped() : null; } return null; } @SuppressWarnings("unchecked") static R get(Request request, Class type, Function getter) { T t = Request.as(request, type); return (t == null) ? null : getter.apply(t); } static Request unWrap(Request request) { while (request instanceof Request.Wrapper wrapped) { request = wrapped.getWrapped(); } return request; } static long getContentBytesRead(Request request) { Request originalRequest = unWrap(request); if (originalRequest instanceof HttpChannelState.ChannelRequest channelRequest) return channelRequest.getContentBytesRead(); return -1; } /** *

Creates a new {@link HttpURI} from the given Request's HttpURI and the given path in context.

*

For example, for {@code contextPath=/ctx}, {@code request.httpURI=http://host/ctx/path?a=b}, and * {@code newPathInContext=/newPath}, the returned HttpURI is {@code http://host/ctx/newPath?a=b}.

* * @param request The request to base the new HttpURI on. * @param newEncodedPathInContext The new path in context for the new HttpURI * @return A new immutable HttpURI with the path in context replaced, but query string and path * parameters retained. */ static HttpURI newHttpURIFrom(Request request, String newEncodedPathInContext) { return HttpURI.build(request.getHttpURI()) .path(URIUtil.addPaths(getContextPath(request), newEncodedPathInContext)) .asImmutable(); } /** * @param request The request to enquire. * @return the minimal {@link AuthenticationState} of the request, or null if no authentication in process. */ static AuthenticationState getAuthenticationState(Request request) { if (request.getAttribute(AuthenticationState.class.getName()) instanceof AuthenticationState authenticationState) return authenticationState; return null; } /** * @param request The request to enquire. * @param state the {@link AuthenticationState} of the request, or null if no authentication in process. */ static void setAuthenticationState(Request request, AuthenticationState state) { request.setAttribute(AuthenticationState.class.getName(), state); } /** * A minimal Authentication interface, primarily used for logging. It is implemented by the * {@code jetty-security} module's {@code AuthenticationState} to provide full authentication services. */ interface AuthenticationState { /** * @return The authenticated user {@link Principal}, or null if the Authentication is in a non-authenticated state. */ default Principal getUserPrincipal() { return null; } } /** *

A {@link Request.Wrapper} that separately provides the request {@link Attributes}.

*

The provided {@link Attributes} should be an {@link Attributes.Wrapper} over the request.

*/ class AttributesWrapper extends Wrapper { private final Attributes _attributes; /** * @param wrapped the request to wrap * @param attributes the provided request attributes, typically a {@link Attributes.Wrapper} over the request */ public AttributesWrapper(Request wrapped, Attributes attributes) { super(wrapped); _attributes = Objects.requireNonNull(attributes); } @Override public Map asAttributeMap() { return _attributes.asAttributeMap(); } @Override public void clearAttributes() { _attributes.clearAttributes(); } @Override public Object removeAttribute(String name) { return _attributes.removeAttribute(name); } @Override public Object setAttribute(String name, Object attribute) { return _attributes.setAttribute(name, attribute); } @Override public Object getAttribute(String name) { return _attributes.getAttribute(name); } @Override public Set getAttributeNameSet() { return _attributes.getAttributeNameSet(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy