org.eclipse.jetty.http2.api.Stream Maven / Gradle / Ivy
//
// ========================================================================
// 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.http2.api;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.io.Retainable;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
/**
* A {@link Stream} represents a bidirectional exchange of data on top of a {@link Session}.
* Differently from socket streams, where the input and output streams are permanently associated
* with the socket (and hence with the connection that the socket represents), there can be multiple
* HTTP/2 streams present concurrently for an HTTP/2 session.
* A {@link Stream} maps to an HTTP request/response cycle, and after the request/response cycle is
* completed, the stream is closed and removed from the session.
* Like {@link Session}, {@link Stream} is the active part and by calling its API applications
* can generate events on the stream; conversely, {@link Stream.Listener} is the passive part, and
* its callbacks are invoked when events happen on the stream.
*
* @see Stream.Listener
*/
public interface Stream
{
/**
* Get the stream unique id.
* @return the stream unique id
*/
public int getId();
/**
* Get the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream.
* @return the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream
*/
public Listener getListener();
/**
* Get the session this stream is associated to.
* @return the session this stream is associated to
*/
public Session getSession();
/**
* Sends the given HEADERS {@code frame} representing an HTTP response.
*
* @param frame the HEADERS frame to send
* @return the CompletableFuture that gets notified when the frame has been sent
*/
public default CompletableFuture headers(HeadersFrame frame)
{
return Promise.Completable.with(p -> headers(frame, Callback.from(() -> p.succeeded(this), p::failed)));
}
/**
* Sends the given HEADERS {@code frame}.
* Typically used to send an HTTP response or to send the HTTP response trailers.
*
* @param frame the HEADERS frame to send
* @param callback the callback that gets notified when the frame has been sent
*/
public void headers(HeadersFrame frame, Callback callback);
/**
* Sends the given PUSH_PROMISE {@code frame}.
*
* @param frame the PUSH_PROMISE frame to send
* @param listener the listener that gets notified of stream events
* @return the CompletableFuture that gets notified of the pushed stream creation
*/
public default CompletableFuture push(PushPromiseFrame frame, Listener listener)
{
return Promise.Completable.with(p -> push(frame, p, listener));
}
/**
* Sends the given PUSH_PROMISE {@code frame}.
*
* @param frame the PUSH_PROMISE frame to send
* @param promise the promise that gets notified of the pushed stream creation
* @param listener the listener that gets notified of stream events
*/
public void push(PushPromiseFrame frame, Promise promise, Listener listener);
/**
* Reads DATA frames from this stream, wrapping them in retainable
* {@link Data} objects.
* The returned {@link Stream.Data} object may be {@code null}, indicating
* that the end of the read side of the stream has not yet been reached, which
* may happen in these cases:
*
* - not all the bytes have been received so far, for example the remote
* peer did not send them yet, or they are in-flight
* - all the bytes have been received, but there is a trailer HEADERS
* frame to be received to indicate the end of the read side of the
* stream
*
* When the returned {@link Stream.Data} object is not {@code null},
* the flow control window has been enlarged by the DATA frame length;
* applications must call, either immediately or later (even
* asynchronously from a different thread) {@link Stream.Data#release()}
* to notify the implementation that the bytes have been processed.
* {@link Stream.Data} objects may be stored away for later, asynchronous,
* processing (for example, to process them only when all of them have been
* received).
* Once the returned {@link Stream.Data} object indicates that the end
* of the read side of the stream has been reached, further calls to this
* method will return a {@link Stream.Data} object with the same indication,
* although the instance may be different.
*
* @return a {@link Stream.Data} object containing the DATA frame,
* or null if no DATA frame is available
* @see #demand()
* @see Listener#onDataAvailable(Stream)
*/
public Data readData();
/**
* Sends the given DATA {@code frame}.
*
* @param frame the DATA frame to send
* @return the CompletableFuture that gets notified when the frame has been sent
*/
public default CompletableFuture data(DataFrame frame)
{
return Promise.Completable.with(p -> data(frame, Callback.from(() -> p.succeeded(this), p::failed)));
}
/**
* Sends the given DATA {@code frame}.
*
* @param frame the DATA frame to send
* @param callback the callback that gets notified when the frame has been sent
*/
public void data(DataFrame frame, Callback callback);
/**
* Sends the given RST_STREAM {@code frame}.
*
* @param frame the RST_STREAM frame to send
* @return the CompletableFuture that gets notified when the frame has been sent
*/
public default CompletableFuture reset(ResetFrame frame)
{
return Callback.Completable.with(c -> reset(frame, c));
}
/**
* Sends the given RST_STREAM {@code frame}.
*
* @param frame the RST_STREAM frame to send
* @param callback the callback that gets notified when the frame has been sent
*/
public void reset(ResetFrame frame, Callback callback);
/**
* @param key the attribute key
* @return an arbitrary object associated with the given key to this stream
* or null if no object can be found for the given key.
* @see #setAttribute(String, Object)
*/
public Object getAttribute(String key);
/**
* @param key the attribute key
* @param value an arbitrary object to associate with the given key to this stream
* @see #getAttribute(String)
* @see #removeAttribute(String)
*/
public void setAttribute(String key, Object value);
/**
* @param key the attribute key
* @return the arbitrary object associated with the given key to this stream
* @see #setAttribute(String, Object)
*/
public Object removeAttribute(String key);
/**
* @return whether this stream is local or remote
*/
public boolean isLocal();
/**
* @return whether this stream has been reset
*/
public boolean isReset();
/**
* @return whether the stream is closed remotely.
* @see #isClosed()
*/
boolean isRemotelyClosed();
/**
* @return whether this stream is closed, both locally and remotely.
*/
public boolean isClosed();
/**
* @return the stream idle timeout
* @see #setIdleTimeout(long)
*/
public long getIdleTimeout();
/**
* @param idleTimeout the stream idle timeout
* @see #getIdleTimeout()
* @see Stream.Listener#onIdleTimeout(Stream, TimeoutException, Promise)
*/
public void setIdleTimeout(long idleTimeout);
/**
* Demands more {@code DATA} frames for this stream.
* Calling this method causes {@link Listener#onDataAvailable(Stream)}
* to be invoked, possibly at a later time, when the stream has data
* to be read, but also when the stream has reached EOF.
* This method is idempotent: calling it when there already is an
* outstanding demand to invoke {@link Listener#onDataAvailable(Stream)}
* is a no-operation.
* The thread invoking this method may invoke directly
* {@link Listener#onDataAvailable(Stream)}, unless another thread
* that must invoke {@link Listener#onDataAvailable(Stream)}
* notices the outstanding demand first.
* It is always guaranteed that invoking this method from within
* {@code onDataAvailable(Stream)} will not cause a
* {@link StackOverflowError}.
*
* @see #readData()
* @see Listener#onDataAvailable(Stream)
*/
public void demand();
/**
* A {@link Stream.Listener} is the passive counterpart of a {@link Stream} and receives
* events happening on an HTTP/2 stream.
* HTTP/2 data is flow controlled - this means that only a finite number of data events
* are delivered, until the flow control window is exhausted.
* Applications control the delivery of data events by requesting them via
* {@link Stream#demand()}; the first event is always delivered, while subsequent
* events must be explicitly demanded.
* Applications control the HTTP/2 flow control by completing the callback associated
* with data events - this allows the implementation to recycle the data buffer and
* eventually to enlarge the flow control window so that the sender can send more data.
*
* @see Stream
*/
public interface Listener
{
/**
* A convenient constant for a {@link Listener} implementation that
* demands and discards DATA frames, typically to be returned from
* {@link Session.Listener#onNewStream(Stream, HeadersFrame)}
* and {@link Listener#onPush(Stream, PushPromiseFrame)}.
*/
public static Listener AUTO_DISCARD = new Listener() {};
/**
* Callback method invoked when a stream is created locally by
* {@link Session#newStream(HeadersFrame, Promise, Listener)}.
*
* @param stream the newly created stream
*/
public default void onNewStream(Stream stream)
{
}
/**
* Callback method invoked when a HEADERS frame representing the HTTP response has been received.
*
* @param stream the stream
* @param frame the HEADERS frame received
*/
public default void onHeaders(Stream stream, HeadersFrame frame)
{
if (!frame.isEndStream())
stream.demand();
}
/**
* Callback method invoked when a PUSH_PROMISE frame has been received.
* Applications that override this method are typically interested in
* processing the pushed stream DATA frames, and must demand for pushed
* DATA frames via {@link Stream#demand()} and then return either a
* {@link Listener} implementation that overrides
* {@link #onDataAvailable(Stream)} where applications can
* read from the {@link Stream} via {@link Stream#readData()}, or
* {@link #AUTO_DISCARD} that automatically reads and
* discards DATA frames.
* Returning {@code null} is possible but discouraged, and has the
* same effect of demanding and discarding the pushed DATA frames.
*
* @param stream the pushed stream
* @param frame the PUSH_PROMISE frame received
* @return a Stream.Listener that will be notified of pushed stream events
*/
public default Listener onPush(Stream stream, PushPromiseFrame frame)
{
stream.demand();
return AUTO_DISCARD;
}
/**
* Callback method invoked if the application has expressed
* {@link Stream#demand() demand} for DATA frames, and if there
* may be content available.
* Applications that wish to handle DATA frames should call
* {@link Stream#demand()} for this method to be invoked when
* the data is available.
* Server applications should typically demand from {@link #onNewStream(Stream)}
* (upon receiving an HTTP request), while client applications
* should typically demand from {@link #onHeaders(Stream, HeadersFrame)}
* (upon receiving an HTTP response).
* Just prior calling this method, the outstanding demand is
* cancelled; applications that implement this method should read
* content calling {@link Stream#readData()}, and call
* {@link Stream#demand()} to signal to the implementation to call
* again this method when there may be more content available.
* Only one thread at a time invokes this method, although it
* may not be the same thread across different invocations.
* It is always guaranteed that invoking {@link Stream#demand()}
* from within this method will not cause a {@link StackOverflowError}.
* Typical usage:
* {@code
* class MyStreamListener implements Stream.Listener
* {
* @Override
* public void onDataAvailable(Stream stream)
* {
* // Read a chunk of the content.
* Stream.Data data = stream.readData();
* if (data == null)
* {
* // No data available now, demand to be called back.
* stream.demand();
* }
* else
* {
* // Process the content.
* process(data.frame().getByteBuffer());
* // Notify that the content has been consumed.
* data.release();
* if (!data.frame().isEndStream())
* {
* // Demand to be called back.
* stream.demand();
* }
* }
* }
* }
* }
*
* @param stream the stream
* @see Stream#demand()
*/
public default void onDataAvailable(Stream stream)
{
while (true)
{
Data data = stream.readData();
if (data == null)
{
stream.demand();
return;
}
data.release();
if (data.frame().isEndStream())
return;
}
}
/**
* Callback method invoked when a RST_STREAM frame has been received for this stream.
*
* @param stream the stream
* @param frame the RST_STREAM frame received
* @param callback the callback to complete when the reset has been handled
*/
public default void onReset(Stream stream, ResetFrame frame, Callback callback)
{
callback.succeeded();
}
/**
* Callback method invoked when the stream exceeds its idle timeout.
*
* @param stream the stream
* @param x the timeout failure
* @param promise the promise to complete
* @see #getIdleTimeout()
*/
public default void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
promise.succeeded(true);
}
/**
* Callback method invoked when the stream failed.
*
* @param stream the stream
* @param error the error code
* @param reason the error reason, or null
* @param failure the failure
* @param callback the callback to complete when the failure has been handled
*/
public default void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback)
{
callback.succeeded();
}
/**
* Callback method invoked after the stream has been closed.
*
* @param stream the stream
*/
public default void onClosed(Stream stream)
{
}
}
/**
* A {@link Retainable} wrapper of a {@link DataFrame}.
*/
public abstract static class Data implements Retainable
{
public static Data eof(int streamId)
{
return new Data.EOF(streamId);
}
private final DataFrame frame;
public Data(DataFrame frame)
{
this.frame = frame;
}
public DataFrame frame()
{
return frame;
}
@Override
public String toString()
{
return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), frame());
}
private static class EOF extends Data
{
public EOF(int streamId)
{
super(new DataFrame(streamId, BufferUtil.EMPTY_BUFFER, true));
}
}
}
}