org.eclipse.jetty.ee8.nested.HttpInput Maven / Gradle / Ivy
Show all versions of jetty-ee8-nested Show documentation
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee8.nested;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.atomic.LongAdder;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.server.Context;
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.eclipse.jetty.util.thread.Invocable;
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
* Context#run(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 HttpChannel _httpChannel;
private final LongAdder _contentConsumed = new LongAdder();
private volatile ContentProducer _contentProducer;
private volatile boolean _consumedEof;
private volatile ReadListener _readListener;
public HttpInput(HttpChannel channel) {
_httpChannel = channel;
_asyncContentProducer = new AsyncContentProducer(_httpChannel);
_blockingContentProducer = new BlockingContentProducer(_asyncContentProducer);
_contentProducer = _blockingContentProducer;
}
public void recycle() {
try (AutoLock lock = _contentProducer.lock()) {
if (LOG.isDebugEnabled())
LOG.debug("recycle {}", this);
_blockingContentProducer.recycle();
_readListener = null;
}
}
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) {
length = Math.min(content.remaining(), 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 (!_httpChannel.getState().isAsyncStarted())
throw new IllegalStateException("Async not started");
_readListener = Objects.requireNonNull(readListener);
_contentProducer = _asyncContentProducer;
// trigger content production
if (// onReadEof b/c we want to transition from WAITING to WOKEN
isReady() && _httpChannel.getState().onReadEof())
// this is needed by AsyncServletIOTest.testStolenAsyncRead
scheduleReadListenerNotification();
}
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()) {
// Don't try to get content if no bytes were requested to be read.
if (len == 0)
return 0;
// 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.execute(_httpChannel);
}
/**
* 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;
}
}
public Invocable.InvocationType getInvocationType() {
// This is the invocation type used for demand callbacks.
// If we are blocking mode, then we implement the callbacks, which just wake up the blocked application thread
// If we are async mode, then we need to ask the read listener (which will probably be seen as blocking as
// the InvocationType API is normally hidden from a web application.
// TODO is there another way for an app to promise its callbacks are not blocking?
return _readListener == null ? // blocking reads have non blocking callbacks
Invocable.InvocationType.NON_BLOCKING : Invocable.getInvocationType(_readListener);
}
/* 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?
_httpChannel.getResponse().getHttpFields().add(HttpFields.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=" + _httpChannel.getState() + " 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.
*
*
* @deprecated Interceptor has been removed with no replacement in the EE10 or later implementations
*/
@Deprecated(forRemoval = true)
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 context bytes by {@link AsyncContentProducer#nextContent()}.
* 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 {
public static Content asChunk(org.eclipse.jetty.io.Content.Chunk chunk) {
if (org.eclipse.jetty.io.Content.Chunk.isFailure(chunk))
return new ErrorContent(chunk.getFailure());
if (chunk.isLast() && !chunk.hasRemaining())
return new EofContent();
Content content = new Content(chunk.getByteBuffer()) {
@Override
public void succeeded() {
chunk.release();
super.succeeded();
}
@Override
public void failed(Throwable x) {
chunk.release();
super.failed(x);
}
};
return chunk.isLast() ? new WrappingContent(content, true) : content;
}
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 + "]";
}
}
}