org.eclipse.jetty.server.HttpInput Maven / Gradle / Ivy
Show all versions of jetty-server Show documentation
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
*
* Content may arrive in patterns such as [content(), content(), messageComplete()]
* so that this class maintains two states: the content state that tells
* whether there is content to consume and the EOF state that tells whether an EOF has arrived.
* Only once the content has been consumed the content state is moved to the EOF state.
*
*/
public class HttpInput extends ServletInputStream implements Runnable
{
/**
* An interceptor for HTTP Request input.
*
* Unlike InputStream wrappers that can be applied by filters, an interceptor
* is entirely transparent and works with async IO APIs.
*
*
* An Interceptor may consume data from the passed content and the interceptor
* will continue to be called for the same content until the interceptor returns
* null or an empty content. Thus even if the passed content is completely consumed
* the interceptor will be called with the same content until it can no longer
* produce more content.
*
* @see HttpInput#setInterceptor(Interceptor)
* @see HttpInput#addInterceptor(Interceptor)
*/
public interface Interceptor
{
/**
* @param content The content to be intercepted (may be empty or a {@link SentinelContent}.
* The content will be modified with any data the interceptor consumes, but there is no requirement
* that all the data is consumed by the interceptor.
* @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}.
*/
public static class ChainedInterceptor implements Interceptor, Destroyable
{
private final Interceptor _prev;
private final Interceptor _next;
public ChainedInterceptor(Interceptor prev, Interceptor next)
{
_prev = prev;
_next = next;
}
public Interceptor getPrev()
{
return _prev;
}
public Interceptor getNext()
{
return _next;
}
@Override
public Content readFrom(Content content)
{
return getNext().readFrom(getPrev().readFrom(content));
}
@Override
public void destroy()
{
if (_prev instanceof Destroyable)
((Destroyable)_prev).destroy();
if (_next instanceof Destroyable)
((Destroyable)_next).destroy();
}
}
private final static Logger LOG = Log.getLogger(HttpInput.class);
final static Content EOF_CONTENT = new EofContent("EOF");
final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
private final byte[] _oneByteBuffer = new byte[1];
private Content _content;
private Content _intercepted;
private final Deque _inputQ = new ArrayDeque<>();
private final HttpChannelState _channelState;
private ReadListener _listener;
private State _state = STREAM;
private long _firstByteTimeStamp = -1;
private long _contentArrived;
private long _contentConsumed;
private long _blockUntil;
private boolean _waitingForContent;
private Interceptor _interceptor;
public HttpInput(HttpChannelState state)
{
_channelState = state;
}
protected HttpChannelState getHttpChannelState()
{
return _channelState;
}
public void recycle()
{
synchronized (_inputQ)
{
if (_content!=null)
_content.failed(null);
_content = null;
Content item = _inputQ.poll();
while (item != null)
{
item.failed(null);
item = _inputQ.poll();
}
_listener = null;
_state = STREAM;
_contentArrived = 0;
_contentConsumed = 0;
_firstByteTimeStamp = -1;
_blockUntil = 0;
_waitingForContent = false;
if (_interceptor instanceof Destroyable)
((Destroyable)_interceptor).destroy();
_interceptor = null;
}
}
/**
* @return The current Interceptor, or null if none set
*/
public Interceptor getInterceptor()
{
return _interceptor;
}
/**
* Set the interceptor.
* @param interceptor The interceptor to use.
*/
public void setInterceptor(Interceptor interceptor)
{
_interceptor = interceptor;
}
/**
* Set the {@link Interceptor}, using a {@link ChainedInterceptor} if
* an {@link Interceptor} is already set.
* @param interceptor the next {@link Interceptor} in a chain
*/
public void addInterceptor(Interceptor interceptor)
{
if (_interceptor == null)
_interceptor = interceptor;
else
_interceptor = new ChainedInterceptor(_interceptor,interceptor);
}
@Override
public int available()
{
int available = 0;
boolean woken = false;
synchronized (_inputQ)
{
if (_content == null)
_content = _inputQ.poll();
if (_content == null)
{
try
{
produceContent();
}
catch (IOException e)
{
woken = failed(e);
}
if (_content == null)
_content = _inputQ.poll();
}
if (_content != null)
available = _content.remaining();
}
if (woken)
wake();
return available;
}
protected void wake()
{
HttpChannel channel = _channelState.getHttpChannel();
Executor executor = channel.getConnector().getServer().getThreadPool();
executor.execute(channel);
}
private long getBlockingTimeout()
{
return getHttpChannelState().getHttpChannel().getHttpConfiguration().getBlockingTimeout();
}
@Override
public int read() throws IOException
{
int read = read(_oneByteBuffer,0,1);
if (read == 0)
throw new IllegalStateException("unready read=0");
return read < 0?-1:_oneByteBuffer[0] & 0xFF;
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
boolean wake = false;
int l;
synchronized (_inputQ)
{
if (!isAsync())
{
// Setup blocking only if not async
if (_blockUntil == 0)
{
long blockingTimeout = getBlockingTimeout();
if (blockingTimeout > 0)
_blockUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(blockingTimeout);
}
}
// Calculate minimum request rate for DOS protection
long minRequestDataRate = _channelState.getHttpChannel().getHttpConfiguration().getMinRequestDataRate();
if (minRequestDataRate > 0 && _firstByteTimeStamp != -1)
{
long period = System.nanoTime() - _firstByteTimeStamp;
if (period > 0)
{
long minimum_data = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1);
if (_contentArrived < minimum_data)
{
BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,
String.format("Request content data rate < %d B/s", minRequestDataRate));
_channelState.getHttpChannel().abort(bad);
throw bad;
}
}
}
// Consume content looking for bytes to read
while (true)
{
Content item = nextContent();
if (item != null)
{
l = get(item,b,off,len);
if (LOG.isDebugEnabled())
LOG.debug("{} read {} from {}",this,l,item);
// Consume any following poison pills
if (item.isEmpty())
nextInterceptedContent();
break;
}
// No content, so should we block?
if (!_state.blockForContent(this))
{
// Not blocking, so what should we return?
l = _state.noContent();
if (l<0)
// If EOF do we need to wake for allDataRead callback?
wake = _channelState.onReadEof();
break;
}
}
}
if (wake)
wake();
return l;
}
/**
* Called when derived implementations should attempt to produce more Content and add it via {@link #addContent(Content)}. For protocols that are constantly
* producing (eg HTTP2) this can be left as a noop;
*
* @throws IOException
* if unable to produce content
*/
protected void produceContent() throws IOException
{
}
/**
* Called by channel when asynchronous IO needs to produce more content
* @throws IOException
* if unable to produce content
*/
public void asyncReadProduce() throws IOException
{
synchronized (_inputQ)
{
produceContent();
}
}
/**
* Get the next content from the inputQ, calling {@link #produceContent()} if need be. EOF is processed and state changed.
*
* @return the content or null if none available.
* @throws IOException
* if retrieving the content fails
*/
protected Content nextContent() throws IOException
{
Content content = nextNonSentinelContent();
if (content == null && !isFinished())
{
produceContent();
content = nextNonSentinelContent();
}
return content;
}
/**
* Poll the inputQ for Content. Consumed buffers and {@link SentinelContent}s are removed and EOF state updated if need be.
*
* @return Content or null
*/
protected Content nextNonSentinelContent()
{
while (true)
{
// Get the next content (or EOF)
Content content = nextInterceptedContent();
// If it is EOF, consume it here
if (content instanceof SentinelContent)
{
// Consume the EOF content, either if it was original content
// or if it was produced by interception
consume(content);
continue;
}
return content;
}
}
/**
* Get the next readable from the inputQ, calling {@link #produceContent()} if need be. EOF is NOT processed and state is not changed.
*
* @return the content or EOF or null if none available.
* @throws IOException
* if retrieving the content fails
*/
protected Content produceNextContext() throws IOException
{
Content content = nextInterceptedContent();
if (content == null && !isFinished())
{
produceContent();
content = nextInterceptedContent();
}
return content;
}
/**
* Poll the inputQ for Content or EOF. Consumed buffers and non EOF {@link SentinelContent}s are removed. EOF state is not updated.
* Interception is done within this method.
* @return Content with remaining, a {@link SentinelContent}, or null
*/
protected Content nextInterceptedContent()
{
// If we have a chunk produced by interception
if (_intercepted!=null)
{
// Use it if it has any remaining content
if (_intercepted.hasContent())
return _intercepted;
// succeed the chunk
_intercepted.succeeded();
_intercepted=null;
}
// If we don't have a Content under consideration, get
// the next one off the input Q.
if (_content == null)
_content = _inputQ.poll();
// While we have content to consider.
while (_content!=null)
{
// Are we intercepting?
if (_interceptor!=null)
{
// Intercept the current content (may be called several
// times for the same content
_intercepted = _interceptor.readFrom(_content);
// If interception produced new content
if (_intercepted!=null && _intercepted!=_content)
{
// if it is not empty use it
if (_intercepted.hasContent())
return _intercepted;
_intercepted.succeeded();
}
// intercepted content consumed
_intercepted=null;
// fall through so that the unintercepted _content is
// considered for any remaining content, for EOF and to
// succeed it if it is entirely consumed.
}
// If the content has content or is an EOF marker, use it
if (_content.hasContent() || _content instanceof SentinelContent)
return _content;
// The content is consumed, so get the next one. Note that EOF
// content is never consumed here, but in #pollContent
_content.succeeded();
_content = _inputQ.poll();
}
return null;
}
private void consume(Content content)
{
if (!isError() && content instanceof EofContent)
{
if (content == EARLY_EOF_CONTENT)
_state = EARLY_EOF;
else if (_listener == null)
_state = EOF;
else
_state = AEOF;
}
// Consume the content, either if it was original content
// or if it was produced by interception
content.succeeded();
if (_content==content)
_content = null;
else if (_intercepted==content)
_intercepted = null;
}
/**
* Copies the given content into the given byte buffer.
*
* @param content
* the content to copy from
* @param buffer
* the buffer to copy into
* @param offset
* the buffer offset to start copying from
* @param length
* the space available in the buffer
* @return the number of bytes actually copied
*/
protected int get(Content content, byte[] buffer, int offset, int length)
{
int l = content.get(buffer,offset,length);
_contentConsumed += l;
return l;
}
/**
* Consumes the given content. Calls the content succeeded if all content consumed.
*
* @param content
* the content to consume
* @param length
* the number of bytes to consume
*/
protected void skip(Content content, int length)
{
int l = content.skip(length);
_contentConsumed += l;
if (l > 0 && content.isEmpty())
nextNonSentinelContent(); // hungry succeed
}
/**
* Blocks until some content or some end-of-file event arrives.
*
* @throws IOException if the wait is interrupted
*/
protected void blockForContent() throws IOException
{
try
{
_waitingForContent = true;
_channelState.getHttpChannel().onBlockWaitForContent();
boolean loop = false;
long timeout = 0;
while (true)
{
if (_blockUntil != 0)
{
timeout = TimeUnit.NANOSECONDS.toMillis(_blockUntil - System.nanoTime());
if (timeout <= 0)
throw new TimeoutException(String.format("Blocking timeout %d ms", getBlockingTimeout()));
}
// This method is called from a loop, so we just
// need to check the timeout before and after waiting.
if (loop)
break;
if (LOG.isDebugEnabled())
LOG.debug("{} blocking for content timeout={}", this, timeout);
if (timeout > 0)
_inputQ.wait(timeout);
else
_inputQ.wait();
loop = true;
}
}
catch (Throwable x)
{
_channelState.getHttpChannel().onBlockWaitForContentFailure(x);
}
}
/**
* Adds some content to this input stream.
*
* @param content
* the content to add
* @return true if content channel woken for read
*/
public boolean addContent(Content content)
{
synchronized (_inputQ)
{
_waitingForContent = false;
if (_firstByteTimeStamp == -1)
_firstByteTimeStamp = System.nanoTime();
if (isFinished())
{
Throwable failure = isError() ? _state.getError() : new EOFException("Content after EOF");
content.failed(failure);
return false;
}
else
{
_contentArrived += content.remaining();
if (_content==null && _inputQ.isEmpty())
_content=content;
else
_inputQ.offer(content);
if (LOG.isDebugEnabled())
LOG.debug("{} addContent {}",this,content);
if (nextInterceptedContent()!=null)
return wakeup();
else
return false;
}
}
}
public boolean hasContent()
{
synchronized (_inputQ)
{
return _content!=null || _inputQ.size() > 0;
}
}
public void unblock()
{
synchronized (_inputQ)
{
_inputQ.notify();
}
}
public long getContentConsumed()
{
synchronized (_inputQ)
{
return _contentConsumed;
}
}
/**
* This method should be called to signal that an EOF has been detected before all the expected content arrived.
*
* Typically this will result in an EOFException being thrown from a subsequent read rather than a -1 return.
*
* @return true if content channel woken for read
*/
public boolean earlyEOF()
{
return addContent(EARLY_EOF_CONTENT);
}
/**
* This method should be called to signal that all the expected content arrived.
*
* @return true if content channel woken for read
*/
public boolean eof()
{
return addContent(EOF_CONTENT);
}
public boolean consumeAll()
{
synchronized (_inputQ)
{
try
{
while (true)
{
Content item = nextContent();
if (item == null)
break; // Let's not bother blocking
skip(item, item.remaining());
}
if (isFinished())
return !isError();
_state = EARLY_EOF;
return false;
}
catch (Throwable e)
{
LOG.debug(e);
_state = new ErrorState(e);
return false;
}
}
}
public boolean isError()
{
synchronized (_inputQ)
{
return _state instanceof ErrorState;
}
}
public boolean isAsync()
{
synchronized (_inputQ)
{
return _state == ASYNC;
}
}
@Override
public boolean isFinished()
{
synchronized (_inputQ)
{
return _state instanceof EOFState;
}
}
@Override
public boolean isReady()
{
try
{
synchronized (_inputQ)
{
if (_listener == null)
return true;
if (_state instanceof EOFState)
return true;
if (_waitingForContent)
return false;
if (produceNextContext() != null)
return true;
_channelState.onReadUnready();
_waitingForContent = true;
}
return false;
}
catch (IOException e)
{
LOG.ignore(e);
return true;
}
}
@Override
public void setReadListener(ReadListener readListener)
{
boolean woken = false;
try
{
synchronized (_inputQ)
{
if (_listener != null)
throw new IllegalStateException("ReadListener already set");
_listener = Objects.requireNonNull(readListener);
Content content = produceNextContext();
if (content!=null)
{
_state = ASYNC;
woken = _channelState.onReadReady();
}
else if (_state == EOF)
{
_state = AEOF;
woken = _channelState.onReadEof();
}
else
{
_state = ASYNC;
_channelState.onReadUnready();
_waitingForContent = true;
}
}
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
if (woken)
wake();
}
public boolean onIdleTimeout(Throwable x)
{
synchronized (_inputQ)
{
boolean neverDispatched = getHttpChannelState().isIdle();
if ((_waitingForContent || neverDispatched) && !isError())
{
x.addSuppressed(new Throwable("HttpInput idle timeout"));
_state = new ErrorState(x);
return wakeup();
}
return false;
}
}
public boolean failed(Throwable x)
{
synchronized (_inputQ)
{
// Errors may be reported multiple times, for example
// a local idle timeout and a remote I/O failure.
if (isError())
{
if (LOG.isDebugEnabled())
{
// Log both the original and current failure
// without modifying the original failure.
Throwable failure = new Throwable(_state.getError());
failure.addSuppressed(x);
LOG.debug(failure);
}
}
else
{
// Add a suppressed throwable to capture this stack
// trace without wrapping/hiding the original failure.
x.addSuppressed(new Throwable("HttpInput failure"));
_state = new ErrorState(x);
}
return wakeup();
}
}
private boolean wakeup()
{
if (_listener != null)
return _channelState.onContentAdded();
_inputQ.notify();
return false;
}
/*
*
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()
{
final ReadListener listener;
Throwable error;
boolean aeof = false;
synchronized (_inputQ)
{
listener = _listener;
if (_state == EOF)
return;
if (_state==AEOF)
{
_state = EOF;
aeof = true;
}
error = _state.getError();
if (!aeof && error==null)
{
Content content = nextInterceptedContent();
if (content==null)
return;
// Consume a directly received EOF without first calling onDataAvailable
// So -1 will never be read and only onAddDataRread or onError will be called
if (content instanceof EofContent)
{
consume(content);
if (_state == EARLY_EOF)
error = _state.getError();
else if (_state == AEOF)
{
aeof = true;
_state = EOF;
}
}
}
}
try
{
if (error != null)
{
// TODO is this necessary to add here?
_channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE);
listener.onError(error);
}
else if (aeof)
{
listener.onAllDataRead();
}
else
{
listener.onDataAvailable();
// If -1 was read, then HttpChannelState#onEOF will have been called and a subsequent
// unhandle will call run again so onAllDataRead() can be called.
}
}
catch (Throwable e)
{
LOG.warn(e.toString());
LOG.debug(e);
try
{
if (aeof || error == null)
{
_channelState.getHttpChannel().getResponse().getHttpFields().add(HttpConnection.CONNECTION_CLOSE);
listener.onError(e);
}
}
catch (Throwable e2)
{
LOG.warn(e2.toString());
LOG.debug(e2);
throw new RuntimeIOException(e2);
}
}
}
@Override
public String toString()
{
State state;
long consumed;
int q;
Content content;
synchronized (_inputQ)
{
state = _state;
consumed = _contentConsumed;
q = _inputQ.size();
content = _inputQ.peekFirst();
}
return String.format("%s@%x[c=%d,q=%d,[0]=%s,s=%s]",
getClass().getSimpleName(),
hashCode(),
consumed,
q,
content,
state);
}
/**
* A Sentinel Content, which has zero length content but
* indicates some other event in the input stream (eg EOF)
*
*/
public static class SentinelContent extends Content
{
private final String _name;
public SentinelContent(String name)
{
super(BufferUtil.EMPTY_BUFFER);
_name = name;
}
@Override
public String toString()
{
return _name;
}
}
public static class EofContent extends SentinelContent
{
EofContent(String name)
{
super(name);
}
}
public static class Content implements Callback
{
protected final ByteBuffer _content;
public Content(ByteBuffer content)
{
_content = content;
}
public ByteBuffer getByteBuffer()
{
return _content;
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
public int get(byte[] buffer, int offset, int length)
{
length = Math.min(_content.remaining(),length);
_content.get(buffer,offset,length);
return length;
}
public int skip(int length)
{
length = Math.min(_content.remaining(),length);
_content.position(_content.position() + length);
return length;
}
public boolean hasContent()
{
return _content.hasRemaining();
}
public int remaining()
{
return _content.remaining();
}
public boolean isEmpty()
{
return !_content.hasRemaining();
}
@Override
public String toString()
{
return String.format("Content@%x{%s}",hashCode(),BufferUtil.toDetailString(_content));
}
}
protected static abstract class State
{
public boolean blockForContent(HttpInput in) throws IOException
{
return false;
}
public int noContent() throws IOException
{
return -1;
}
public Throwable getError()
{
return null;
}
}
protected static class EOFState extends State
{
}
protected class ErrorState extends EOFState
{
final Throwable _error;
ErrorState(Throwable error)
{
_error = error;
}
@Override
public Throwable getError()
{
return _error;
}
@Override
public int noContent() throws IOException
{
if (_error instanceof IOException)
throw (IOException)_error;
throw new IOException(_error);
}
@Override
public String toString()
{
return "ERROR:" + _error;
}
}
protected static final State STREAM = new State()
{
@Override
public boolean blockForContent(HttpInput input) throws IOException
{
input.blockForContent();
return true;
}
@Override
public String toString()
{
return "STREAM";
}
};
protected static final State ASYNC = new State()
{
@Override
public int noContent() throws IOException
{
return 0;
}
@Override
public String toString()
{
return "ASYNC";
}
};
protected static final State EARLY_EOF = new EOFState()
{
@Override
public int noContent() throws IOException
{
throw getError();
}
@Override
public String toString()
{
return "EARLY_EOF";
}
@Override
public IOException getError()
{
return new EofException("Early EOF");
}
};
protected static final State EOF = new EOFState()
{
@Override
public String toString()
{
return "EOF";
}
};
protected static final State AEOF = new EOFState()
{
@Override
public String toString()
{
return "AEOF";
}
};
}