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

org.eclipse.jetty.server.HttpInput 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.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.atomic.LongAdder;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link * ContextHandler#handle(Runnable)} to setup classloaders etc.

*/ public class HttpInput extends ServletInputStream implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(HttpInput.class); private final byte[] _oneByteBuffer = new byte[1]; private final BlockingContentProducer _blockingContentProducer; private final AsyncContentProducer _asyncContentProducer; private final HttpChannelState _channelState; private final LongAdder _contentConsumed = new LongAdder(); private volatile ContentProducer _contentProducer; private volatile boolean _consumedEof; private volatile ReadListener _readListener; public HttpInput(HttpChannelState state) { _channelState = state; _asyncContentProducer = new AsyncContentProducer(state.getHttpChannel()); _blockingContentProducer = new BlockingContentProducer(_asyncContentProducer); _contentProducer = _blockingContentProducer; } public void recycle() { try (AutoLock lock = _contentProducer.lock()) { if (LOG.isDebugEnabled()) LOG.debug("recycle {}", this); _blockingContentProducer.recycle(); } } public void reopen() { try (AutoLock lock = _contentProducer.lock()) { if (LOG.isDebugEnabled()) LOG.debug("reopen {}", this); _blockingContentProducer.reopen(); _contentProducer = _blockingContentProducer; _consumedEof = false; _readListener = null; _contentConsumed.reset(); } } /** * @return The current Interceptor, or null if none set */ public Interceptor getInterceptor() { try (AutoLock lock = _contentProducer.lock()) { return _contentProducer.getInterceptor(); } } /** * Set the interceptor. * * @param interceptor The interceptor to use. */ public void setInterceptor(Interceptor interceptor) { try (AutoLock lock = _contentProducer.lock()) { if (LOG.isDebugEnabled()) LOG.debug("setting interceptor to {} on {}", interceptor, this); _contentProducer.setInterceptor(interceptor); } } /** * Set the {@link Interceptor}, chaining it to the existing one if * an {@link Interceptor} is already set. * * @param interceptor the next {@link Interceptor} in a chain */ public void addInterceptor(Interceptor interceptor) { try (AutoLock lock = _contentProducer.lock()) { Interceptor currentInterceptor = _contentProducer.getInterceptor(); if (currentInterceptor == null) { if (LOG.isDebugEnabled()) LOG.debug("adding single interceptor: {} on {}", interceptor, this); _contentProducer.setInterceptor(interceptor); } else { ChainedInterceptor chainedInterceptor = new ChainedInterceptor(currentInterceptor, interceptor); if (LOG.isDebugEnabled()) LOG.debug("adding chained interceptor: {} on {}", chainedInterceptor, this); _contentProducer.setInterceptor(chainedInterceptor); } } } private int get(Content content, byte[] bytes, int offset, int length) { int consumed = content.get(bytes, offset, length); _contentConsumed.add(consumed); return consumed; } private int get(Content content, ByteBuffer des) { var capacity = des.remaining(); var src = content.getByteBuffer(); if (src.remaining() > capacity) { int limit = src.limit(); src.limit(src.position() + capacity); des.put(src); src.limit(limit); } else { des.put(src); } var consumed = capacity - des.remaining(); _contentConsumed.add(consumed); return consumed; } public long getContentConsumed() { return _contentConsumed.sum(); } public long getContentReceived() { try (AutoLock lock = _contentProducer.lock()) { return _contentProducer.getRawContentArrived(); } } public boolean consumeAll() { try (AutoLock lock = _contentProducer.lock()) { if (LOG.isDebugEnabled()) LOG.debug("consumeAll {}", this); boolean atEof = _contentProducer.consumeAll(); if (atEof) _consumedEof = true; if (isFinished()) return !isError(); return false; } } public boolean isError() { try (AutoLock lock = _contentProducer.lock()) { boolean error = _contentProducer.isError(); if (LOG.isDebugEnabled()) LOG.debug("isError={} {}", error, this); return error; } } public boolean isAsync() { if (LOG.isDebugEnabled()) LOG.debug("isAsync read listener {} {}", _readListener, this); return _readListener != null; } /* ServletInputStream */ @Override public boolean isFinished() { boolean finished = _consumedEof; if (LOG.isDebugEnabled()) LOG.debug("isFinished={} {}", finished, this); return finished; } @Override public boolean isReady() { try (AutoLock lock = _contentProducer.lock()) { boolean ready = _contentProducer.isReady(); if (LOG.isDebugEnabled()) LOG.debug("isReady={} {}", ready, this); return ready; } } @Override public void setReadListener(ReadListener readListener) { if (LOG.isDebugEnabled()) LOG.debug("setting read listener to {} {}", readListener, this); if (_readListener != null) throw new IllegalStateException("ReadListener already set"); //illegal if async not started if (!_channelState.isAsyncStarted()) throw new IllegalStateException("Async not started"); _readListener = Objects.requireNonNull(readListener); _contentProducer = _asyncContentProducer; // trigger content production if (isReady() && _channelState.onReadEof()) // onReadEof b/c we want to transition from WAITING to WOKEN scheduleReadListenerNotification(); // this is needed by AsyncServletIOTest.testStolenAsyncRead } public boolean onContentProducible() { try (AutoLock lock = _contentProducer.lock()) { return _contentProducer.onContentProducible(); } } @Override public int read() throws IOException { try (AutoLock lock = _contentProducer.lock()) { int read = read(_oneByteBuffer, 0, 1); if (read == 0) throw new IOException("unready read=0"); return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF; } } @Override public int read(byte[] b, int off, int len) throws IOException { return read(null, b, off, len); } public int read(ByteBuffer buffer) throws IOException { return read(buffer, null, -1, -1); } private int read(ByteBuffer buffer, byte[] b, int off, int len) throws IOException { try (AutoLock lock = _contentProducer.lock()) { // Calculate minimum request rate for DoS protection _contentProducer.checkMinDataRate(); Content content = _contentProducer.nextContent(); if (content == null) throw new IllegalStateException("read on unready input"); if (!content.isSpecial()) { int read = buffer == null ? get(content, b, off, len) : get(content, buffer); if (LOG.isDebugEnabled()) LOG.debug("read produced {} byte(s) {}", read, this); if (content.isEmpty()) _contentProducer.reclaim(content); return read; } Throwable error = content.getError(); if (LOG.isDebugEnabled()) LOG.debug("read error={} {}", error, this); if (error != null) { if (error instanceof IOException) throw (IOException)error; throw new IOException(error); } if (content.isEof()) { if (LOG.isDebugEnabled()) LOG.debug("read at EOF, setting consumed EOF to true {}", this); _consumedEof = true; // If EOF do we need to wake for allDataRead callback? if (onContentProducible()) scheduleReadListenerNotification(); return -1; } throw new AssertionError("no data, no error and not EOF"); } } private void scheduleReadListenerNotification() { HttpChannel channel = _channelState.getHttpChannel(); channel.execute(channel); } /** * Check if this HttpInput instance has content stored internally, without fetching/parsing * anything from the underlying channel. * @return true if the input contains content, false otherwise. */ public boolean hasContent() { try (AutoLock lock = _contentProducer.lock()) { // Do not call _contentProducer.available() as it calls HttpChannel.produceContent() // which is forbidden by this method's contract. boolean hasContent = _contentProducer.hasContent(); if (LOG.isDebugEnabled()) LOG.debug("hasContent={} {}", hasContent, this); return hasContent; } } @Override public int available() { try (AutoLock lock = _contentProducer.lock()) { int available = _contentProducer.available(); if (LOG.isDebugEnabled()) LOG.debug("available={} {}", available, this); return available; } } /* Runnable */ /* *

While this class is-a Runnable, it should never be dispatched in it's own thread. It is a runnable only so that the calling thread can use {@link * ContextHandler#handle(Runnable)} to setup classloaders etc.

*/ @Override public void run() { Content content; try (AutoLock lock = _contentProducer.lock()) { // Call isReady() to make sure that if not ready we register for fill interest. if (!_contentProducer.isReady()) { if (LOG.isDebugEnabled()) LOG.debug("running but not ready {}", this); return; } content = _contentProducer.nextContent(); if (LOG.isDebugEnabled()) LOG.debug("running on content {} {}", content, this); } // This check is needed when a request is started async but no read listener is registered. if (_readListener == null) { if (LOG.isDebugEnabled()) LOG.debug("running without a read listener {}", this); onContentProducible(); return; } if (content.isSpecial()) { Throwable error = content.getError(); if (error != null) { if (LOG.isDebugEnabled()) LOG.debug("running error={} {}", error, this); // TODO is this necessary to add here? _channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE); _readListener.onError(error); } else if (content.isEof()) { try { if (LOG.isDebugEnabled()) LOG.debug("running at EOF {}", this); _readListener.onAllDataRead(); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("running failed onAllDataRead {}", this, x); _readListener.onError(x); } } } else { if (LOG.isDebugEnabled()) LOG.debug("running has content {}", this); try { _readListener.onDataAvailable(); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("running failed onDataAvailable {}", this, x); _readListener.onError(x); } } } @Override public String toString() { return getClass().getSimpleName() + "@" + hashCode() + " cs=" + _channelState + " cp=" + _contentProducer + " eof=" + _consumedEof; } /** *

{@link Content} interceptor that can be registered using {@link #setInterceptor(Interceptor)} or * {@link #addInterceptor(Interceptor)}. * When {@link Content} instances are generated, they are passed to the registered interceptor (if any) * that is then responsible for providing the actual content that is consumed by {@link #read(byte[], int, int)} and its * sibling methods.

* A minimal implementation could be as simple as: *
     * public HttpInput.Content readFrom(HttpInput.Content content)
     * {
     *     LOGGER.debug("read content: {}", asString(content));
     *     return content;
     * }
     * 
* which would not do anything with the content besides logging it. A more involved implementation could look like the * following: *
     * public HttpInput.Content readFrom(HttpInput.Content content)
     * {
     *     if (content.hasContent())
     *         this.processedContent = processContent(content.getByteBuffer());
     *     if (content.isEof())
     *         disposeResources();
     *     return content.isSpecial() ? content : this.processedContent;
     * }
     * 
* Implementors of this interface must keep the following in mind: *
    *
  • Calling {@link Content#getByteBuffer()} when {@link Content#isSpecial()} returns true throws * {@link IllegalStateException}.
  • *
  • A {@link Content} can both be non-special and have {@link Content#isEof()} return true.
  • *
  • {@link Content} extends {@link Callback} to manage the lifecycle of the contained byte buffer. The code calling * {@link #readFrom(Content)} is responsible for managing the lifecycle of both the passed and the returned content * instances, once {@link ByteBuffer#hasRemaining()} returns false {@link HttpInput} will make sure * {@link Callback#succeeded()} is called, or {@link Callback#failed(Throwable)} if an error occurs.
  • *
  • After {@link #readFrom(Content)} is called for the first time, subsequent {@link #readFrom(Content)} calls will * occur only after the contained byte buffer is empty (see above) or at any time if the returned content was special.
  • *
  • Once {@link #readFrom(Content)} returned a special content, subsequent calls to {@link #readFrom(Content)} must * always return the same special content.
  • *
  • Implementations implementing both this interface and {@link Destroyable} will have their * {@link Destroyable#destroy()} method called when {@link #recycle()} is called.
  • *
* @see org.eclipse.jetty.server.handler.gzip.GzipHttpInputInterceptor */ public interface Interceptor { /** * @param content The content to be intercepted. * The content will be modified with any data the interceptor consumes. There is no requirement * that all the data is consumed by the interceptor but at least one byte must be consumed * unless the returned content is the passed content instance. * @return The intercepted content or null if interception is completed for that content. */ Content readFrom(Content content); } /** * An {@link Interceptor} that chains two other {@link Interceptor}s together. * The {@link Interceptor#readFrom(Content)} calls the previous {@link Interceptor}'s * {@link Interceptor#readFrom(Content)} and then passes any {@link Content} returned * to the next {@link Interceptor}. */ private static class ChainedInterceptor implements Interceptor, Destroyable { private final Interceptor _prev; private final Interceptor _next; ChainedInterceptor(Interceptor prev, Interceptor next) { _prev = prev; _next = next; } Interceptor getPrev() { return _prev; } Interceptor getNext() { return _next; } @Override public Content readFrom(Content content) { Content c = getPrev().readFrom(content); if (c == null) return null; return getNext().readFrom(c); } @Override public void destroy() { if (_prev instanceof Destroyable) ((Destroyable)_prev).destroy(); if (_next instanceof Destroyable) ((Destroyable)_next).destroy(); } @Override public String toString() { return getClass().getSimpleName() + "@" + hashCode() + " [p=" + _prev + ",n=" + _next + "]"; } } /** * A content represents the production of a {@link HttpChannel} returned by {@link HttpChannel#produceContent()}. * There are two fundamental types of content: special and non-special. * Non-special content always wraps a byte buffer that can be consumed and must be recycled once it is empty, either * via {@link #succeeded()} or {@link #failed(Throwable)}. * Special content indicates a special event, like EOF or an error and never wraps a byte buffer. Calling * {@link #succeeded()} or {@link #failed(Throwable)} on those have no effect. */ public static class Content implements Callback { protected final ByteBuffer _content; public Content(ByteBuffer content) { _content = content; } /** * Get the wrapped byte buffer. Throws {@link IllegalStateException} if the content is special. * @return the wrapped byte buffer. */ public ByteBuffer getByteBuffer() { return _content; } @Override public InvocationType getInvocationType() { return InvocationType.NON_BLOCKING; } /** * Read the wrapped byte buffer. Throws {@link IllegalStateException} if the content is special. * @param buffer The array into which bytes are to be written. * @param offset The offset within the array of the first byte to be written. * @param length The maximum number of bytes to be written to the given array. * @return The amount of bytes read from the buffer. */ public int get(byte[] buffer, int offset, int length) { length = Math.min(_content.remaining(), length); _content.get(buffer, offset, length); return length; } /** * Skip some bytes from the buffer. Has no effect on a special content. * @param length How many bytes to skip. * @return How many bytes were skipped. */ public int skip(int length) { length = Math.min(_content.remaining(), length); _content.position(_content.position() + length); return length; } /** * Check if there is at least one byte left in the buffer. * Always false on a special content. * @return true if there is at least one byte left in the buffer. */ public boolean hasContent() { return _content.hasRemaining(); } /** * Get the number of bytes remaining in the buffer. * Always 0 on a special content. * @return the number of bytes remaining in the buffer. */ public int remaining() { return _content.remaining(); } /** * Check if the buffer is empty. * Always true on a special content. * @return true if there is 0 byte left in the buffer. */ public boolean isEmpty() { return !_content.hasRemaining(); } /** * Check if the content is special. A content is deemed special * if it does not hold bytes but rather conveys a special event, * like when EOF has been reached or an error has occurred. * @return true if the content is special, false otherwise. */ public boolean isSpecial() { return false; } /** * Check if EOF was reached. Both special and non-special content * can have this flag set to true but in the case of non-special content, * this can be interpreted as a hint as it is always going to be followed * by another content that is both special and EOF. * @return true if EOF was reached, false otherwise. */ public boolean isEof() { return false; } /** * Get the reported error. Only special contents can have an error. * @return the error or null if there is none. */ public Throwable getError() { return null; } @Override public String toString() { return String.format("%s@%x{%s,spc=%s,eof=%s,err=%s}", getClass().getSimpleName(), hashCode(), BufferUtil.toDetailString(_content), isSpecial(), isEof(), getError()); } } /** * Simple non-special content wrapper allow overriding the EOF flag. */ public static class WrappingContent extends Content { private final Content _delegate; private final boolean _eof; public WrappingContent(Content delegate, boolean eof) { super(delegate.getByteBuffer()); _delegate = delegate; _eof = eof; } @Override public boolean isEof() { return _eof; } @Override public void succeeded() { _delegate.succeeded(); } @Override public void failed(Throwable x) { _delegate.failed(x); } @Override public InvocationType getInvocationType() { return _delegate.getInvocationType(); } } /** * Abstract class that implements the standard special content behavior. */ public abstract static class SpecialContent extends Content { public SpecialContent() { super(null); } @Override public final ByteBuffer getByteBuffer() { throw new IllegalStateException(this + " has no buffer"); } @Override public final int get(byte[] buffer, int offset, int length) { throw new IllegalStateException(this + " has no buffer"); } @Override public final int skip(int length) { return 0; } @Override public final boolean hasContent() { return false; } @Override public final int remaining() { return 0; } @Override public final boolean isEmpty() { return true; } @Override public final boolean isSpecial() { return true; } } /** * EOF special content. */ public static final class EofContent extends SpecialContent { @Override public boolean isEof() { return true; } @Override public String toString() { return getClass().getSimpleName(); } } /** * Error special content. */ public static final class ErrorContent extends SpecialContent { private final Throwable _error; public ErrorContent(Throwable error) { _error = error; } @Override public Throwable getError() { return _error; } @Override public String toString() { return getClass().getSimpleName() + " [" + _error + "]"; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy