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

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

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

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.server.HttpChannelState.Action;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.eclipse.jetty.util.thread.Invocable.InvocationType.NON_BLOCKING;

/**
 * HttpChannel represents a single endpoint for HTTP semantic processing.
 * The HttpChannel is both an HttpParser.RequestHandler, where it passively receives events from
 * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
 * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
 * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
 * HttpParser.RequestHandler callbacks.   The completion of the active phase is signalled by a call to
 * HttpTransport.completed().
 */
public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
{
    public static Listener NOOP_LISTENER = new Listener() {};
    private static final Logger LOG = LoggerFactory.getLogger(HttpChannel.class);

    private final AtomicLong _requests = new AtomicLong();
    private final Connector _connector;
    private final Executor _executor;
    private final HttpConfiguration _configuration;
    private final EndPoint _endPoint;
    private final HttpTransport _transport;
    private final HttpChannelState _state;
    private final Request _request;
    private final Response _response;
    private final HttpChannel.Listener _combinedListener;
    @Deprecated
    private final List _transientListeners = new ArrayList<>();
    private MetaData.Response _committedMetaData;
    private RequestLog _requestLog;
    private long _oldIdleTimeout;

    /**
     * Bytes written after interception (eg after compression)
     */
    private long _written;

    public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport)
    {
        _connector = connector;
        _configuration = Objects.requireNonNull(configuration);
        _endPoint = endPoint;
        _transport = transport;

        _state = new HttpChannelState(this);
        _request = new Request(this, newHttpInput(_state));
        _response = new Response(this, newHttpOutput());
        _executor = connector.getServer().getThreadPool();
        _requestLog = connector.getServer().getRequestLog();
        _combinedListener = (connector instanceof AbstractConnector)
            ? ((AbstractConnector)connector).getHttpChannelListeners()
            : NOOP_LISTENER;

        if (LOG.isDebugEnabled())
            LOG.debug("new {} -> {},{},{}",
                this,
                _endPoint,
                _endPoint == null ? null : _endPoint.getConnection(),
                _state);
    }

    public boolean isSendError()
    {
        return _state.isSendError();
    }

    /** Format the address or host returned from Request methods
     * @param addr The address or host
     * @return Default implementation returns {@link HostPort#normalizeHost(String)}
     */
    protected String formatAddrOrHost(String addr)
    {
        return HostPort.normalizeHost(addr);
    }

    private HttpInput newHttpInput(HttpChannelState state)
    {
        return new HttpInput(state);
    }

    /**
     * Notify the channel that content is needed. If some content is immediately available, true is returned and
     * {@link #produceContent()} has to be called and will return a non-null object.
     * If no content is immediately available, an attempt to produce content must be made; if new content has been
     * produced, true is returned; otherwise {@link HttpInput#onContentProducible()} is called once some content
     * arrives and {@link #produceContent()} can be called without returning {@code null}.
     * If a failure happens, then {@link HttpInput#onContentProducible()} will be called and an error content will
     * return the error on the next call to {@link #produceContent()}.
     * @return true if content is immediately available.
     */
    public abstract boolean needContent();

    /**
     * Produce a {@link HttpInput.Content} object with data currently stored within the channel. The produced content
     * can be special (meaning calling {@link HttpInput.Content#isSpecial()} returns true) if the channel reached a special
     * state, like EOF or an error.
     * Once a special content has been returned, all subsequent calls to this method will always return a special content
     * of the same kind and {@link #needContent()} will always return true.
     * The returned content is "raw", i.e.: not decoded.
     * @return a {@link HttpInput.Content} object if one is immediately available without blocking, null otherwise.
     */
    public abstract HttpInput.Content produceContent();

    /**
     * Fail all content that is currently stored within the channel.
     * @param failure the failure to fail the content with.
     * @return true if EOF was reached while failing all content, false otherwise.
     */
    public abstract boolean failAllContent(Throwable failure);

    /**
     * Fail the channel's input.
     * @param failure the failure.
     * @return true if the channel needs to be rescheduled.
     */
    public abstract boolean failed(Throwable failure);

    /**
     * Mark the channel's input as EOF.
     * @return true if the channel needs to be rescheduled.
     */
    protected abstract boolean eof();

    protected HttpOutput newHttpOutput()
    {
        return new HttpOutput(this);
    }

    public HttpChannelState getState()
    {
        return _state;
    }

    /**
     * Add a transient Listener to the HttpChannel.
     * 

Listeners added by this method will only be notified * if the HttpChannel has been constructed with an instance of * {@link TransientListeners} as an {@link AbstractConnector} * provided listener

*

Transient listeners are removed after every request cycle

* @param listener the listener to add * @return true if the listener was added. */ @Deprecated public boolean addListener(Listener listener) { return _transientListeners.add(listener); } @Deprecated public boolean removeListener(Listener listener) { return _transientListeners.remove(listener); } @Deprecated public List getTransientListeners() { return _transientListeners; } public long getBytesWritten() { return _written; } /** * @return the number of requests handled by this connection */ public long getRequests() { return _requests.get(); } public Connector getConnector() { return _connector; } public HttpTransport getHttpTransport() { return _transport; } public RequestLog getRequestLog() { return _requestLog; } public void setRequestLog(RequestLog requestLog) { _requestLog = requestLog; } public void addRequestLog(RequestLog requestLog) { if (_requestLog == null) _requestLog = requestLog; else if (_requestLog instanceof RequestLogCollection) ((RequestLogCollection)_requestLog).add(requestLog); else _requestLog = new RequestLogCollection(_requestLog, requestLog); } public MetaData.Response getCommittedMetaData() { return _committedMetaData; } /** * Get the idle timeout. *

This is implemented as a call to {@link EndPoint#getIdleTimeout()}, but may be * overridden by channels that have timeouts different from their connections. * * @return the idle timeout (in milliseconds) */ public long getIdleTimeout() { return _endPoint.getIdleTimeout(); } /** * Set the idle timeout. *

This is implemented as a call to {@link EndPoint#setIdleTimeout(long)}, but may be * overridden by channels that have timeouts different from their connections. * * @param timeoutMs the idle timeout in milliseconds */ public void setIdleTimeout(long timeoutMs) { _endPoint.setIdleTimeout(timeoutMs); } public ByteBufferPool getByteBufferPool() { return _connector.getByteBufferPool(); } public HttpConfiguration getHttpConfiguration() { return _configuration; } public Server getServer() { return _connector.getServer(); } public Request getRequest() { return _request; } public Response getResponse() { return _response; } public Connection getConnection() { return _endPoint.getConnection(); } public EndPoint getEndPoint() { return _endPoint; } /** *

Return the local name of the connected channel.

* *

* This is the host name after the connector is bound and the connection is accepted. *

*

* Value can be overridden by {@link HttpConfiguration#setLocalAddress(SocketAddress)}. *

*

* Note: some connectors are not based on IP networking, and default behavior here will * result in a null return. Use {@link HttpConfiguration#setLocalAddress(SocketAddress)} * to set the value to an acceptable host name. *

* * @return the local name, or null */ public String getLocalName() { HttpConfiguration httpConfiguration = getHttpConfiguration(); if (httpConfiguration != null) { SocketAddress localAddress = httpConfiguration.getLocalAddress(); if (localAddress instanceof InetSocketAddress) return ((InetSocketAddress)localAddress).getHostName(); } InetSocketAddress local = getLocalAddress(); if (local != null) return local.getHostString(); return null; } /** *

Return the Local Port of the connected channel.

* *

* This is the port the connector is bound to and is accepting connections on. *

*

* Value can be overridden by {@link HttpConfiguration#setLocalAddress(SocketAddress)}. *

*

* Note: some connectors are not based on IP networking, and default behavior here will * result in a value of 0 returned. Use {@link HttpConfiguration#setLocalAddress(SocketAddress)} * to set the value to an acceptable port. *

* * @return the local port, or 0 if unspecified */ public int getLocalPort() { HttpConfiguration httpConfiguration = getHttpConfiguration(); if (httpConfiguration != null) { SocketAddress localAddress = httpConfiguration.getLocalAddress(); if (localAddress instanceof InetSocketAddress) return ((InetSocketAddress)localAddress).getPort(); } InetSocketAddress local = getLocalAddress(); return local == null ? 0 : local.getPort(); } public InetSocketAddress getLocalAddress() { HttpConfiguration httpConfiguration = getHttpConfiguration(); if (httpConfiguration != null) { SocketAddress localAddress = httpConfiguration.getLocalAddress(); if (localAddress instanceof InetSocketAddress) return ((InetSocketAddress)localAddress); } SocketAddress local = _endPoint.getLocalSocketAddress(); if (local instanceof InetSocketAddress) return (InetSocketAddress)local; return null; } public InetSocketAddress getRemoteAddress() { SocketAddress remote = _endPoint.getRemoteSocketAddress(); if (remote instanceof InetSocketAddress) return (InetSocketAddress)remote; return null; } /** * @return return the HttpConfiguration server authority override */ public HostPort getServerAuthority() { HttpConfiguration httpConfiguration = getHttpConfiguration(); if (httpConfiguration != null) return httpConfiguration.getServerAuthority(); return null; } /** * If the associated response has the Expect header set to 100 Continue, * then accessing the input stream indicates that the handler/servlet * is ready for the request body and thus a 100 Continue response is sent. * * @param available estimate of the number of bytes that are available * @throws IOException if the InputStream cannot be created */ public void continue100(int available) throws IOException { throw new UnsupportedOperationException(); } public void recycle() { _request.recycle(); _response.recycle(); _committedMetaData = null; _requestLog = _connector == null ? null : _connector.getServer().getRequestLog(); _written = 0; _oldIdleTimeout = 0; _transientListeners.clear(); } @Override public void run() { handle(); } /** * @return True if the channel is ready to continue handling (ie it is not suspended) */ public boolean handle() { if (LOG.isDebugEnabled()) LOG.debug("handle {} {} ", _request.getHttpURI(), this); HttpChannelState.Action action = _state.handling(); // Loop here to handle async request redispatches. // The loop is controlled by the call to async.unhandle in the // finally block below. Unhandle will return false only if an async dispatch has // already happened when unhandle is called. loop: while (!getServer().isStopped()) { try { if (LOG.isDebugEnabled()) LOG.debug("action {} {}", action, this); switch (action) { case TERMINATED: onCompleted(); break loop; case WAIT: // break loop without calling unhandle break loop; case DISPATCH: { if (!_request.hasMetaData()) throw new IllegalStateException("state=" + _state); dispatch(DispatcherType.REQUEST, () -> { for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers()) { customizer.customize(getConnector(), _configuration, _request); if (_request.isHandled()) return; } getServer().handle(HttpChannel.this); }); break; } case ASYNC_DISPATCH: { dispatch(DispatcherType.ASYNC, () -> getServer().handleAsync(this)); break; } case ASYNC_TIMEOUT: _state.onTimeout(); break; case SEND_ERROR: { try { // Get ready to send an error response _response.resetContent(); // the following is needed as you cannot trust the response code and reason // as those could have been modified after calling sendError Integer code = (Integer)_request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); if (code == null) code = HttpStatus.INTERNAL_SERVER_ERROR_500; _response.setStatus(code); // The handling of the original dispatch failed and we are now going to either generate // and error response ourselves or dispatch for an error page. If there is content left over // from the failed dispatch, then we try to consume it here and if we fail we add a // Connection:close. This can't be deferred to COMPLETE as the response will be committed // by then. ensureConsumeAllOrNotPersistent(); ContextHandler.Context context = (ContextHandler.Context)_request.getAttribute(ErrorHandler.ERROR_CONTEXT); ErrorHandler errorHandler = ErrorHandler.getErrorHandler(getServer(), context == null ? null : context.getContextHandler()); // If we can't have a body, then create a minimal error response. if (HttpStatus.hasNoBody(_response.getStatus()) || errorHandler == null || !errorHandler.errorPageForMethod(_request.getMethod())) { sendResponseAndComplete(); break; } dispatch(DispatcherType.ERROR, () -> { errorHandler.handle(null, _request, _request, _response); _request.setHandled(true); }); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Could not perform ERROR dispatch, aborting", x); if (_state.isResponseCommitted()) abort(x); else { try { _response.resetContent(); sendResponseAndComplete(); } catch (Throwable t) { if (x != t) x.addSuppressed(t); abort(x); } } } finally { // clean up the context that was set in Response.sendError _request.removeAttribute(ErrorHandler.ERROR_CONTEXT); } break; } case ASYNC_ERROR: { throw _state.getAsyncContextEvent().getThrowable(); } case READ_CALLBACK: { ContextHandler handler = _state.getContextHandler(); if (handler != null) handler.handle(_request, _request.getHttpInput()); else _request.getHttpInput().run(); break; } case WRITE_CALLBACK: { ContextHandler handler = _state.getContextHandler(); if (handler != null) handler.handle(_request, _response.getHttpOutput()); else _response.getHttpOutput().run(); break; } case COMPLETE: { if (!_response.isCommitted()) { if (!_request.isHandled() && !_response.getHttpOutput().isClosed()) { // The request was not actually handled _response.sendError(HttpStatus.NOT_FOUND_404); break; } // Indicate Connection:close if we can't consume all. if (_response.getStatus() >= 200) ensureConsumeAllOrNotPersistent(); } // RFC 7230, section 3.3. if (!_request.isHead() && _response.getStatus() != HttpStatus.NOT_MODIFIED_304 && !_response.isContentComplete(_response.getHttpOutput().getWritten())) { if (sendErrorOrAbort("Insufficient content written")) break; } // If send error is called we need to break. if (checkAndPrepareUpgrade()) break; // Set a close callback on the HttpOutput to make it an async callback _response.completeOutput(Callback.from(NON_BLOCKING, () -> _state.completed(null), _state::completed)); break; } default: throw new IllegalStateException(this.toString()); } } catch (Throwable failure) { if ("org.eclipse.jetty.continuation.ContinuationThrowable".equals(failure.getClass().getName())) LOG.trace("IGNORED", failure); else handleException(failure); } action = _state.unhandle(); } if (LOG.isDebugEnabled()) LOG.debug("!handle {} {}", action, this); boolean suspended = action == Action.WAIT; return !suspended; } public void ensureConsumeAllOrNotPersistent() { switch (_request.getHttpVersion()) { case HTTP_1_0: if (_request.getHttpInput().consumeAll()) return; // Remove any keep-alive value in Connection headers _response.getHttpFields().computeField(HttpHeader.CONNECTION, (h, fields) -> { if (fields == null || fields.isEmpty()) return null; String v = fields.stream() .flatMap(field -> Stream.of(field.getValues()).filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s))) .collect(Collectors.joining(", ")); if (StringUtil.isEmpty(v)) return null; return new HttpField(HttpHeader.CONNECTION, v); }); break; case HTTP_1_1: if (_request.getHttpInput().consumeAll()) return; // Add close value to Connection headers _response.getHttpFields().computeField(HttpHeader.CONNECTION, (h, fields) -> { if (fields == null || fields.isEmpty()) return HttpConnection.CONNECTION_CLOSE; if (fields.stream().anyMatch(f -> f.contains(HttpHeaderValue.CLOSE.asString()))) { if (fields.size() == 1) { HttpField f = fields.get(0); if (HttpConnection.CONNECTION_CLOSE.equals(f)) return f; } return new HttpField(HttpHeader.CONNECTION, fields.stream() .flatMap(field -> Stream.of(field.getValues()).filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s))) .collect(Collectors.joining(", "))); } return new HttpField(HttpHeader.CONNECTION, Stream.concat(fields.stream() .flatMap(field -> Stream.of(field.getValues()).filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s))), Stream.of(HttpHeaderValue.CLOSE.asString())) .collect(Collectors.joining(", "))); }); break; default: break; } } /** * @param message the error message. * @return true if we have sent an error, false if we have aborted. */ public boolean sendErrorOrAbort(String message) { try { if (isCommitted()) { abort(new IOException(message)); return false; } _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, message); return true; } catch (Throwable x) { LOG.trace("IGNORED", x); abort(x); } return false; } private void dispatch(DispatcherType type, Dispatchable dispatchable) throws IOException, ServletException { try { _request.setHandled(false); _response.reopen(); _request.setDispatcherType(type); _combinedListener.onBeforeDispatch(_request); dispatchable.dispatch(); } catch (Throwable x) { _combinedListener.onDispatchFailure(_request, x); throw x; } finally { _combinedListener.onAfterDispatch(_request); _request.setDispatcherType(null); } } /** *

Sends an error 500, performing a special logic to detect whether the request is suspended, * to avoid concurrent writes from the application.

*

It may happen that the application suspends, and then throws an exception, while an application * spawned thread writes the response content; in such case, we attempt to commit the error directly * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.

* * @param failure the Throwable that caused the problem */ protected void handleException(Throwable failure) { // Unwrap wrapping Jetty and Servlet exceptions. Throwable quiet = unwrap(failure, QuietException.class); Throwable noStack = unwrap(failure, BadMessageException.class, IOException.class, TimeoutException.class); if (quiet != null || !getServer().isRunning()) { if (LOG.isDebugEnabled()) LOG.debug(_request.getRequestURI(), failure); } else if (noStack != null) { // No stack trace unless there is debug turned on if (LOG.isDebugEnabled()) LOG.warn("handleException {}", _request.getRequestURI(), failure); else LOG.warn("handleException {} {}", _request.getRequestURI(), noStack.toString()); } else { LOG.warn(_request.getRequestURI(), failure); } if (isCommitted()) { abort(failure); } else { try { _state.onError(failure); } catch (IllegalStateException e) { abort(failure); } } } /** * Unwrap failure causes to find target class * * @param failure The throwable to have its causes unwrapped * @param targets Exception classes that we should not unwrap * @return A target throwable or null */ protected Throwable unwrap(Throwable failure, Class... targets) { while (failure != null) { for (Class x : targets) { if (x.isInstance(failure)) return failure; } failure = failure.getCause(); } return null; } public void sendResponseAndComplete() { try { _request.setHandled(true); _state.completing(); sendResponse(null, _response.getHttpOutput().getBuffer(), true, Callback.from(() -> _state.completed(null), _state::completed)); } catch (Throwable x) { abort(x); } } public boolean isExpecting100Continue() { return false; } public boolean isExpecting102Processing() { return false; } @Override public String toString() { long timeStamp = _request.getTimeStamp(); return String.format("%s@%x{s=%s,r=%s,c=%b/%b,a=%s,uri=%s,age=%d}", getClass().getSimpleName(), hashCode(), _state, _requests, isRequestCompleted(), isResponseCompleted(), _state.getState(), _request.getHttpURI(), timeStamp == 0 ? 0 : System.currentTimeMillis() - timeStamp); } public void onRequest(MetaData.Request request) { _requests.incrementAndGet(); _request.setTimeStamp(System.currentTimeMillis()); HttpFields.Mutable fields = _response.getHttpFields(); if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE)) fields.put(_connector.getServer().getDateField()); long idleTO = _configuration.getIdleTimeout(); _oldIdleTimeout = getIdleTimeout(); if (idleTO >= 0 && _oldIdleTimeout != idleTO) setIdleTimeout(idleTO); _request.setMetaData(request); _combinedListener.onRequestBegin(_request); if (LOG.isDebugEnabled()) LOG.debug("REQUEST for {} on {}{}{} {} {}{}{}", request.getURIString(), this, System.lineSeparator(), request.getMethod(), request.getURIString(), request.getHttpVersion(), System.lineSeparator(), request.getFields()); } public boolean onContent(HttpInput.Content content) { if (LOG.isDebugEnabled()) LOG.debug("onContent {} {}", this, content); _combinedListener.onRequestContent(_request, content.getByteBuffer()); return false; } public boolean onContentComplete() { if (LOG.isDebugEnabled()) LOG.debug("onContentComplete {}", this); _combinedListener.onRequestContentEnd(_request); return false; } public void onTrailers(HttpFields trailers) { if (LOG.isDebugEnabled()) LOG.debug("onTrailers {} {}", this, trailers); _request.setTrailerHttpFields(trailers); _combinedListener.onRequestTrailers(_request); } public boolean onRequestComplete() { if (LOG.isDebugEnabled()) LOG.debug("onRequestComplete {}", this); boolean result = eof(); _combinedListener.onRequestEnd(_request); return result; } /** *

Checks whether the processing of the request resulted in an upgrade, * and if so performs upgrade preparation steps before the upgrade * response is sent back to the client.

*

This avoids a race where the server is unprepared if the client sends * data immediately after having received the upgrade response.

* @return true if the channel is not complete and more processing is required, * typically because sendError has been called. */ protected boolean checkAndPrepareUpgrade() { return false; } public void onCompleted() { if (LOG.isDebugEnabled()) LOG.debug("onCompleted for {} written={}", getRequest().getRequestURI(), getBytesWritten()); long idleTO = _configuration.getIdleTimeout(); if (idleTO >= 0 && getIdleTimeout() != _oldIdleTimeout) setIdleTimeout(_oldIdleTimeout); _request.onCompleted(); _combinedListener.onComplete(_request); _transport.onCompleted(); } public void onBadMessage(BadMessageException failure) { int status = failure.getCode(); String reason = failure.getReason(); if (status < HttpStatus.BAD_REQUEST_400 || status > 599) failure = new BadMessageException(HttpStatus.BAD_REQUEST_400, reason, failure); _combinedListener.onRequestFailure(_request, failure); Action action; try { action = _state.handling(); } catch (Throwable e) { // The bad message cannot be handled in the current state, // so rethrow, hopefully somebody will be able to handle. abort(e); throw failure; } try { if (action == Action.DISPATCH) { ByteBuffer content = null; HttpFields.Mutable fields = HttpFields.build(); ErrorHandler handler = getServer().getBean(ErrorHandler.class); if (handler != null) content = handler.badMessageError(status, reason, fields); sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1, status, null, fields, BufferUtil.length(content)), content, true); } } catch (IOException e) { if (LOG.isDebugEnabled()) LOG.debug("Unable to send bad message response", e); } finally { try { onCompleted(); } catch (Throwable e) { LOG.debug("Unable to complete bad message", e); abort(e); } } } protected boolean sendResponse(MetaData.Response response, ByteBuffer content, boolean complete, final Callback callback) { boolean committing = _state.commitResponse(); if (LOG.isDebugEnabled()) LOG.debug("sendResponse info={} content={} complete={} committing={} callback={}", response, BufferUtil.toDetailString(content), complete, committing, callback); if (committing) { // Let HttpChannel.Listeners modify the response before commit _combinedListener.onResponseBegin(_request); // We need an info to commit if (response == null) response = _response.newResponseMetaData(); commit(response); _request.onResponseCommit(); // wrap callback to process informational responses final int status = response.getStatus(); final Callback committed = HttpStatus.isInformational(status) ? new Send1XXCallback(callback) : new SendCallback(callback, content, true, complete); // committing write _transport.send(_request.getMetaData(), response, content, complete, committed); } else if (response == null) { // This is a normal write _transport.send(_request.getMetaData(), null, content, complete, new SendCallback(callback, content, false, complete)); } else { callback.failed(new IllegalStateException("committed")); } return committing; } public boolean sendResponse(MetaData.Response info, ByteBuffer content, boolean complete) throws IOException { try (Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback()) { boolean committing = sendResponse(info, content, complete, blocker); blocker.block(); return committing; } catch (Throwable failure) { if (LOG.isDebugEnabled()) LOG.debug("Unable to send response", failure); abort(failure); throw failure; } } protected void commit(MetaData.Response info) { _committedMetaData = info; if (LOG.isDebugEnabled()) LOG.debug("COMMIT for {} on {}{}{} {} {}{}{}", getRequest().getRequestURI(), this, System.lineSeparator(), info.getStatus(), info.getReason(), info.getHttpVersion(), System.lineSeparator(), info.getFields()); } public boolean isCommitted() { return _state.isResponseCommitted(); } /** * @return True if the request lifecycle is completed */ public boolean isRequestCompleted() { return _state.isCompleted(); } /** * @return True if the response is completely written. */ public boolean isResponseCompleted() { return _state.isResponseCompleted(); } public boolean isPersistent() { return _endPoint.isOpen(); } /** *

Non-Blocking write, committing the response if needed.

* Called as last link in HttpOutput.Filter chain * * @param content the content buffer to write * @param complete whether the content is complete for the response * @param callback Callback when complete or failed */ @Override public void write(ByteBuffer content, boolean complete, Callback callback) { sendResponse(null, content, complete, callback); } @Override public void resetBuffer() { if (isCommitted()) throw new IllegalStateException("Committed"); } @Override public HttpOutput.Interceptor getNextInterceptor() { return null; } protected void execute(Runnable task) { _executor.execute(task); } public Scheduler getScheduler() { return _connector.getScheduler(); } public boolean isUseInputDirectByteBuffers() { return getHttpConfiguration().isUseInputDirectByteBuffers(); } public boolean isUseOutputDirectByteBuffers() { return getHttpConfiguration().isUseOutputDirectByteBuffers(); } /** * If a write or similar operation to this channel fails, * then this method should be called. *

* The standard implementation calls {@link HttpTransport#abort(Throwable)}. * * @param failure the failure that caused the abort. */ public void abort(Throwable failure) { if (_state.abortResponse()) { _combinedListener.onResponseFailure(_request, failure); _transport.abort(failure); } } public boolean isTunnellingSupported() { return false; } public EndPoint getTunnellingEndPoint() { throw new UnsupportedOperationException("Tunnelling not supported"); } private void notifyEvent1(Function> function, Request request) { for (Listener listener : _transientListeners) { try { function.apply(listener).accept(request); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Failure invoking listener {}", listener, x); } } } private void notifyEvent2(Function> function, Request request, ByteBuffer content) { for (Listener listener : _transientListeners) { ByteBuffer view = content.slice(); try { function.apply(listener).accept(request, view); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Failure invoking listener {}", listener, x); } } } private void notifyEvent2(Function> function, Request request, Throwable failure) { for (Listener listener : _transientListeners) { try { function.apply(listener).accept(request, failure); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Failure invoking listener {}", listener, x); } } } interface Dispatchable { void dispatch() throws IOException, ServletException; } /** *

Listener for {@link HttpChannel} events.

*

HttpChannel will emit events for the various phases it goes through while * processing an HTTP request and response.

*

Implementations of this interface may listen to those events to track * timing and/or other values such as request URI, etc.

*

The events parameters, especially the {@link Request} object, may be * in a transient state depending on the event, and not all properties/features * of the parameters may be available inside a listener method.

*

It is recommended that the event parameters are not acted upon * in the listener methods, or undefined behavior may result. For example, it * would be a bad idea to try to read some content from the * {@link jakarta.servlet.ServletInputStream} in listener methods. On the other * hand, it is legit to store request attributes in one listener method that * may be possibly retrieved in another listener method in a later event.

*

Listener methods are invoked synchronously from the thread that is * performing the request processing, and they should not call blocking code * (otherwise the request processing will be blocked as well).

*

Listener instances that are set as a bean on the {@link Connector} are * efficiently added to {@link HttpChannel}. If additional listeners are added * using the deprecated {@link HttpChannel#addListener(Listener)}

method, * then an instance of {@link TransientListeners} must be added to the connector * in order for them to be invoked. */ public interface Listener extends EventListener { /** * Invoked just after the HTTP request line and headers have been parsed. * * @param request the request object */ default void onRequestBegin(Request request) { } /** * Invoked just before calling the application. * * @param request the request object */ default void onBeforeDispatch(Request request) { } /** * Invoked when the application threw an exception. * * @param request the request object * @param failure the exception thrown by the application */ default void onDispatchFailure(Request request, Throwable failure) { } /** * Invoked just after the application returns from the first invocation. * * @param request the request object */ default void onAfterDispatch(Request request) { } /** * Invoked every time a request content chunk has been parsed, just before * making it available to the application. * * @param request the request object * @param content a {@link ByteBuffer#slice() slice} of the request content chunk */ default void onRequestContent(Request request, ByteBuffer content) { } /** * Invoked when the end of the request content is detected. * * @param request the request object */ default void onRequestContentEnd(Request request) { } /** * Invoked when the request trailers have been parsed. * * @param request the request object */ default void onRequestTrailers(Request request) { } /** * Invoked when the request has been fully parsed. * * @param request the request object */ default void onRequestEnd(Request request) { } /** * Invoked when the request processing failed. * * @param request the request object * @param failure the request failure */ default void onRequestFailure(Request request, Throwable failure) { } /** * Invoked just before the response line is written to the network. * * @param request the request object */ default void onResponseBegin(Request request) { } /** * Invoked just after the response is committed (that is, the response * line, headers and possibly some content have been written to the * network). * * @param request the request object */ default void onResponseCommit(Request request) { } /** * Invoked after a response content chunk has been written to the network. * * @param request the request object * @param content a {@link ByteBuffer#slice() slice} of the response content chunk */ default void onResponseContent(Request request, ByteBuffer content) { } /** * Invoked when the response has been fully written. * * @param request the request object */ default void onResponseEnd(Request request) { } /** * Invoked when the response processing failed. * * @param request the request object * @param failure the response failure */ default void onResponseFailure(Request request, Throwable failure) { } /** * Invoked when the request and response processing are complete. * * @param request the request object */ default void onComplete(Request request) { } } private class SendCallback extends Callback.Nested { private final ByteBuffer _content; private final int _length; private final boolean _commit; private final boolean _complete; private SendCallback(Callback callback, ByteBuffer content, boolean commit, boolean complete) { super(callback); _content = content == null ? BufferUtil.EMPTY_BUFFER : content.slice(); _length = _content.remaining(); _commit = commit; _complete = complete; } @Override public void succeeded() { _written += _length; if (_commit) _combinedListener.onResponseCommit(_request); if (_length > 0) _combinedListener.onResponseContent(_request, _content); if (_complete && _state.completeResponse()) _combinedListener.onResponseEnd(_request); super.succeeded(); } @Override public void failed(final Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Commit failed", x); if (x instanceof BadMessageException) { _transport.send(_request.getMetaData(), HttpGenerator.RESPONSE_500_INFO, null, true, new Callback.Nested(this) { @Override public void succeeded() { _response.getHttpOutput().completed(null); super.failed(x); } @Override public void failed(Throwable th) { abort(x); super.failed(x); } }); } else { abort(x); super.failed(x); } } } private class Send1XXCallback extends SendCallback { private Send1XXCallback(Callback callback) { super(callback, null, false, false); } @Override public void succeeded() { if (_state.partialResponse()) super.succeeded(); else super.failed(new IllegalStateException()); } } /** * A Listener instance that can be added as a bean to {@link AbstractConnector} so that * the listeners obtained from HttpChannel{@link #getTransientListeners()} */ @Deprecated public static class TransientListeners implements Listener { @Override public void onRequestBegin(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onRequestBegin, request); } @Override public void onBeforeDispatch(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onBeforeDispatch, request); } @Override public void onDispatchFailure(Request request, Throwable failure) { request.getHttpChannel().notifyEvent2(listener -> listener::onDispatchFailure, request, failure); } @Override public void onAfterDispatch(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onAfterDispatch, request); } @Override public void onRequestContent(Request request, ByteBuffer content) { request.getHttpChannel().notifyEvent2(listener -> listener::onRequestContent, request, content); } @Override public void onRequestContentEnd(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onRequestContentEnd, request); } @Override public void onRequestTrailers(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onRequestTrailers, request); } @Override public void onRequestEnd(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onRequestEnd, request); } @Override public void onRequestFailure(Request request, Throwable failure) { request.getHttpChannel().notifyEvent2(listener -> listener::onRequestFailure, request, failure); } @Override public void onResponseBegin(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onResponseBegin, request); } @Override public void onResponseCommit(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onResponseCommit, request); } @Override public void onResponseContent(Request request, ByteBuffer content) { request.getHttpChannel().notifyEvent2(listener -> listener::onResponseContent, request, content); } @Override public void onResponseEnd(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onResponseEnd, request); } @Override public void onResponseFailure(Request request, Throwable failure) { request.getHttpChannel().notifyEvent2(listener -> listener::onResponseFailure, request, failure); } @Override public void onComplete(Request request) { request.getHttpChannel().notifyEvent1(listener -> listener::onComplete, request); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy