Jetty 12.0.x retainable chunk (#8325)

Made Chunk implement Retainable, and protocol implementations
to link the RetainableByteBuffer all the way to the Chunk.

Extended Retainable to have both a retain() and a release() methods.

Removed HTTP/2's ISession and IStream, now just using HTTP2Session and HTTP2Stream.
Removed *.Adapter classes in favor of interfaces with default methods.

Javadoc additions and clarifications.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2022-07-25 13:22:14 +02:00 committed by GitHub
parent 02324c35e2
commit 69a7b06f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 3039 additions and 3110 deletions

View File

@ -26,9 +26,11 @@ This means that a sender and a receiver maintain a _flow control window_ that tr
When a sender sends data bytes, it reduces its flow control window.
When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application.
The application consumes the data bytes and tells back the receiver that it has consumed the data bytes.
The receiver then enlarges the flow control window, and arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window.
The receiver then enlarges the flow control window, and the implementation arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window.
A sender can send data bytes up to its whole flow control window, then it must stop sending until it receives a message from the receiver that the data bytes have been consumed, which enlarges the flow control window, which allows the sender to send more data bytes.
A sender can send data bytes up to its whole flow control window, then it must stop sending.
The sender may resume sending data bytes when it receives a message from the receiver that the data bytes sent previously have been consumed.
This message enlarges the sender flow control window, which allows the sender to send more data bytes.
HTTP/2 defines _two_ flow control windows: one for each _session_, and one for each _stream_.
Let's see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes.
@ -37,33 +39,46 @@ The sender opens a session, and then opens `stream_1` on that session, and sends
At this point the session flow control window is `40` bytes (`120 - 80`), and ``stream_1``'s flow control window is `20` bytes (`100 - 80`).
The sender now opens `stream_2` on the same session and sends `40` data bytes.
At this point, the session flow control window is `0` bytes (`40 - 40`), while ``stream_2``'s flow control window is `60` (`100 - 40`).
Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2` despite both have their stream flow control windows greater than `0`.
Since now the session flow control window is `0`, the sender cannot send more data bytes, neither on `stream_1` nor on `stream_2`, nor on other streams, despite all the streams having their stream flow control windows greater than `0`.
The receiver consumes ``stream_2``'s `40` data bytes and sends a message to the sender with this information.
At this point, the session flow control window is `40` (`0 40`), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (`60 40`).
If the sender opens `stream_3` and would like to send 50 data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point.
At this point, the session flow control window is `40` (``0 + 40``), ``stream_1``'s flow control window is still `20` and ``stream_2``'s flow control window is `100` (``60 + 40``).
If the sender opens `stream_3` and would like to send `50` data bytes, it would only be able to send `40` because that is the maximum allowed by the session flow control window at this point.
It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a `WINDOW_UPDATE` frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to `0`.
end::flowControl[]
tag::apiFlowControl[]
NOTE: Returning from the `onData(...)` method implicitly demands for more `DATA` frames (unless the one just delivered was the last).
Additional `DATA` frames may be delivered immediately if they are available or later, asynchronously, when they arrive.
[NOTE]
====
When `onDataAvailable(Stream stream)` is invoked, the demand is implicitly cancelled.
Applications that consume the content buffer within `onData(...)` (for example, writing it to a file, or copying the bytes to another storage) should succeed the callback as soon as they have consumed the content buffer.
Just returning from the `onDataAvailable(Stream stream)` method does _not_ implicitly demand for more `DATA` frames.
Applications must call `Stream.demand()` to explicitly require that `onDataAvailable(Stream stream)` is invoked again when more `DATA` frames are available.
====
Applications that consume the content buffer within `onDataAvailable(Stream stream)` (for example, writing it to a file, or copying the bytes to another storage) should call `Data.release()` as soon as they have consumed the content buffer.
This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers.
Alternatively, a client application may store away _both_ the buffer and the callback to consume the buffer bytes later, or pass _both_ the buffer and the callback to another asynchronous API (this is typical in proxy applications).
Alternatively, a client application may store away the `Data` object to consume the buffer bytes later, or pass the `Data` object to another asynchronous API (this is typical in proxy applications).
IMPORTANT: Completing the `Callback` is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling.
[IMPORTANT]
====
Calling `Data.release()` is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more `DATA` frames without stalling.
====
Applications can also precisely control _when_ to demand more `DATA` frames, by implementing the `onDataDemanded(...)` method instead of `onData(...)`:
Applications can unwrap the `Data` object into some other object that may be used later, provided that the _release_ semantic is maintained:
[source,java,indent=0]
----
include::{doc_code}/org/eclipse/jetty/docs/programming/HTTP2Docs.java[tags=dataDemanded]
include::{doc_code}/org/eclipse/jetty/docs/programming/HTTP2Docs.java[tags=dataUnwrap]
----
IMPORTANT: Applications that implement `onDataDemanded(...)` must remember to call `Stream.demand(...)`.
If they don't, the implementation will not deliver `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session.
[IMPORTANT]
====
Applications that implement `onDataAvailable(Stream stream)` must remember to call `Stream.demand()` eventually.
If they do not call `Stream.demand()`, the implementation will not invoke `onDataAvailable(Stream stream)` to deliver more `DATA` frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session.
====
end::apiFlowControl[]

View File

@ -89,7 +89,7 @@ Receiving the `HEADERS` frame opens the `Stream`:
include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java[tags=request]
----
Server applications should return a `Stream.Listener` implementation from `onNewStream(...)` to be notified of events generated by the client, such as `DATA` frames carrying request content, or a `RST_STREAM` frame indicating that the client wants to _reset_ the request, or an idle timeout event indicating that the client was supposed to send more frames but it did not.
Server applications should return a `Stream.Listener` implementation from `onNewStream(\...)` to be notified of events generated by the client, such as `DATA` frames carrying request content, or a `RST_STREAM` frame indicating that the client wants to _reset_ the request, or an idle timeout event indicating that the client was supposed to send more frames but it did not.
The example below shows how to receive request content:

View File

@ -28,7 +28,6 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
@ -40,7 +39,7 @@ public class HTTP2Docs
HTTP2Client http2Client = new HTTP2Client();
http2Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -48,7 +47,7 @@ public class HTTP2Docs
MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);
HeadersFrame headersFrame = new HeadersFrame(request, null, true);
// tag::dataDemanded[]
// tag::dataUnwrap[]
class Chunk
{
private final ByteBuffer buffer;
@ -64,29 +63,39 @@ public class HTTP2Docs
// A queue that consumers poll to consume content asynchronously.
Queue<Chunk> dataQueue = new ConcurrentLinkedQueue<>();
// Implementation of Stream.Listener.onDataDemanded(...)
// in case of asynchronous content consumption and demand.
Stream.Listener listener = new Stream.Listener.Adapter()
// Implementation of Stream.Listener.onDataAvailable(Stream stream)
// in case of unwrapping of the Data object for asynchronous content
// consumption and demand.
Stream.Listener listener = new Stream.Listener()
{
@Override
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Get the content buffer.
ByteBuffer buffer = frame.getData();
Stream.Data data = stream.readData();
// Store buffer to consume it asynchronously, and wrap the callback.
if (data == null)
{
stream.demand();
return;
}
// Get the content buffer.
ByteBuffer buffer = data.frame().getData();
// Unwrap the Data object, converting it to a Chunk.
// The Data.release() semantic is maintained in the completion of the Callback.
dataQueue.offer(new Chunk(buffer, Callback.from(() ->
{
// When the buffer has been consumed, then:
// A) succeed the nested callback.
callback.succeeded();
// A) release the Data object.
data.release();
// B) demand more DATA frames.
stream.demand(1);
}, callback::failed)));
stream.demand();
})));
// Do not demand more content here, to avoid to overflow the queue.
// Do not demand more data here, to avoid to overflow the queue.
}
};
// end::dataDemanded[]
// end::dataUnwrap[]
}
}

View File

@ -77,7 +77,7 @@ public class HTTP2ClientDocs
// Connect to the server, the CompletableFuture will be
// notified when the connection is succeeded (or failed).
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
// Block to obtain the Session.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
@ -98,7 +98,7 @@ public class HTTP2ClientDocs
// Connect to the server, the CompletableFuture will be
// notified when the connection is succeeded (or failed).
CompletableFuture<Session> sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener() {});
// Block to obtain the Session.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
@ -113,7 +113,7 @@ public class HTTP2ClientDocs
// tag::configure[]
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
http2Client.connect(serverAddress, new Session.Listener.Adapter()
http2Client.connect(serverAddress, new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -138,7 +138,7 @@ public class HTTP2ClientDocs
http2Client.start();
// tag::newStream[]
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
// Configure the request headers.
@ -153,7 +153,7 @@ public class HTTP2ClientDocs
HeadersFrame headersFrame = new HeadersFrame(request, null, true);
// Open a Stream by sending the HEADERS frame.
session.newStream(headersFrame, new Stream.Listener.Adapter());
session.newStream(headersFrame, null);
// end::newStream[]
}
@ -163,7 +163,7 @@ public class HTTP2ClientDocs
http2Client.start();
// tag::newStreamWithData[]
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
// Configure the request headers.
@ -178,7 +178,7 @@ public class HTTP2ClientDocs
HeadersFrame headersFrame = new HeadersFrame(request, null, false);
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter());
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, null);
// Block to obtain the Stream.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
@ -205,7 +205,7 @@ public class HTTP2ClientDocs
HTTP2Client http2Client = new HTTP2Client();
http2Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -215,7 +215,7 @@ public class HTTP2ClientDocs
// tag::responseListener[]
// Open a Stream by sending the HEADERS frame.
session.newStream(headersFrame, new Stream.Listener.Adapter()
session.newStream(headersFrame, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -227,6 +227,12 @@ public class HTTP2ClientDocs
{
MetaData.Response response = (MetaData.Response)metaData;
System.getLogger("http2").log(INFO, "Received response {0}", response);
if (!frame.isEndStream())
{
// Demand for DATA frames, so that onDataAvailable()
// below will be called when they are available.
stream.demand();
}
}
else
{
@ -235,19 +241,29 @@ public class HTTP2ClientDocs
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Read a Data object.
Stream.Data data = stream.readData();
if (data == null)
{
// Demand more DATA frames.
stream.demand();
return;
}
// Get the content buffer.
ByteBuffer buffer = frame.getData();
ByteBuffer buffer = data.frame().getData();
// Consume the buffer, here - as an example - just log it.
System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
// Tell the implementation that the buffer has been consumed.
callback.succeeded();
data.release();
// By returning from the method, implicitly tell the implementation
// to deliver to this method more DATA frames when they are available.
// Demand more DATA frames when they are available.
stream.demand();
}
});
// end::responseListener[]
@ -258,7 +274,7 @@ public class HTTP2ClientDocs
HTTP2Client http2Client = new HTTP2Client();
http2Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -268,12 +284,15 @@ public class HTTP2ClientDocs
// tag::reset[]
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
// The server reset this stream.
// Succeed the callback to signal that the reset event has been handled.
callback.succeeded();
}
});
Stream stream = streamCF.get();
@ -288,7 +307,7 @@ public class HTTP2ClientDocs
HTTP2Client http2Client = new HTTP2Client();
http2Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -298,7 +317,7 @@ public class HTTP2ClientDocs
// tag::push[]
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener()
{
@Override
public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
@ -314,7 +333,7 @@ public class HTTP2ClientDocs
Stream primaryStream = pushedStream.getSession().getStream(frame.getStreamId());
// Return a Stream.Listener to listen for the pushed "response" events.
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -326,18 +345,29 @@ public class HTTP2ClientDocs
{
// The pushed "response" headers.
HttpFields pushedResponseHeaders = metaData.getFields();
// Typically a pushed stream has data, so demand for data.
stream.demand();
}
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Handle the pushed stream "response" content.
Stream.Data data = stream.readData();
if (data == null)
{
stream.demand();
return;
}
// The pushed stream "response" content bytes.
ByteBuffer buffer = frame.getData();
// Consume the buffer and complete the callback.
callback.succeeded();
ByteBuffer buffer = data.frame().getData();
// Consume the buffer and release the Data object.
data.release();
}
};
}
@ -350,7 +380,7 @@ public class HTTP2ClientDocs
HTTP2Client http2Client = new HTTP2Client();
http2Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener() {});
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -360,12 +390,12 @@ public class HTTP2ClientDocs
// tag::pushReset[]
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener()
{
@Override
public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
{
// Reset the pushed stream to tell the server we are not interested.
// Reset the pushed stream to tell the server you are not interested.
pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
// Not interested in listening to pushed response events.

View File

@ -212,7 +212,7 @@ public class HTTP3ClientDocs
process(data.getByteBuffer());
// Notify the implementation that the content has been consumed.
data.complete();
data.release();
if (!data.isLast())
{
@ -266,7 +266,7 @@ public class HTTP3ClientDocs
HTTP3Client http3Client = new HTTP3Client();
http3Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
CompletableFuture<Session> sessionCF = http3Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http3Client.connect(serverAddress, new Session.Listener());
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -276,7 +276,7 @@ public class HTTP3ClientDocs
// tag::push[]
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener()
{
@Override
public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
@ -328,7 +328,7 @@ public class HTTP3ClientDocs
HTTP3Client http3Client = new HTTP3Client();
http3Client.start();
SocketAddress serverAddress = new InetSocketAddress("localhost", 8444);
CompletableFuture<Session> sessionCF = http3Client.connect(serverAddress, new Session.Listener.Adapter());
CompletableFuture<Session> sessionCF = http3Client.connect(serverAddress, new Session.Listener());
Session session = sessionCF.get();
HttpFields requestHeaders = HttpFields.build()
@ -338,7 +338,7 @@ public class HTTP3ClientDocs
// tag::pushReset[]
// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener()
{
@Override
public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)

View File

@ -51,7 +51,7 @@ public class HTTP2ServerDocs
// Create a Server instance.
Server server = new Server();
ServerSessionListener sessionListener = new ServerSessionListener.Adapter();
ServerSessionListener sessionListener = new ServerSessionListener() {};
// Create a ServerConnector with RawHTTP2ServerConnectionFactory.
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(sessionListener);
@ -77,7 +77,7 @@ public class HTTP2ServerDocs
public void accept()
{
// tag::accept[]
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -92,7 +92,7 @@ public class HTTP2ServerDocs
public void preface()
{
// tag::preface[]
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -112,7 +112,7 @@ public class HTTP2ServerDocs
public void request()
{
// tag::request[]
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -122,7 +122,10 @@ public class HTTP2ServerDocs
// Return a Stream.Listener to handle the request events,
// for example request content events or a request reset.
return new Stream.Listener.Adapter();
return new Stream.Listener()
{
// Override callback methods for events you are interested in.
};
}
};
// end::request[]
@ -131,29 +134,41 @@ public class HTTP2ServerDocs
public void requestContent()
{
// tag::requestContent[]
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
MetaData.Request request = (MetaData.Request)frame.getMetaData();
// Demand for request data content.
stream.demand();
// Return a Stream.Listener to handle the request events.
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
if (data == null)
{
stream.demand();
return;
}
// Get the content buffer.
ByteBuffer buffer = frame.getData();
ByteBuffer buffer = data.frame().getData();
// Consume the buffer, here - as an example - just log it.
System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
// Tell the implementation that the buffer has been consumed.
callback.succeeded();
data.release();
// By returning from the method, implicitly tell the implementation
// to deliver to this method more DATA frames when they are available.
// Demand more DATA frames when they are available.
stream.demand();
}
};
}
@ -164,7 +179,7 @@ public class HTTP2ServerDocs
public void response()
{
// tag::response[]
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -178,15 +193,30 @@ public class HTTP2ServerDocs
}
else
{
return new Stream.Listener.Adapter()
// Demand for request data.
stream.demand();
// Return a listener to handle the request events.
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
if (data == null)
{
stream.demand();
return;
}
// Consume the request content.
callback.succeeded();
if (frame.isEndStream())
data.release();
if (data.frame().isEndStream())
respond(stream, request);
else
stream.demand();
}
};
}
@ -230,7 +260,7 @@ public class HTTP2ServerDocs
{
float maxRequestRate = 0F;
// tag::reset[]
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -247,7 +277,10 @@ public class HTTP2ServerDocs
// The request is accepted.
MetaData.Request request = (MetaData.Request)frame.getMetaData();
// Return a Stream.Listener to handle the request events.
return new Stream.Listener.Adapter();
return new Stream.Listener()
{
// Override callback methods for events you are interested in.
};
}
}
// tag::exclude[]
@ -267,7 +300,7 @@ public class HTTP2ServerDocs
// The favicon bytes.
ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true);
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
// By default, push is enabled.
private boolean pushEnabled = true;
@ -292,7 +325,7 @@ public class HTTP2ServerDocs
HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico");
MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY);
PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest);
stream.push(promiseFrame, new Stream.Listener.Adapter())
stream.push(promiseFrame, null)
.thenCompose(pushedStream ->
{
// Send the favicon "response".
@ -302,7 +335,10 @@ public class HTTP2ServerDocs
});
}
// Return a Stream.Listener to handle the request events.
return new Stream.Listener.Adapter();
return new Stream.Listener()
{
// Override callback methods for events you are interested in.
};
}
};
// end::push[]

View File

@ -157,7 +157,7 @@ public class HTTP3ServerDocs
System.getLogger("http3").log(INFO, "Consuming buffer {0}", buffer);
// Tell the implementation that the buffer has been consumed.
data.complete();
data.release();
if (!data.isLast())
{
@ -204,7 +204,7 @@ public class HTTP3ServerDocs
else
{
// Consume the request content.
data.complete();
data.release();
if (data.isLast())
respond(stream, request);
}
@ -290,7 +290,7 @@ public class HTTP3ServerDocs
// The favicon bytes.
ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true);
ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
ServerSessionListener sessionListener = new ServerSessionListener()
{
// By default, push is enabled.
private boolean pushEnabled = true;
@ -315,7 +315,7 @@ public class HTTP3ServerDocs
HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico");
MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY);
PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest);
stream.push(promiseFrame, new Stream.Listener.Adapter())
stream.push(promiseFrame, null)
.thenCompose(pushedStream ->
{
// Send the favicon "response".
@ -325,7 +325,7 @@ public class HTTP3ServerDocs
});
}
// Return a Stream.Listener to handle the request events.
return new Stream.Listener.Adapter();
return new Stream.Listener();
}
};
// end::push[]

View File

@ -28,9 +28,7 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
@ -69,14 +67,14 @@ public class ConscryptHTTP2ClientTest
client.start();
FuturePromise<Session> sessionPromise = new FuturePromise<>();
client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise);
client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener() {}, sessionPromise);
Session session = sessionPromise.get(15, TimeUnit.SECONDS);
HttpFields requestFields = HttpFields.build().put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
HeadersFrame headersFrame = new HeadersFrame(metaData, null, true);
CountDownLatch latch = new CountDownLatch(1);
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -84,14 +82,17 @@ public class ConscryptHTTP2ClientTest
System.err.println(frame);
if (frame.isEndStream())
latch.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
System.err.println(frame);
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
System.err.println(data);
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});

View File

@ -25,9 +25,7 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
@ -55,7 +53,7 @@ public class JDK9HTTP2ClientTest
client.start();
FuturePromise<Session> sessionPromise = new FuturePromise<>();
client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise);
client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener() {}, sessionPromise);
Session session = sessionPromise.get(15, TimeUnit.SECONDS);
HttpFields.Mutable requestFields = HttpFields.build();
@ -63,7 +61,7 @@ public class JDK9HTTP2ClientTest
MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
HeadersFrame headersFrame = new HeadersFrame(metaData, null, true);
CountDownLatch latch = new CountDownLatch(1);
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -71,14 +69,17 @@ public class JDK9HTTP2ClientTest
System.err.println(frame);
if (frame.isEndStream())
latch.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
System.err.println(frame);
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
System.err.println(data);
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});

View File

@ -95,7 +95,7 @@ public abstract class HttpReceiver
throw new IllegalArgumentException("Invalid demand " + n);
boolean resume = false;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
demand = MathUtils.cappedAdd(demand, n);
if (stalled)
@ -104,7 +104,7 @@ public abstract class HttpReceiver
resume = true;
}
if (LOG.isDebugEnabled())
LOG.debug("Response demand={}/{}, resume={}", n, demand, resume);
LOG.debug("Response demand={}/{}, resume={} on {}", n, demand, resume, this);
}
if (resume)
@ -123,7 +123,7 @@ public abstract class HttpReceiver
private long demand(LongUnaryOperator operator)
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return demand = operator.applyAsLong(demand);
}
@ -131,7 +131,7 @@ public abstract class HttpReceiver
protected boolean hasDemandOrStall()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
stalled = demand <= 0;
return !stalled;
@ -229,17 +229,11 @@ public abstract class HttpReceiver
{
switch (fieldHeader)
{
case SET_COOKIE:
case SET_COOKIE2:
case SET_COOKIE, SET_COOKIE2 ->
{
URI uri = exchange.getRequest().getURI();
if (uri != null)
storeCookie(uri, field);
break;
}
default:
{
break;
}
}
}
@ -602,10 +596,11 @@ public abstract class HttpReceiver
@Override
public String toString()
{
return String.format("%s@%x(rsp=%s,failure=%s)",
return String.format("%s@%x(rsp=%s,demand=%d,failure=%s)",
getClass().getSimpleName(),
hashCode(),
responseState,
demand(),
failure);
}

View File

@ -16,7 +16,7 @@ package org.eclipse.jetty.client.util;
import java.io.InputStream;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.io.content.InputStreamContentSource;
/**
@ -33,7 +33,7 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen
public InputStreamRequestContent(InputStream stream)
{
this("application/octet-stream", stream, null);
this(stream, 4096);
}
public InputStreamRequestContent(InputStream stream, int bufferSize)
@ -43,7 +43,7 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen
public InputStreamRequestContent(String contentType, InputStream stream, int bufferSize)
{
this(contentType, stream, null);
this(contentType, stream);
setBufferSize(bufferSize);
}
@ -52,7 +52,7 @@ public class InputStreamRequestContent extends InputStreamContentSource implemen
this(contentType, stream, null);
}
public InputStreamRequestContent(String contentType, InputStream stream, ByteBufferPool bufferPool)
public InputStreamRequestContent(String contentType, InputStream stream, RetainableByteBufferPool bufferPool)
{
super(stream, bufferPool);
this.contentType = contentType;

View File

@ -17,16 +17,15 @@ import java.io.IOException;
import java.nio.file.Path;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.io.content.PathContentSource;
/**
* <p>A {@link Request.Content} for files using JDK 7's {@code java.nio.file} APIs.</p>
* <p>It is possible to specify a buffer size used to read content from the stream,
* by default 4096 bytes, and whether the buffer should be direct or not.</p>
* <p>If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)},
* the buffer will be allocated from that pool, otherwise one buffer will be
* allocated and used to read the file.</p>
* by default 4096 bytes, whether the buffer should be direct or not, and a
* {@link RetainableByteBufferPool} from which {@code ByteBuffer}s will be acquired
* to read from the {@code Path}.</p>
*/
public class PathRequestContent extends PathContentSource implements Request.Content
{
@ -49,11 +48,16 @@ public class PathRequestContent extends PathContentSource implements Request.Con
public PathRequestContent(String contentType, Path filePath, int bufferSize) throws IOException
{
super(filePath);
this.contentType = contentType;
this(contentType, filePath, null);
setBufferSize(bufferSize);
}
public PathRequestContent(String contentType, Path filePath, RetainableByteBufferPool bufferPool) throws IOException
{
super(filePath, bufferPool);
this.contentType = contentType;
}
@Override
public String getContentType()
{

View File

@ -372,7 +372,7 @@ public class ServerFCGIConnection extends AbstractConnection implements Connecti
if (stream != null)
{
networkBuffer.retain();
stream.onContent(Content.Chunk.from(buffer, false, networkBuffer::release));
stream.onContent(Content.Chunk.from(buffer, false, networkBuffer));
// Signal that the content is processed asynchronously, to ensure backpressure.
return true;
}

View File

@ -39,13 +39,20 @@ public class Trailers implements Content.Chunk
return true;
}
@Override
public void release()
{
}
public HttpFields getTrailers()
{
return trailers;
}
@Override
public void retain()
{
throw new UnsupportedOperationException();
}
@Override
public boolean release()
{
return true;
}
}

View File

@ -54,13 +54,13 @@ import org.eclipse.jetty.util.thread.Scheduler;
* int port = 443;
*
* FuturePromise&lt;Session&gt; sessionPromise = new FuturePromise&lt;&gt;();
* client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener.Adapter(), sessionPromise);
* client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener() {}, sessionPromise);
*
* // Obtain the client Session object.
* Session session = sessionPromise.get(5, TimeUnit.SECONDS);
*
* // Prepare the HTTP request headers.
* HttpFields requestFields = new HttpFields();
* HttpFields requestFields = HttpFields.build();
* requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
* // Prepare the HTTP request object.
* MetaData.Request request = new MetaData.Request("PUT", HttpURI.from("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
@ -68,7 +68,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
* HeadersFrame headersFrame = new HeadersFrame(request, null, false);
*
* // Prepare the listener to receive the HTTP response frames.
* Stream.Listener responseListener = new new Stream.Listener.Adapter()
* Stream.Listener responseListener = new new Stream.Listener()
* {
* &#64;Override
* public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -19,13 +19,13 @@ import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.client.internal.HTTP2ClientSession;
import org.eclipse.jetty.http2.frames.PrefaceFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.internal.HTTP2Connection;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.generator.Generator;
import org.eclipse.jetty.http2.internal.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
@ -35,7 +35,6 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.Scheduler;
public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
@ -85,7 +84,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
private final Promise<Session> promise;
private final Session.Listener listener;
private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise<Session> promise, Session.Listener listener)
private HTTP2ClientConnection(HTTP2Client client, RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endpoint, Parser parser, HTTP2Session session, int bufferSize, Promise<Session> promise, Session.Listener listener)
{
super(retainableByteBufferPool, executor, endpoint, parser, session, bufferSize);
this.client = client;
@ -109,7 +108,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
PrefaceFrame prefaceFrame = new PrefaceFrame();
SettingsFrame settingsFrame = new SettingsFrame(settings, false);
ISession session = getSession();
HTTP2Session session = getSession();
int windowDelta = client.getInitialSessionRecvWindow() - FlowControlStrategy.DEFAULT_WINDOW_SIZE;
session.updateRecvWindow(windowDelta);
@ -150,7 +149,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
public void onOpened(Connection connection)
{
HTTP2ClientConnection http2Connection = (HTTP2ClientConnection)connection;
http2Connection.client.addManaged((LifeCycle)http2Connection.getSession());
http2Connection.client.addManaged(http2Connection.getSession());
}
@Override

View File

@ -16,13 +16,13 @@ package org.eclipse.jetty.http2.client.internal;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.internal.generator.Generator;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
@ -47,7 +47,7 @@ public class HTTP2ClientSession extends HTTP2Session
// HEADERS can be received for normal and pushed responses.
int streamId = frame.getStreamId();
IStream stream = getStream(streamId);
HTTP2Stream stream = getStream(streamId);
if (stream != null)
{
MetaData metaData = frame.getMetaData();
@ -94,7 +94,7 @@ public class HTTP2ClientSession extends HTTP2Session
int streamId = frame.getStreamId();
int pushStreamId = frame.getPromisedStreamId();
IStream stream = getStream(streamId);
HTTP2Stream stream = getStream(streamId);
if (stream == null)
{
if (LOG.isDebugEnabled())
@ -102,7 +102,7 @@ public class HTTP2ClientSession extends HTTP2Session
}
else
{
IStream pushStream = createRemoteStream(pushStreamId, frame.getMetaData());
HTTP2Stream pushStream = createRemoteStream(pushStreamId, frame.getMetaData());
if (pushStream != null)
{
pushStream.process(frame, Callback.NOOP);
@ -112,7 +112,7 @@ public class HTTP2ClientSession extends HTTP2Session
}
}
private Stream.Listener notifyPush(IStream stream, IStream pushStream, PushPromiseFrame frame)
private Stream.Listener notifyPush(HTTP2Stream stream, HTTP2Stream pushStream, PushPromiseFrame frame)
{
Stream.Listener listener = stream.getListener();
if (listener == null)

View File

@ -14,13 +14,18 @@
package org.eclipse.jetty.http2;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
@ -35,7 +40,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
private final AtomicLong sessionStall = new AtomicLong();
private final AtomicLong sessionStallTime = new AtomicLong();
private final Map<IStream, Long> streamsStalls = new ConcurrentHashMap<>();
private final Map<Stream, Long> streamsStalls = new ConcurrentHashMap<>();
private final AtomicLong streamsStallTime = new AtomicLong();
private int initialStreamSendWindow;
private int initialStreamRecvWindow;
@ -59,20 +64,20 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
}
@Override
public void onStreamCreated(IStream stream)
public void onStreamCreated(Stream stream)
{
stream.updateSendWindow(initialStreamSendWindow);
stream.updateRecvWindow(initialStreamRecvWindow);
updateSendWindow(stream, getInitialStreamSendWindow());
updateRecvWindow(stream, getInitialStreamRecvWindow());
}
@Override
public void onStreamDestroyed(IStream stream)
public void onStreamDestroyed(Stream stream)
{
streamsStalls.remove(stream);
}
@Override
public void updateInitialStreamWindow(ISession session, int initialStreamWindow, boolean local)
public void updateInitialStreamWindow(Session session, int initialStreamWindow, boolean local)
{
int previousInitialStreamWindow;
if (local)
@ -94,19 +99,19 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
{
if (local)
{
((IStream)stream).updateRecvWindow(delta);
updateRecvWindow(stream, delta);
if (LOG.isDebugEnabled())
LOG.debug("Updated initial stream recv window {} -> {} for {}", previousInitialStreamWindow, initialStreamWindow, stream);
}
else
{
session.onWindowUpdate((IStream)stream, new WindowUpdateFrame(stream.getId(), delta));
updateWindow(session, stream, new WindowUpdateFrame(stream.getId(), delta));
}
}
}
@Override
public void onWindowUpdate(ISession session, IStream stream, WindowUpdateFrame frame)
public void onWindowUpdate(Session session, Stream stream, WindowUpdateFrame frame)
{
int delta = frame.getWindowDelta();
if (frame.getStreamId() > 0)
@ -114,7 +119,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
// The stream may have been removed concurrently.
if (stream != null)
{
int oldSize = stream.updateSendWindow(delta);
int oldSize = updateSendWindow(stream, delta);
if (LOG.isDebugEnabled())
LOG.debug("Updated stream send window {} -> {} for {}", oldSize, oldSize + delta, stream);
if (oldSize <= 0)
@ -123,7 +128,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
}
else
{
int oldSize = session.updateSendWindow(delta);
int oldSize = updateSendWindow(session, delta);
if (LOG.isDebugEnabled())
LOG.debug("Updated session send window {} -> {} for {}", oldSize, oldSize + delta, session);
if (oldSize <= 0)
@ -132,40 +137,40 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
}
@Override
public void onDataReceived(ISession session, IStream stream, int length)
public void onDataReceived(Session session, Stream stream, int length)
{
int oldSize = session.updateRecvWindow(-length);
int oldSize = updateRecvWindow(session, -length);
if (LOG.isDebugEnabled())
LOG.debug("Data received, {} bytes, updated session recv window {} -> {} for {}", length, oldSize, oldSize - length, session);
if (stream != null)
{
oldSize = stream.updateRecvWindow(-length);
oldSize = updateRecvWindow(stream, -length);
if (LOG.isDebugEnabled())
LOG.debug("Data received, {} bytes, updated stream recv window {} -> {} for {}", length, oldSize, oldSize - length, stream);
}
}
@Override
public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame)
public void windowUpdate(Session session, Stream stream, WindowUpdateFrame frame)
{
}
@Override
public void onDataSending(IStream stream, int length)
public void onDataSending(Stream stream, int length)
{
if (length == 0)
return;
ISession session = stream.getSession();
int oldSessionWindow = session.updateSendWindow(-length);
Session session = stream.getSession();
int oldSessionWindow = updateSendWindow(session, -length);
int newSessionWindow = oldSessionWindow - length;
if (LOG.isDebugEnabled())
LOG.debug("Sending, session send window {} -> {} for {}", oldSessionWindow, newSessionWindow, session);
if (newSessionWindow <= 0)
onSessionStalled(session);
int oldStreamWindow = stream.updateSendWindow(-length);
int oldStreamWindow = updateSendWindow(stream, -length);
int newStreamWindow = oldStreamWindow - length;
if (LOG.isDebugEnabled())
LOG.debug("Sending, stream send window {} -> {} for {}", oldStreamWindow, newStreamWindow, stream);
@ -174,25 +179,55 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
}
@Override
public void onDataSent(IStream stream, int length)
public void onDataSent(Stream stream, int length)
{
}
protected void onSessionStalled(ISession session)
protected void updateWindow(Session session, Stream stream, WindowUpdateFrame frame)
{
((HTTP2Session)session).onWindowUpdate((HTTP2Stream)stream, frame);
}
protected int updateRecvWindow(Session session, int value)
{
return ((HTTP2Session)session).updateRecvWindow(value);
}
protected int updateSendWindow(Session session, int value)
{
return ((HTTP2Session)session).updateSendWindow(value);
}
protected int updateRecvWindow(Stream stream, int value)
{
return ((HTTP2Stream)stream).updateRecvWindow(value);
}
protected int updateSendWindow(Stream stream, int value)
{
return ((HTTP2Stream)stream).updateSendWindow(value);
}
protected void sendWindowUpdate(Session session, Stream stream, List<WindowUpdateFrame> frames)
{
((HTTP2Session)session).frames((HTTP2Stream)stream, frames, Callback.NOOP);
}
protected void onSessionStalled(Session session)
{
sessionStall.set(System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Session stalled {}", session);
}
protected void onStreamStalled(IStream stream)
protected void onStreamStalled(Stream stream)
{
streamsStalls.put(stream, System.nanoTime());
if (LOG.isDebugEnabled())
LOG.debug("Stream stalled {}", stream);
}
protected void onSessionUnstalled(ISession session)
protected void onSessionUnstalled(Session session)
{
long stallTime = System.nanoTime() - sessionStall.getAndSet(0);
sessionStallTime.addAndGet(stallTime);
@ -200,7 +235,7 @@ public abstract class AbstractFlowControlStrategy implements FlowControlStrategy
LOG.debug("Session unstalled after {} ms {}", TimeUnit.NANOSECONDS.toMillis(stallTime), session);
}
protected void onStreamUnstalled(IStream stream)
protected void onStreamUnstalled(Stream stream)
{
Long time = streamsStalls.remove(stream);
if (time != null)

View File

@ -18,9 +18,10 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
@ -66,7 +67,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
private final AtomicInteger maxSessionRecvWindow = new AtomicInteger(DEFAULT_WINDOW_SIZE);
private final AtomicInteger sessionLevel = new AtomicInteger();
private final Map<IStream, AtomicInteger> streamLevels = new ConcurrentHashMap<>();
private final Map<Stream, AtomicInteger> streamLevels = new ConcurrentHashMap<>();
private float bufferRatio;
public BufferingFlowControlStrategy(float bufferRatio)
@ -92,21 +93,21 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
}
@Override
public void onStreamCreated(IStream stream)
public void onStreamCreated(Stream stream)
{
super.onStreamCreated(stream);
streamLevels.put(stream, new AtomicInteger());
}
@Override
public void onStreamDestroyed(IStream stream)
public void onStreamDestroyed(Stream stream)
{
streamLevels.remove(stream);
super.onStreamDestroyed(stream);
}
@Override
public void onDataConsumed(ISession session, IStream stream, int length)
public void onDataConsumed(Session session, Stream stream, int length)
{
if (length <= 0)
return;
@ -119,10 +120,10 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
{
if (sessionLevel.compareAndSet(level, 0))
{
session.updateRecvWindow(level);
updateRecvWindow(session, level);
if (LOG.isDebugEnabled())
LOG.debug("Data consumed, {} bytes, updated session recv window by {}/{} for {}", length, level, maxLevel, session);
sendWindowUpdate(null, session, new WindowUpdateFrame(0, level));
sendWindowUpdate(session, null, List.of(new WindowUpdateFrame(0, level)));
}
else
{
@ -153,10 +154,10 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
if (level > maxLevel)
{
level = streamLevel.getAndSet(0);
stream.updateRecvWindow(level);
updateRecvWindow(stream, level);
if (LOG.isDebugEnabled())
LOG.debug("Data consumed, {} bytes, updated stream recv window by {}/{} for {}", length, level, maxLevel, stream);
sendWindowUpdate(stream, session, new WindowUpdateFrame(stream.getId(), level));
sendWindowUpdate(session, stream, List.of(new WindowUpdateFrame(stream.getId(), level)));
}
else
{
@ -168,13 +169,8 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
}
}
protected void sendWindowUpdate(IStream stream, ISession session, WindowUpdateFrame frame)
{
session.frames(stream, List.of(frame), Callback.NOOP);
}
@Override
public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame)
public void windowUpdate(Session session, Stream stream, WindowUpdateFrame frame)
{
super.windowUpdate(session, stream, frame);
@ -207,7 +203,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
if (frame.getStreamId() == 0)
{
int sessionWindow = session.updateRecvWindow(0);
int sessionWindow = updateRecvWindow(session, 0);
Atomics.updateMax(maxSessionRecvWindow, sessionWindow);
}
}

View File

@ -13,29 +13,31 @@
package org.eclipse.jetty.http2;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
public interface FlowControlStrategy
{
public static int DEFAULT_WINDOW_SIZE = 65535;
public void onStreamCreated(IStream stream);
public void onStreamCreated(Stream stream);
public void onStreamDestroyed(IStream stream);
public void onStreamDestroyed(Stream stream);
public void updateInitialStreamWindow(ISession session, int initialStreamWindow, boolean local);
public void updateInitialStreamWindow(Session session, int initialStreamWindow, boolean local);
public void onWindowUpdate(ISession session, IStream stream, WindowUpdateFrame frame);
public void onWindowUpdate(Session session, Stream stream, WindowUpdateFrame frame);
public void onDataReceived(ISession session, IStream stream, int length);
public void onDataReceived(Session session, Stream stream, int length);
public void onDataConsumed(ISession session, IStream stream, int length);
public void onDataConsumed(Session session, Stream stream, int length);
public void windowUpdate(ISession session, IStream stream, WindowUpdateFrame frame);
public void windowUpdate(Session session, Stream stream, WindowUpdateFrame frame);
public void onDataSending(IStream stream, int length);
public void onDataSending(Stream stream, int length);
public void onDataSent(IStream stream, int length);
public void onDataSent(Stream stream, int length);
public interface Factory
{

View File

@ -1,169 +0,0 @@
//
// ========================================================================
// 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.http2;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
/**
* <p>The SPI interface for implementing an HTTP/2 session.</p>
* <p>This class extends {@link Session} by adding the methods required to
* implement the HTTP/2 session functionalities.</p>
*/
public interface ISession extends Session
{
@Override
public IStream getStream(int streamId);
/**
* <p>Removes the given {@code stream}.</p>
*
* @param stream the stream to remove
* @return whether the stream was removed
*/
public boolean removeStream(IStream stream);
/**
* <p>Sends the given list of frames to create a new {@link Stream}.</p>
*
* @param frames the list of frames to send
* @param promise the promise that gets notified of the stream creation
* @param listener the listener that gets notified of stream events
*/
public void newStream(IStream.FrameList frames, Promise<Stream> promise, Stream.Listener listener);
/**
* <p>Enqueues the given frames to be written to the connection.</p>
* @param stream the stream the frames belong to
* @param frames the frames to enqueue
* @param callback the callback that gets notified when the frames have been sent
*/
public void frames(IStream stream, List<? extends Frame> frames, Callback callback);
/**
* <p>Enqueues the given PUSH_PROMISE frame to be written to the connection.</p>
* <p>Differently from {@link #frames(IStream, List, Callback)}, this method
* generates atomically the stream id for the pushed stream.</p>
*
* @param stream the stream associated to the pushed stream
* @param promise the promise that gets notified of the pushed stream creation
* @param frame the PUSH_PROMISE frame to enqueue
* @param listener the listener that gets notified of pushed stream events
*/
public void push(IStream stream, Promise<Stream> promise, PushPromiseFrame frame, Stream.Listener listener);
/**
* <p>Enqueues the given DATA frame to be written to the connection.</p>
*
* @param stream the stream the data frame belongs to
* @param callback the callback that gets notified when the frame has been sent
* @param frame the DATA frame to send
*/
public void data(IStream stream, Callback callback, DataFrame frame);
/**
* <p>Updates the session send window by the given {@code delta}.</p>
*
* @param delta the delta value (positive or negative) to add to the session send window
* @return the previous value of the session send window
*/
public int updateSendWindow(int delta);
/**
* <p>Updates the session receive window by the given {@code delta}.</p>
*
* @param delta the delta value (positive or negative) to add to the session receive window
* @return the previous value of the session receive window
*/
public int updateRecvWindow(int delta);
/**
* <p>Callback method invoked when a WINDOW_UPDATE frame has been received.</p>
*
* @param stream the stream the window update belongs to, or null if the window update belongs to the session
* @param frame the WINDOW_UPDATE frame received
*/
public void onWindowUpdate(IStream stream, WindowUpdateFrame frame);
/**
* @return whether the push functionality is enabled
*/
public boolean isPushEnabled();
/**
* <p>Callback invoked when the connection reads -1.</p>
*
* @see #onIdleTimeout()
* @see #close(int, String, Callback)
*/
public void onShutdown();
/**
* <p>Callback invoked when the idle timeout expires.</p>
*
* @return {@code true} if the session has expired
* @see #onShutdown()
* @see #close(int, String, Callback)
*/
public boolean onIdleTimeout();
/**
* <p>Callback method invoked during an HTTP/1.1 to HTTP/2 upgrade requests
* to process the given synthetic frame.</p>
*
* @param frame the synthetic frame to process
*/
public void onFrame(Frame frame);
/**
* <p>Callback method invoked when bytes are flushed to the network.</p>
*
* @param bytes the number of bytes flushed to the network
* @throws IOException if the flush should fail
*/
public void onFlushed(long bytes) throws IOException;
/**
* @return the number of bytes written by this session
*/
public long getBytesWritten();
/**
* <p>Callback method invoked when a DATA frame is received.</p>
*
* @param frame the DATA frame received
* @param callback the callback to notify when the frame has been processed
*/
public void onData(DataFrame frame, Callback callback);
/**
* <p>Gracefully closes the session, returning a {@code CompletableFuture} that
* is completed when all the streams currently being processed are completed.</p>
* <p>Implementation is idempotent, i.e. calling this method a second time
* or concurrently results in a no-operation.</p>
*
* @return a {@code CompletableFuture} that is completed when all the streams are completed
*/
public CompletableFuture<Void> shutdown();
}

View File

@ -1,228 +0,0 @@
//
// ========================================================================
// 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.http2;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.StreamFrame;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Callback;
/**
* <p>The SPI interface for implementing an HTTP/2 stream.</p>
* <p>This class extends {@link Stream} by adding the methods required to
* implement the HTTP/2 stream functionalities.</p>
*/
public interface IStream extends Stream, Attachable, Closeable
{
/**
* @return whether this stream is local or remote
*/
public boolean isLocal();
@Override
public ISession getSession();
/**
* @return the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream
* @see #setListener(Stream.Listener)
*/
public Listener getListener();
// TODO: move this to Stream.
public Data readData();
/**
* @param listener the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream
* @see #getListener()
*/
public void setListener(Listener listener);
/**
* <p>Sends the given list of frames.</p>
* <p>Typically used to send HTTP headers along with content and possibly trailers.</p>
*
* @param frameList the list of frames to send
* @param callback the callback that gets notified when the frames have been sent
*/
void send(FrameList frameList, Callback callback);
/**
* <p>Processes the given {@code frame}, belonging to this stream.</p>
*
* @param frame the frame to process
* @param callback the callback to complete when frame has been processed
*/
public void process(Frame frame, Callback callback);
/**
* <p>Updates the close state of this stream.</p>
*
* @param update whether to update the close state
* @param event the event that caused the close state update
* @return whether the stream has been fully closed by this invocation
*/
public boolean updateClose(boolean update, CloseState.Event event);
/**
* <p>Forcibly closes this stream.</p>
*/
@Override
public void close();
/**
* <p>Updates the stream send window by the given {@code delta}.</p>
*
* @param delta the delta value (positive or negative) to add to the stream send window
* @return the previous value of the stream send window
*/
public int updateSendWindow(int delta);
/**
* <p>Updates the stream receive window by the given {@code delta}.</p>
*
* @param delta the delta value (positive or negative) to add to the stream receive window
* @return the previous value of the stream receive window
*/
public int updateRecvWindow(int delta);
/**
* <p>Marks this stream as not idle so that the
* {@link #getIdleTimeout() idle timeout} is postponed.</p>
*/
public void notIdle();
/**
* @return whether the stream is closed remotely.
* @see #isClosed()
*/
boolean isRemotelyClosed();
/**
* Fail all data queued in the stream and reset
* demand to 0.
* @param x the exception to fail the data with.
* @return true if the end of the stream was reached, false otherwise.
*/
boolean failAllData(Throwable x);
/**
* @return whether this stream has been reset (locally or remotely) or has been failed
* @see #isReset()
* @see Listener#onFailure(Stream, int, String, Throwable, Callback)
*/
boolean isResetOrFailed();
/**
* Marks this stream as committed.
*
* @see #isCommitted()
*/
void commit();
/**
* @return whether bytes for this stream have been sent to the remote peer.
* @see #commit()
*/
boolean isCommitted();
/**
* <p>An ordered list of frames belonging to the same stream.</p>
*/
public static class FrameList
{
private final List<StreamFrame> frames;
/**
* <p>Creates a frame list of just the given HEADERS frame.</p>
*
* @param headers the HEADERS frame
*/
public FrameList(HeadersFrame headers)
{
Objects.requireNonNull(headers);
this.frames = List.of(headers);
}
/**
* <p>Creates a frame list of the given frames.</p>
*
* @param headers the HEADERS frame for the headers
* @param data the DATA frame for the content, or null if there is no content
* @param trailers the HEADERS frame for the trailers, or null if there are no trailers
*/
public FrameList(HeadersFrame headers, DataFrame data, HeadersFrame trailers)
{
Objects.requireNonNull(headers);
ArrayList<StreamFrame> frames = new ArrayList<>(3);
int streamId = headers.getStreamId();
if (data != null && data.getStreamId() != streamId)
throw new IllegalArgumentException("Invalid stream ID for DATA frame " + data);
if (trailers != null && trailers.getStreamId() != streamId)
throw new IllegalArgumentException("Invalid stream ID for HEADERS frame " + trailers);
frames.add(headers);
if (data != null)
frames.add(data);
if (trailers != null)
frames.add(trailers);
this.frames = Collections.unmodifiableList(frames);
}
/**
* @return the stream ID of the frames in this list
*/
public int getStreamId()
{
return frames.get(0).getStreamId();
}
/**
* @return a List of non-null frames
*/
public List<StreamFrame> getFrames()
{
return frames;
}
}
public static final class Data
{
private final DataFrame frame;
private final Runnable complete;
public Data(DataFrame frame, Runnable complete)
{
this.frame = frame;
this.complete = complete;
}
public DataFrame frame()
{
return frame;
}
public void complete()
{
complete.run();
}
}
}

View File

@ -16,9 +16,9 @@ package org.eclipse.jetty.http2;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,7 +37,7 @@ public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy
}
@Override
public void onDataConsumed(ISession session, IStream stream, int length)
public void onDataConsumed(Session session, Stream stream, int length)
{
if (length <= 0)
return;
@ -46,10 +46,10 @@ public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy
// This method is called when a whole flow controlled frame has been consumed.
// We send a WindowUpdate every time, even if the frame was very small.
List<Frame> frames = new ArrayList<>(2);
List<WindowUpdateFrame> frames = new ArrayList<>(2);
WindowUpdateFrame sessionFrame = new WindowUpdateFrame(0, length);
frames.add(sessionFrame);
session.updateRecvWindow(length);
updateRecvWindow(session, length);
if (LOG.isDebugEnabled())
LOG.debug("Data consumed, increased session recv window by {} for {}", length, session);
@ -64,12 +64,12 @@ public class SimpleFlowControlStrategy extends AbstractFlowControlStrategy
{
WindowUpdateFrame streamFrame = new WindowUpdateFrame(stream.getId(), length);
frames.add(streamFrame);
stream.updateRecvWindow(length);
updateRecvWindow(stream, length);
if (LOG.isDebugEnabled())
LOG.debug("Data consumed, increased stream recv window by {} for {}", length, stream);
}
}
session.frames(stream, frames, Callback.NOOP);
sendWindowUpdate(session, stream, frames);
}
}

View File

@ -19,7 +19,6 @@ import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PingFrame;
@ -36,7 +35,7 @@ import org.eclipse.jetty.util.Promise;
* Session session = ...;
* HeadersFrame frame = ...;
* Promise&lt;Stream&gt; promise = ...
* session.newStream(frame, promise, new Stream.Listener.Adapter()
* session.newStream(frame, promise, new Stream.Listener()
* {
* public void onHeaders(Stream stream, HeadersFrame frame)
* {
@ -122,6 +121,11 @@ public interface Session
*/
public boolean isClosed();
/**
* @return whether the push functionality is enabled
*/
public boolean isPushEnabled();
/**
* @return a snapshot of all the streams currently belonging to this session
*/
@ -169,6 +173,16 @@ public interface Session
return getRemoteAddress();
}
/**
* <p>Gracefully closes the session, returning a {@code CompletableFuture} that
* is completed when all the streams currently being processed are completed.</p>
* <p>Implementation is idempotent, i.e. calling this method a second time
* or concurrently results in a no-operation.</p>
*
* @return a {@code CompletableFuture} that is completed when all the streams are completed
*/
public CompletableFuture<Void> shutdown();
/**
* <p>A {@link Listener} is the passive counterpart of a {@link Session} and
* receives events happening on an HTTP/2 connection.</p>
@ -191,7 +205,10 @@ public interface Session
* @return a (possibly empty or null) map containing SETTINGS configuration
* options to send.
*/
public Map<Integer, Integer> onPreface(Session session);
public default Map<Integer, Integer> onPreface(Session session)
{
return null;
}
/**
* <p>Callback method invoked when a new stream is being created upon
@ -201,15 +218,19 @@ public interface Session
* {@link Stream#headers(HeadersFrame, Callback)}.</p>
* <p>Applications can detect whether request DATA frames will be arriving
* by testing {@link HeadersFrame#isEndStream()}. If the application is
* interested in processing the DATA frames, it must return a
* interested in processing the DATA frames, it must demand for DATA
* frames using {@link Stream#demand()} and return a
* {@link Stream.Listener} implementation that overrides
* {@link Stream.Listener#onData(Stream, DataFrame, Callback)}.</p>
* {@link Stream.Listener#onDataAvailable(Stream)}.</p>
*
* @param stream the newly created stream
* @param frame the HEADERS frame received
* @return a {@link Stream.Listener} that will be notified of stream events
*/
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame);
public default Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return null;
}
/**
* <p>Callback method invoked when a SETTINGS frame has been received.</p>
@ -217,7 +238,9 @@ public interface Session
* @param session the session
* @param frame the SETTINGS frame received
*/
public void onSettings(Session session, SettingsFrame frame);
public default void onSettings(Session session, SettingsFrame frame)
{
}
/**
* <p>Callback method invoked when a PING frame has been received.</p>
@ -225,16 +248,20 @@ public interface Session
* @param session the session
* @param frame the PING frame received
*/
public void onPing(Session session, PingFrame frame);
public default void onPing(Session session, PingFrame frame)
{
}
/**
* <p>Callback method invoked when a RST_STREAM frame has been received for an unknown stream.</p>
*
* @param session the session
* @param frame the RST_STREAM frame received
* @see Stream.Listener#onReset(Stream, ResetFrame)
* @see Stream.Listener#onReset(Stream, ResetFrame, Callback)
*/
public void onReset(Session session, ResetFrame frame);
public default void onReset(Session session, ResetFrame frame)
{
}
/**
* <p>Callback method invoked when a GOAWAY frame has been received.</p>
@ -255,26 +282,19 @@ public interface Session
*/
public default void onClose(Session session, GoAwayFrame frame, Callback callback)
{
try
{
onClose(session, frame);
callback.succeeded();
}
catch (Throwable x)
{
callback.failed(x);
}
callback.succeeded();
}
public void onClose(Session session, GoAwayFrame frame);
/**
* <p>Callback method invoked when the idle timeout expired.</p>
*
* @param session the session
* @return whether the session should be closed
*/
public boolean onIdleTimeout(Session session);
public default boolean onIdleTimeout(Session session)
{
return true;
}
/**
* <p>Callback method invoked when a failure has been detected for this session.</p>
@ -285,66 +305,7 @@ public interface Session
*/
public default void onFailure(Session session, Throwable failure, Callback callback)
{
try
{
onFailure(session, failure);
callback.succeeded();
}
catch (Throwable x)
{
callback.failed(x);
}
}
public void onFailure(Session session, Throwable failure);
/**
* <p>Empty implementation of {@link Stream.Listener}.</p>
*/
public static class Adapter implements Session.Listener
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
return null;
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return null;
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
}
@Override
public void onPing(Session session, PingFrame frame)
{
}
@Override
public void onReset(Session session, ResetFrame frame)
{
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
}
@Override
public boolean onIdleTimeout(Session session)
{
return true;
}
@Override
public void onFailure(Session session, Throwable failure)
{
}
callback.succeeded();
}
}
}

View File

@ -19,6 +19,8 @@ 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;
@ -42,6 +44,11 @@ public interface Stream
*/
public int getId();
/**
* @return the {@link org.eclipse.jetty.http2.api.Stream.Listener} associated with this stream
*/
public Listener getListener();
/**
* @return the session this stream is associated to
*/
@ -92,6 +99,34 @@ public interface Stream
*/
public void push(PushPromiseFrame frame, Promise<Stream> promise, Listener listener);
/**
* <p>Reads DATA frames from this stream, wrapping them in retainable {@link Data}
* objects.</p>
* <p>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:</p>
* <ul>
* <li>not all the bytes have been received so far, for example the remote
* peer did not send them yet, or they are in-flight</li>
* <li>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</li>
* </ul>
* <p>When the returned {@link Stream.Data} object is not {@code null},
* applications <em>must</em> call, either immediately or later (possibly
* asynchronously) {@link Stream.Data#release()} to notify the
* implementation that the bytes have been processed.</p>
* <p>{@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).</p>
*
* @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();
/**
* <p>Sends the given DATA {@code frame}.</p>
*
@ -144,11 +179,22 @@ public interface Stream
*/
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.
*/
@ -168,12 +214,26 @@ public interface Stream
public void setIdleTimeout(long idleTimeout);
/**
* <p>Demands {@code n} more {@code DATA} frames for this stream.</p>
* <p>Demands more {@code DATA} frames for this stream, causing
* {@link Listener#onDataAvailable(Stream)} to be invoked, possibly at a later time,
* when the stream has data to be read.</p>
* <p>This method is idempotent: calling it when there already is an
* outstanding demand to invoke {@link Listener#onDataAvailable(Stream)}
* is a no-operation.</p>
* <p>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.</p>
* <p>When all bytes have been read (via {@link #readData()}), further
* invocations of this method are a no-operation.</p>
* <p>It is always guaranteed that invoking this method from within
* {@code onDataAvailable(Stream)} will not cause a
* {@link StackOverflowError}.</p>
*
* @param n the increment of the demand, must be greater than zero
* @see Listener#onDataDemanded(Stream, DataFrame, Callback)
* @see #readData()
* @see Listener#onDataAvailable(Stream)
*/
public void demand(long n);
public void demand();
/**
* <p>A {@link Stream.Listener} is the passive counterpart of a {@link Stream} and receives
@ -181,7 +241,7 @@ public interface Stream
* <p>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.</p>
* <p>Applications control the delivery of data events by requesting them via
* {@link Stream#demand(long)}; the first event is always delivered, while subsequent
* {@link Stream#demand()}; the first event is always delivered, while subsequent
* events must be explicitly demanded.</p>
* <p>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
@ -207,7 +267,10 @@ public interface Stream
* @param stream the stream
* @param frame the HEADERS frame received
*/
public void onHeaders(Stream stream, HeadersFrame frame);
public default void onHeaders(Stream stream, HeadersFrame frame)
{
stream.demand();
}
/**
* <p>Callback method invoked when a PUSH_PROMISE frame has been received.</p>
@ -216,45 +279,66 @@ public interface Stream
* @param frame the PUSH_PROMISE frame received
* @return a Stream.Listener that will be notified of pushed stream events
*/
public Listener onPush(Stream stream, PushPromiseFrame frame);
/**
* <p>Callback method invoked before notifying the first DATA frame.</p>
* <p>The default implementation initializes the demand for DATA frames.</p>
*
* @param stream the stream
*/
public default void onBeforeData(Stream stream)
public default Listener onPush(Stream stream, PushPromiseFrame frame)
{
stream.demand(1);
return null;
}
/**
* <p>Callback method invoked when a DATA frame has been received.</p>
* <p>Callback method invoked if the application has expressed
* {@link Stream#demand() demand} for DATA frames, and if there
* may be content available.</p>
* <p>Applications that wish to handle DATA frames should call
* {@link Stream#demand()} for this method to be invoked when
* the data is available.</p>
* <p>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).</p>
* <p>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.</p>
* <p>Only one thread at a time invokes this method, although it
* may not be the same thread across different invocations.</p>
* <p>It is always guaranteed that invoking {@link Stream#demand()}
* from within this method will not cause a {@link StackOverflowError}.</p>
* <p>Typical usage:</p>
* <pre>
* class MyStreamListener implements Stream.Listener
* {
* &#64;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.getByteBuffer());
* // Notify that the content has been consumed.
* data.release();
* if (!data.frame().isEndStream())
* {
* // Demand to be called back.
* stream.demand();
* }
* }
* }
* }
* </pre>
*
* @param stream the stream
* @param frame the DATA frame received
* @param callback the callback to complete when the bytes of the DATA frame have been consumed
* @see #onDataDemanded(Stream, DataFrame, Callback)
* @see Stream#demand()
*/
public default void onData(Stream stream, DataFrame frame, Callback callback)
public default void onDataAvailable(Stream stream)
{
callback.succeeded();
}
/**
* <p>Callback method invoked when a DATA frame has been demanded.</p>
* <p>Implementations of this method must arrange to call (within the
* method or otherwise asynchronously) {@link #demand(long)}.</p>
*
* @param stream the stream
* @param frame the DATA frame received
* @param callback the callback to complete when the bytes of the DATA frame have been consumed
*/
public default void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
{
onData(stream, frame, callback);
stream.demand(1);
}
/**
@ -266,26 +350,7 @@ public interface Stream
*/
public default void onReset(Stream stream, ResetFrame frame, Callback callback)
{
try
{
onReset(stream, frame);
callback.succeeded();
}
catch (Throwable x)
{
callback.failed(x);
}
}
/**
* <p>Callback method invoked when a RST_STREAM frame has been received for this stream.</p>
*
* @param stream the stream
* @param frame the RST_FRAME received
* @see Session.Listener#onReset(Session, ResetFrame)
*/
public default void onReset(Stream stream, ResetFrame frame)
{
callback.succeeded();
}
/**
@ -296,7 +361,10 @@ public interface Stream
* @return true to reset the stream, false to ignore the idle timeout
* @see #getIdleTimeout()
*/
public boolean onIdleTimeout(Stream stream, Throwable x);
public default boolean onIdleTimeout(Stream stream, Throwable x)
{
return true;
}
/**
* <p>Callback method invoked when the stream failed.</p>
@ -320,38 +388,53 @@ public interface Stream
public default void onClosed(Stream stream)
{
}
}
/**
* <p>Empty implementation of {@link Listener}</p>
*/
public static class Adapter implements Listener
/**
* <p>A {@link Retainable} wrapper of a {@link DataFrame}.</p>
*/
public abstract static class Data implements Retainable
{
public static Data eof(int streamId)
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
}
return new Data.EOF(streamId);
}
@Override
public Listener onPush(Stream stream, PushPromiseFrame frame)
{
return null;
}
private final DataFrame frame;
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
}
public Data(DataFrame frame)
{
this.frame = frame;
}
@Override
public void onReset(Stream stream, ResetFrame frame)
{
}
public DataFrame frame()
{
return frame;
}
@Override
public boolean onIdleTimeout(Stream stream, Throwable x)
@Override
public void retain()
{
throw new UnsupportedOperationException();
}
@Override
public boolean release()
{
return true;
}
@Override
public String toString()
{
return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), frame());
}
private static class EOF extends Data
{
public EOF(int streamId)
{
return true;
super(new DataFrame(streamId, BufferUtil.EMPTY_BUFFER, true));
}
}
}

View File

@ -25,16 +25,7 @@ public interface ServerSessionListener extends Session.Listener
*
* @param session the session
*/
public void onAccept(Session session);
/**
* <p>Empty implementation of {@link ServerSessionListener}</p>
*/
public static class Adapter extends Session.Listener.Adapter implements ServerSessionListener
public default void onAccept(Session session)
{
@Override
public void onAccept(Session session)
{
}
}
}

View File

@ -15,7 +15,6 @@ package org.eclipse.jetty.http2.internal;
import java.util.function.Consumer;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
@ -32,7 +31,7 @@ public interface HTTP2Channel
*/
public interface Client
{
public void onData(DataFrame frame, Callback callback);
public void onDataAvailable();
public boolean onTimeout(Throwable failure);
@ -47,7 +46,7 @@ public interface HTTP2Channel
*/
public interface Server
{
public Runnable onData(DataFrame frame, Callback callback);
public Runnable onDataAvailable();
public Runnable onTrailer(HeadersFrame frame);

View File

@ -20,12 +20,13 @@ import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.internal.parser.Parser;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.Retainable;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.io.WriteFlusher;
@ -48,13 +49,13 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
private final AtomicLong bytesIn = new AtomicLong();
private final RetainableByteBufferPool retainableByteBufferPool;
private final Parser parser;
private final ISession session;
private final HTTP2Session session;
private final int bufferSize;
private final ExecutionStrategy strategy;
private boolean useInputDirectByteBuffers;
private boolean useOutputDirectByteBuffers;
protected HTTP2Connection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize)
protected HTTP2Connection(RetainableByteBufferPool retainableByteBufferPool, Executor executor, EndPoint endPoint, Parser parser, HTTP2Session session, int bufferSize)
{
super(endPoint, executor);
this.retainableByteBufferPool = retainableByteBufferPool;
@ -69,14 +70,14 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
@Override
public long getMessagesIn()
{
HTTP2Session session = (HTTP2Session)getSession();
HTTP2Session session = getSession();
return session.getStreamsOpened();
}
@Override
public long getMessagesOut()
{
HTTP2Session session = (HTTP2Session)getSession();
HTTP2Session session = getSession();
return session.getStreamsClosed();
}
@ -92,7 +93,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
return session.getBytesWritten();
}
public ISession getSession()
public HTTP2Session getSession()
{
return session;
}
@ -196,7 +197,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
private void offerTask(Runnable task)
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
tasks.offer(task);
}
@ -226,7 +227,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
private Runnable pollTask()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return tasks.poll();
}
@ -257,7 +258,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
{
Runnable task = pollTask();
if (LOG.isDebugEnabled())
LOG.debug("Dequeued task {}", String.valueOf(task));
LOG.debug("Dequeued task {}", task);
if (task != null)
return task;
@ -404,8 +405,7 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
{
NetworkBuffer networkBuffer = producer.networkBuffer;
networkBuffer.retain();
Callback callback = networkBuffer;
session.onData(frame, callback);
session.onData(new StreamData(frame, networkBuffer));
}
@Override
@ -416,7 +416,30 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
}
}
private class NetworkBuffer implements Callback
private static class StreamData extends Stream.Data
{
private final Retainable retainable;
private StreamData(DataFrame frame, Retainable retainable)
{
super(frame);
this.retainable = retainable;
}
@Override
public void retain()
{
retainable.retain();
}
@Override
public boolean release()
{
return retainable.release();
}
}
private class NetworkBuffer implements Retainable
{
private final RetainableByteBuffer delegate;
@ -440,46 +463,27 @@ public class HTTP2Connection extends AbstractConnection implements WriteFlusher.
return delegate.hasRemaining();
}
public boolean release()
{
return delegate.release();
}
@Override
public void retain()
{
delegate.retain();
}
@Override
public boolean release()
{
if (delegate.release())
{
if (LOG.isDebugEnabled())
LOG.debug("Released retained {}", this);
return true;
}
return false;
}
private void put(ByteBuffer source)
{
BufferUtil.append(delegate.getBuffer(), source);
}
@Override
public void succeeded()
{
completed(null);
}
@Override
public void failed(Throwable failure)
{
completed(failure);
}
private void completed(Throwable failure)
{
if (delegate.release())
{
if (LOG.isDebugEnabled())
LOG.debug("Released retained {}", this, failure);
}
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
}
}

View File

@ -26,7 +26,6 @@ import java.util.Queue;
import java.util.Set;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
@ -69,10 +68,10 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
return invocationType;
}
public void window(IStream stream, WindowUpdateFrame frame)
public void window(HTTP2Stream stream, WindowUpdateFrame frame)
{
Throwable closed;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
closed = terminated;
if (closed == null)
@ -86,7 +85,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
public boolean prepend(Entry entry)
{
Throwable closed;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
closed = terminated;
if (closed == null)
@ -105,7 +104,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
public boolean append(Entry entry)
{
Throwable closed;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
closed = terminated;
if (closed == null)
@ -124,7 +123,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
public boolean append(List<Entry> list)
{
Throwable closed;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
closed = terminated;
if (closed == null)
@ -142,7 +141,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
private int getWindowQueueSize()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return windows.size();
}
@ -150,7 +149,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
public int getFrameQueueSize()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return entries.size();
}
@ -162,7 +161,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
if (LOG.isDebugEnabled())
LOG.debug("Flushing {}", session);
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
if (terminated != null)
throw terminated;
@ -354,7 +353,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
Throwable closed;
Set<Entry> allEntries;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
closed = terminated;
terminated = x;
@ -383,7 +382,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
void terminate(Throwable cause)
{
Throwable closed;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
closed = terminated;
terminated = cause;
@ -425,9 +424,9 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
public abstract static class Entry extends Callback.Nested
{
protected final Frame frame;
protected final IStream stream;
protected final HTTP2Stream stream;
protected Entry(Frame frame, IStream stream, Callback callback)
protected Entry(Frame frame, HTTP2Stream stream, Callback callback)
{
super(callback);
this.frame = frame;
@ -514,10 +513,10 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
private class WindowEntry
{
private final IStream stream;
private final HTTP2Stream stream;
private final WindowUpdateFrame frame;
public WindowEntry(IStream stream, WindowUpdateFrame frame)
public WindowEntry(HTTP2Stream stream, WindowUpdateFrame frame)
{
this.stream = stream;
this.frame = frame;

View File

@ -13,11 +13,16 @@
package org.eclipse.jetty.http2.internal;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.WritePendingException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@ -30,8 +35,6 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.FailureFrame;
@ -39,31 +42,31 @@ import org.eclipse.jetty.http2.frames.Frame;
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.http2.frames.StreamFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.Attachable;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.MathUtils;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.Expirable
public class HTTP2Stream implements Stream, Attachable, Closeable, Callback, Dumpable, CyclicTimeouts.Expirable
{
private static final Logger LOG = LoggerFactory.getLogger(HTTP2Stream.class);
private final AutoLock lock = new AutoLock();
private Deque<DataEntry> dataQueue;
private Deque<Data> dataQueue;
private final AtomicReference<Object> attachment = new AtomicReference<>();
private final AtomicReference<ConcurrentMap<String, Object>> attributes = new AtomicReference<>();
private final AtomicReference<CloseState> closeState = new AtomicReference<>(CloseState.NOT_CLOSED);
private final AtomicInteger sendWindow = new AtomicInteger();
private final AtomicInteger recvWindow = new AtomicInteger();
private final long timeStamp = System.nanoTime();
private final ISession session;
private final HTTP2Session session;
private final int streamId;
private final MetaData.Request request;
private final boolean local;
@ -73,27 +76,21 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
private boolean remoteReset;
private Listener listener;
private long dataLength;
private long dataDemand;
private boolean dataInitial;
private boolean dataProcess;
private boolean dataEOF;
private boolean dataDemand;
private boolean dataStalled;
private boolean committed;
private long idleTimeout;
private long expireNanoTime = Long.MAX_VALUE;
public HTTP2Stream(ISession session, int streamId, MetaData.Request request, boolean local)
public HTTP2Stream(HTTP2Session session, int streamId, MetaData.Request request, boolean local)
{
this.session = session;
this.streamId = streamId;
this.request = request;
this.local = local;
this.dataLength = Long.MIN_VALUE;
this.dataInitial = true;
}
@Deprecated
public HTTP2Stream(Scheduler scheduler, ISession session, int streamId, MetaData.Request request, boolean local)
{
this(session, streamId, request, local);
this.dataStalled = true;
}
@Override
@ -121,7 +118,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
}
@Override
public ISession getSession()
public HTTP2Session getSession()
{
return session;
}
@ -132,7 +129,6 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
send(new FrameList(frame), callback);
}
@Override
public void send(FrameList frameList, Callback callback)
{
if (startWrite(callback))
@ -149,14 +145,14 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
public void data(DataFrame frame, Callback callback)
{
if (startWrite(callback))
session.data(this, this, frame);
session.data(this, frame, this);
}
@Override
public void reset(ResetFrame frame, Callback callback)
{
Throwable resetFailure = null;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
if (isReset())
{
@ -171,13 +167,13 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
if (resetFailure != null)
callback.failed(resetFailure);
else
((HTTP2Session)session).reset(this, frame, callback);
session.reset(this, frame, callback);
}
private boolean startWrite(Callback callback)
{
Throwable failure;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
failure = this.failure;
if (failure == null && sendCallback == null)
@ -213,7 +209,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
@Override
public boolean isReset()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return localReset || remoteReset;
}
@ -221,16 +217,15 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
private boolean isFailed()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return failure != null;
}
}
@Override
public boolean isResetOrFailed()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return isReset() || isFailed();
}
@ -249,39 +244,16 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
return state == CloseState.REMOTELY_CLOSED || state == CloseState.CLOSING || state == CloseState.CLOSED;
}
@Override
public boolean failAllData(Throwable x)
{
Deque<DataEntry> copy;
try (AutoLock l = lock.lock())
{
dataDemand = 0;
copy = dataQueue;
dataQueue = null;
}
DataEntry lastDataEntry = null;
if (copy != null)
{
copy.forEach(dataEntry -> dataEntry.callback.failed(x));
lastDataEntry = copy.isEmpty() ? null : copy.peekLast();
}
if (lastDataEntry == null)
return isRemotelyClosed();
return lastDataEntry.frame.isEndStream();
}
public boolean isLocallyClosed()
{
return closeState.get() == CloseState.LOCALLY_CLOSED;
}
@Override
public void commit()
{
committed = true;
}
@Override
public boolean isCommitted()
{
return committed;
@ -292,7 +264,6 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
return !isClosed();
}
@Override
public void notIdle()
{
long idleTimeout = getIdleTimeout();
@ -317,7 +288,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
{
this.idleTimeout = idleTimeout;
notIdle();
((HTTP2Session)session).scheduleTimeout(this);
session.scheduleTimeout(this);
}
protected void onIdleExpired(TimeoutException timeout)
@ -356,73 +327,53 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
@Override
public Data readData()
{
DataEntry dataEntry;
try (AutoLock l = lock.lock())
Data data;
try (AutoLock ignored = lock.lock())
{
if (dataQueue == null || dataQueue.isEmpty())
return null;
dataEntry = dataQueue.poll();
{
if (dataEOF)
return Data.eof(getId());
else
return null;
}
else
{
data = dataQueue.poll();
dataEOF = data.frame().isEndStream();
}
}
DataFrame frame = dataEntry.frame;
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
if (updateClose(data.frame().isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
return new Data(frame, () -> dataEntry.callback.succeeded());
return data;
}
@Override
public void setListener(Listener listener)
{
this.listener = listener;
}
@Override
public void process(Frame frame, Callback callback)
{
notIdle();
switch (frame.getType())
{
case PREFACE:
{
onNewStream(callback);
break;
}
case HEADERS:
{
onHeaders((HeadersFrame)frame, callback);
break;
}
case DATA:
{
onData((DataFrame)frame, callback);
break;
}
case RST_STREAM:
{
onReset((ResetFrame)frame, callback);
break;
}
case PUSH_PROMISE:
{
onPush((PushPromiseFrame)frame, callback);
break;
}
case WINDOW_UPDATE:
{
onWindowUpdate((WindowUpdateFrame)frame, callback);
break;
}
case FAILURE:
{
onFailure((FailureFrame)frame, callback);
break;
}
default:
{
throw new UnsupportedOperationException();
}
case PREFACE -> onNewStream(callback);
case HEADERS -> onHeaders((HeadersFrame)frame, callback);
case RST_STREAM -> onReset((ResetFrame)frame, callback);
case PUSH_PROMISE -> onPush((PushPromiseFrame)frame, callback);
case WINDOW_UPDATE -> onWindowUpdate((WindowUpdateFrame)frame, callback);
case FAILURE -> onFailure((FailureFrame)frame, callback);
default -> throw new UnsupportedOperationException();
}
}
public void process(Data data)
{
notIdle();
onData(data);
}
private void onNewStream(Callback callback)
{
notifyNewStream(this);
@ -440,120 +391,105 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
length = fields.getLongField(HttpHeader.CONTENT_LENGTH);
dataLength = length >= 0 ? length : Long.MIN_VALUE;
}
// Set EOF for either the request, the response or the trailers.
try (AutoLock ignored = lock.lock())
{
dataEOF = frame.isEndStream();
}
callback.succeeded();
}
private void onData(DataFrame frame, Callback callback)
private void onData(Data data)
{
// SPEC: remotely closed streams must be replied with a reset.
if (isRemotelyClosed())
{
if (LOG.isDebugEnabled())
LOG.debug("Data {} for already closed {}", data, this);
data.release();
reset(new ResetFrame(streamId, ErrorCode.STREAM_CLOSED_ERROR.code), Callback.NOOP);
callback.failed(new EOFException("stream_closed"));
return;
}
if (isReset())
{
// Just drop the frame.
callback.failed(new IOException("stream_reset"));
if (LOG.isDebugEnabled())
LOG.debug("Data {} for already reset {}", data, this);
data.release();
return;
}
if (dataLength != Long.MIN_VALUE)
{
DataFrame frame = data.frame();
dataLength -= frame.remaining();
if (dataLength < 0 || (frame.isEndStream() && dataLength != 0))
{
if (LOG.isDebugEnabled())
LOG.debug("Invalid data length {} for {}", data, this);
data.release();
reset(new ResetFrame(streamId, ErrorCode.PROTOCOL_ERROR.code), Callback.NOOP);
callback.failed(new IOException("invalid_data_length"));
return;
}
}
boolean initial;
boolean proceed = false;
DataEntry entry = new DataEntry(frame, callback);
try (AutoLock l = lock.lock())
boolean process;
try (AutoLock ignored = lock.lock())
{
if (dataQueue == null)
dataQueue = new ArrayDeque<>();
dataQueue.offer(entry);
initial = dataInitial;
if (initial)
{
dataInitial = false;
// Fake that we are processing data so we return
// from onBeforeData() before calling onData().
dataProcess = true;
}
else if (!dataProcess)
{
dataProcess = proceed = dataDemand > 0;
}
}
if (initial)
{
if (LOG.isDebugEnabled())
LOG.debug("Starting data processing of {} for {}", frame, this);
notifyBeforeData(this);
try (AutoLock l = lock.lock())
{
dataProcess = proceed = dataDemand > 0;
}
process = dataQueue.isEmpty() && dataDemand;
dataQueue.offer(data);
}
if (LOG.isDebugEnabled())
LOG.debug("{} data processing of {} for {}", proceed ? "Proceeding" : "Stalling", frame, this);
if (proceed)
LOG.debug("Data {} notifying onDataAvailable() {} for {}", data, process, this);
if (process)
processData();
}
@Override
public void demand(long n)
public void demand()
{
if (n <= 0)
throw new IllegalArgumentException("Invalid demand " + n);
long demand;
boolean proceed = false;
try (AutoLock l = lock.lock())
boolean process = false;
try (AutoLock ignored = lock.lock())
{
demand = dataDemand = MathUtils.cappedAdd(dataDemand, n);
if (!dataProcess)
dataProcess = proceed = dataQueue != null && !dataQueue.isEmpty();
dataDemand = true;
if (dataStalled && dataQueue != null && !dataQueue.isEmpty())
{
dataStalled = false;
process = true;
}
}
if (LOG.isDebugEnabled())
LOG.debug("Demand {}/{}, {} data processing for {}", n, demand, proceed ? "proceeding" : "stalling", this);
if (proceed)
LOG.debug("Demand, {} data processing for {}", process ? "proceeding" : "stalling", this);
if (process)
processData();
}
private void processData()
public void processData()
{
while (true)
{
DataEntry dataEntry;
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
if (dataQueue == null || dataQueue.isEmpty() || dataDemand == 0)
if (dataQueue == null || dataQueue.isEmpty() || !dataDemand)
{
if (LOG.isDebugEnabled())
LOG.debug("Stalling data processing for {}", this);
dataProcess = false;
dataStalled = true;
return;
}
--dataDemand;
dataEntry = dataQueue.poll();
dataDemand = false;
dataStalled = false;
}
DataFrame frame = dataEntry.frame;
if (updateClose(frame.isEndStream(), CloseState.Event.RECEIVED))
session.removeStream(this);
notifyDataDemanded(this, frame, dataEntry.callback);
notifyDataAvailable(this);
}
}
private long demand()
private boolean hasDemand()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
return dataDemand;
}
@ -561,7 +497,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
private void onReset(ResetFrame frame, Callback callback)
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
remoteReset = true;
failure = new EofException("reset");
@ -588,7 +524,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
private void onFailure(FailureFrame frame, Callback callback)
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
failure = frame.getFailure();
}
@ -599,7 +535,6 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
callback.succeeded();
}
@Override
public boolean updateClose(boolean update, CloseState.Event event)
{
if (LOG.isDebugEnabled())
@ -608,17 +543,12 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
if (!update)
return false;
switch (event)
return switch (event)
{
case RECEIVED:
return updateCloseAfterReceived();
case BEFORE_SEND:
return updateCloseBeforeSend();
case AFTER_SEND:
return updateCloseAfterSend();
default:
return false;
}
case RECEIVED -> updateCloseAfterReceived();
case BEFORE_SEND -> updateCloseBeforeSend();
case AFTER_SEND -> updateCloseAfterSend();
};
}
private boolean updateCloseAfterReceived()
@ -628,27 +558,25 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
CloseState current = closeState.get();
switch (current)
{
case NOT_CLOSED:
case NOT_CLOSED ->
{
if (closeState.compareAndSet(current, CloseState.REMOTELY_CLOSED))
return false;
break;
}
case LOCALLY_CLOSING:
case LOCALLY_CLOSING ->
{
if (closeState.compareAndSet(current, CloseState.CLOSING))
{
updateStreamCount(0, 1);
return false;
}
break;
}
case LOCALLY_CLOSED:
case LOCALLY_CLOSED ->
{
close();
return true;
}
default:
default ->
{
return false;
}
@ -663,22 +591,20 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
CloseState current = closeState.get();
switch (current)
{
case NOT_CLOSED:
case NOT_CLOSED ->
{
if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSING))
return false;
break;
}
case REMOTELY_CLOSED:
case REMOTELY_CLOSED ->
{
if (closeState.compareAndSet(current, CloseState.CLOSING))
{
updateStreamCount(0, 1);
return false;
}
break;
}
default:
default ->
{
return false;
}
@ -693,20 +619,17 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
CloseState current = closeState.get();
switch (current)
{
case NOT_CLOSED:
case LOCALLY_CLOSING:
case NOT_CLOSED, LOCALLY_CLOSING ->
{
if (closeState.compareAndSet(current, CloseState.LOCALLY_CLOSED))
return false;
break;
}
case REMOTELY_CLOSED:
case CLOSING:
case REMOTELY_CLOSED, CLOSING ->
{
close();
return true;
}
default:
default ->
{
return false;
}
@ -724,13 +647,11 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
return recvWindow.get();
}
@Override
public int updateSendWindow(int delta)
{
return sendWindow.getAndAdd(delta);
}
@Override
public int updateRecvWindow(int delta)
{
return recvWindow.getAndAdd(delta);
@ -755,7 +676,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
private void updateStreamCount(int deltaStream, int deltaClosing)
{
((HTTP2Session)session).updateStreamCount(isLocal(), deltaStream, deltaClosing);
session.updateStreamCount(isLocal(), deltaStream, deltaClosing);
}
@Override
@ -785,7 +706,7 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
private Callback endWrite()
{
try (AutoLock l = lock.lock())
try (AutoLock ignored = lock.lock())
{
Callback callback = sendCallback;
sendCallback = null;
@ -809,14 +730,14 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
}
}
private void notifyBeforeData(Stream stream)
private void notifyDataAvailable(Stream stream)
{
Listener listener = this.listener;
if (listener != null)
{
try
{
listener.onBeforeData(stream);
listener.onDataAvailable(stream);
}
catch (Throwable x)
{
@ -825,29 +746,10 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
}
else
{
stream.demand(1);
}
}
private void notifyDataDemanded(Stream stream, DataFrame frame, Callback callback)
{
Listener listener = this.listener;
if (listener != null)
{
try
{
listener.onDataDemanded(stream, frame, callback);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener {}", listener, x);
callback.failed(x);
}
}
else
{
callback.succeeded();
stream.demand(1);
Data data = readData();
if (data != null)
data.release();
stream.demand();
}
}
@ -939,14 +841,14 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
@Override
public String toString()
{
return String.format("%s@%x#%d@%x{sendWindow=%s,recvWindow=%s,demand=%d,reset=%b/%b,%s,age=%d,attachment=%s}",
return String.format("%s@%x#%d@%x{sendWindow=%s,recvWindow=%s,demand=%b,reset=%b/%b,%s,age=%d,attachment=%s}",
getClass().getSimpleName(),
hashCode(),
getId(),
session.hashCode(),
sendWindow,
recvWindow,
demand(),
hasDemand(),
localReset,
remoteReset,
closeState,
@ -954,15 +856,62 @@ public class HTTP2Stream implements IStream, Callback, Dumpable, CyclicTimeouts.
attachment);
}
private static class DataEntry
/**
* <p>An ordered list of frames belonging to the same stream.</p>
*/
public static class FrameList
{
private final DataFrame frame;
private final Callback callback;
private final List<StreamFrame> frames;
private DataEntry(DataFrame frame, Callback callback)
/**
* <p>Creates a frame list of just the given HEADERS frame.</p>
*
* @param headers the HEADERS frame
*/
public FrameList(HeadersFrame headers)
{
this.frame = frame;
this.callback = callback;
Objects.requireNonNull(headers);
this.frames = List.of(headers);
}
/**
* <p>Creates a frame list of the given frames.</p>
*
* @param headers the HEADERS frame for the headers
* @param data the DATA frame for the content, or null if there is no content
* @param trailers the HEADERS frame for the trailers, or null if there are no trailers
*/
public FrameList(HeadersFrame headers, DataFrame data, HeadersFrame trailers)
{
Objects.requireNonNull(headers);
ArrayList<StreamFrame> frames = new ArrayList<>(3);
int streamId = headers.getStreamId();
if (data != null && data.getStreamId() != streamId)
throw new IllegalArgumentException("Invalid stream ID for DATA frame " + data);
if (trailers != null && trailers.getStreamId() != streamId)
throw new IllegalArgumentException("Invalid stream ID for HEADERS frame " + trailers);
frames.add(headers);
if (data != null)
frames.add(data);
if (trailers != null)
frames.add(trailers);
this.frames = Collections.unmodifiableList(frames);
}
/**
* @return the stream ID of the frames in this list
*/
public int getStreamId()
{
return frames.get(0).getStreamId();
}
/**
* @return a List of non-null frames
*/
public List<StreamFrame> getFrames()
{
return frames;
}
}
}

View File

@ -20,19 +20,17 @@ import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -41,17 +39,17 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
{
private static final Logger LOG = LoggerFactory.getLogger(HTTP2StreamEndPoint.class);
private final AutoLock lock = new AutoLock();
private final Deque<Entry> dataQueue = new ArrayDeque<>();
private final AtomicReference<WriteState> writeState = new AtomicReference<>(WriteState.IDLE);
private final AtomicReference<Callback> readCallback = new AtomicReference<>();
private final long created = System.currentTimeMillis();
private final HTTP2Stream stream;
private final AtomicBoolean eof = new AtomicBoolean();
private final AtomicBoolean closed = new AtomicBoolean();
private final IStream stream;
private final AtomicReference<Throwable> failure = new AtomicReference<>();
private Stream.Data data;
private Connection connection;
public HTTP2StreamEndPoint(IStream stream)
public HTTP2StreamEndPoint(HTTP2Stream stream)
{
this.stream = stream;
}
@ -188,31 +186,31 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
@Override
public int fill(ByteBuffer sink) throws IOException
{
Entry entry;
try (AutoLock l = lock.lock())
{
entry = dataQueue.poll();
}
if (data != null)
return fillFromData(sink);
Throwable failure = this.failure.get();
if (failure != null)
throw IO.rethrow(failure);
if (eof.get())
return -1;
Stream.Data data = this.data = stream.readData();
if (LOG.isDebugEnabled())
LOG.debug("filled {} on {}", entry, this);
LOG.debug("filled {} on {}", data, this);
if (entry == null)
if (data == null)
return 0;
if (entry.isEOF())
{
entry.succeed();
return shutdownInput();
}
IOException failure = entry.ioFailure();
if (failure != null)
{
entry.fail(failure);
throw failure;
}
return fillFromData(sink);
}
private int fillFromData(ByteBuffer sink)
{
int sinkPosition = BufferUtil.flipToFill(sink);
ByteBuffer source = entry.buffer;
ByteBuffer source = data.frame().getData();
int sourceLength = source.remaining();
int length = Math.min(sourceLength, sink.remaining());
int sourceLimit = source.limit();
@ -221,27 +219,15 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
source.limit(sourceLimit);
BufferUtil.flipToFlush(sink, sinkPosition);
if (source.hasRemaining())
if (!source.hasRemaining())
{
try (AutoLock l = lock.lock())
{
dataQueue.offerFirst(entry);
}
eof.set(data.frame().isEndStream());
data.release();
data = null;
stream.demand();
}
else
{
entry.succeed();
// WebSocket does not have a backpressure API so you must always demand
// the next frame after succeeding the previous one.
stream.demand(1);
}
return length;
}
private int shutdownInput()
{
eof.set(true);
return -1;
return length;
}
@Override
@ -394,24 +380,19 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
WriteState current = writeState.get();
switch (current.state)
{
case IDLE:
case IDLE ->
{
if (!writeState.compareAndSet(current, WriteState.PENDING))
break;
continue;
// TODO: we really need a Stream primitive to write multiple frames.
ByteBuffer result = coalesce(buffers, false);
stream.data(new DataFrame(stream.getId(), result, false), Callback.from(() -> writeSuccess(callback), x -> writeFailure(x, callback)));
return;
case PENDING:
callback.failed(new WritePendingException());
return;
case OSHUTTING:
case OSHUT:
callback.failed(new EofException("Output shutdown"));
return;
case FAILED:
callback.failed(current.failure);
return;
}
case PENDING -> callback.failed(new WritePendingException());
case OSHUTTING, OSHUT -> callback.failed(new EofException("Output shutdown"));
case FAILED -> callback.failed(current.failure);
}
return;
}
}
}
@ -423,23 +404,21 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
WriteState current = writeState.get();
switch (current.state)
{
case IDLE:
case OSHUT:
callback.failed(new IllegalStateException());
return;
case PENDING:
case IDLE, OSHUT -> callback.failed(new IllegalStateException());
case PENDING ->
{
if (!writeState.compareAndSet(current, WriteState.IDLE))
break;
continue;
callback.succeeded();
return;
case OSHUTTING:
}
case OSHUTTING ->
{
callback.succeeded();
shutdownOutput();
return;
case FAILED:
callback.failed(current.failure);
return;
}
case FAILED -> callback.failed(current.failure);
}
return;
}
}
@ -539,54 +518,22 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
newConnection.onOpen();
}
protected void offerData(DataFrame frame, Callback callback)
protected void processDataAvailable()
{
ByteBuffer buffer = frame.getData();
if (LOG.isDebugEnabled())
LOG.debug("offering {} on {}", frame, this);
if (frame.isEndStream())
{
if (buffer.hasRemaining())
offer(buffer, Callback.from(Callback.NOOP::succeeded, callback::failed), null);
offer(BufferUtil.EMPTY_BUFFER, callback, Entry.EOF);
}
else
{
if (buffer.hasRemaining())
offer(buffer, callback, null);
else
callback.succeeded();
}
process();
}
protected void offerFailure(Throwable failure)
protected void processFailure(Throwable failure)
{
offer(BufferUtil.EMPTY_BUFFER, Callback.NOOP, failure);
process();
if (this.failure.compareAndSet(null, failure))
process();
}
private void offer(ByteBuffer buffer, Callback callback, Throwable failure)
private void process()
{
try (AutoLock l = lock.lock())
{
dataQueue.offer(new Entry(buffer, callback, failure));
}
}
protected void process()
{
boolean empty;
try (AutoLock l = lock.lock())
{
empty = dataQueue.isEmpty();
}
if (!empty)
{
Callback callback = readCallback.getAndSet(null);
if (callback != null)
callback.succeeded();
}
Callback callback = readCallback.getAndSet(null);
if (callback != null)
callback.succeeded();
}
@Override
@ -599,51 +546,6 @@ public abstract class HTTP2StreamEndPoint implements EndPoint
writeState);
}
private static class Entry
{
private static final Throwable EOF = new Throwable();
private final ByteBuffer buffer;
private final Callback callback;
private final Throwable failure;
private Entry(ByteBuffer buffer, Callback callback, Throwable failure)
{
this.buffer = buffer;
this.callback = callback;
this.failure = failure;
}
private boolean isEOF()
{
return failure == EOF;
}
private IOException ioFailure()
{
if (failure == null || isEOF())
return null;
return failure instanceof IOException ? (IOException)failure : new IOException(failure);
}
private void succeed()
{
callback.succeeded();
}
private void fail(Throwable failure)
{
callback.failed(failure);
}
@Override
public String toString()
{
return String.format("%s@%x[b=%s,eof=%b,f=%s]", getClass().getSimpleName(), hashCode(),
BufferUtil.toDetailString(buffer), isEOF(), isEOF() ? null : failure);
}
}
private static class WriteState
{
public static final WriteState IDLE = new WriteState(State.IDLE);

View File

@ -13,9 +13,8 @@
package org.eclipse.jetty.http2.client.http.internal;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.internal.HTTP2Channel;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.internal.HTTP2StreamEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.util.Callback;
@ -26,15 +25,15 @@ public class ClientHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT
{
private static final Logger LOG = LoggerFactory.getLogger(ClientHTTP2StreamEndPoint.class);
public ClientHTTP2StreamEndPoint(IStream stream)
public ClientHTTP2StreamEndPoint(HTTP2Stream stream)
{
super(stream);
}
@Override
public void onData(DataFrame frame, Callback callback)
public void onDataAvailable()
{
offerData(frame, callback);
processDataAvailable();
}
@Override

View File

@ -25,9 +25,10 @@ import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
public class HTTPSessionListenerPromise extends Session.Listener.Adapter implements Promise<Session>
public class HTTPSessionListenerPromise implements Session.Listener, Promise<Session>
{
private final AtomicMarkableReference<HttpConnectionOverHTTP2> connection = new AtomicMarkableReference<>(null, false);
private final Map<String, Object> context;
@ -82,13 +83,15 @@ public class HTTPSessionListenerPromise extends Session.Listener.Adapter impleme
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (failConnectionPromise(new ClosedChannelException()))
return;
HttpConnectionOverHTTP2 connection = this.connection.getReference();
if (connection != null)
onClose(connection, frame);
if (!failConnectionPromise(new ClosedChannelException()))
{
HttpConnectionOverHTTP2 connection = this.connection.getReference();
if (connection != null)
onClose(connection, frame);
}
callback.succeeded();
}
public void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame)
@ -110,13 +113,15 @@ public class HTTPSessionListenerPromise extends Session.Listener.Adapter impleme
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
if (failConnectionPromise(failure))
return;
HttpConnectionOverHTTP2 connection = this.connection.getReference();
if (connection != null)
connection.close(failure);
if (!failConnectionPromise(failure))
{
HttpConnectionOverHTTP2 connection = this.connection.getReference();
if (connection != null)
connection.close(failure);
}
callback.succeeded();
}
private boolean failConnectionPromise(Throwable failure)

View File

@ -19,15 +19,14 @@ import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
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.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Channel;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -88,7 +87,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
{
this.stream = stream;
if (stream != null)
((IStream)stream).setAttachment(receiver);
((HTTP2Stream)stream).setAttachment(receiver);
}
public boolean isFailed()
@ -186,30 +185,31 @@ public class HttpChannelOverHTTP2 extends HttpChannel
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment();
channel.onData(frame, callback);
HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment();
channel.onDataAvailable();
}
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
// TODO: needs to call HTTP2Channel?
receiver.onReset(stream, frame);
receiver.onReset(frame);
callback.succeeded();
}
@Override
public boolean onIdleTimeout(Stream stream, Throwable x)
{
HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment();
HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment();
return channel.onTimeout(x);
}
@Override
public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback)
{
HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment();
HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment();
channel.onFailure(failure, callback);
}
}

View File

@ -15,9 +15,8 @@ package org.eclipse.jetty.http2.client.http.internal;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import org.eclipse.jetty.client.HttpChannel;
@ -33,18 +32,16 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
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.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Channel;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -52,7 +49,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
{
private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP2.class);
private final ContentNotifier contentNotifier = new ContentNotifier(this);
private final AtomicBoolean notifySuccess = new AtomicBoolean();
public HttpReceiverOverHTTP2(HttpChannel channel)
{
@ -68,81 +65,91 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
@Override
protected void receive()
{
contentNotifier.process(true);
}
// Called when the application resumes demand of content.
if (LOG.isDebugEnabled())
LOG.debug("Resuming response processing on {}", this);
@Override
protected void reset()
{
super.reset();
contentNotifier.reset();
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return;
if (notifySuccess.get())
responseSuccess(exchange);
else
getHttpChannel().getStream().demand();
}
void onHeaders(Stream stream, HeadersFrame frame)
{
MetaData metaData = frame.getMetaData();
if (metaData.isResponse())
onResponse(stream, frame);
else
onTrailer(frame);
}
private void onResponse(Stream stream, HeadersFrame frame)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return;
MetaData.Response response = (MetaData.Response)frame.getMetaData();
HttpResponse httpResponse = exchange.getResponse();
MetaData metaData = frame.getMetaData();
if (metaData.isResponse())
httpResponse.version(response.getHttpVersion()).status(response.getStatus()).reason(response.getReason());
if (responseBegin(exchange))
{
MetaData.Response response = (MetaData.Response)frame.getMetaData();
httpResponse.version(response.getHttpVersion()).status(response.getStatus()).reason(response.getReason());
if (responseBegin(exchange))
HttpFields headers = response.getFields();
for (HttpField header : headers)
{
HttpFields headers = response.getFields();
for (HttpField header : headers)
{
if (!responseHeader(exchange, header))
return;
}
if (!responseHeader(exchange, header))
return;
}
HttpRequest httpRequest = exchange.getRequest();
if (MetaData.isTunnel(httpRequest.getMethod(), httpResponse.getStatus()))
{
ClientHTTP2StreamEndPoint endPoint = new ClientHTTP2StreamEndPoint((IStream)stream);
long idleTimeout = httpRequest.getIdleTimeout();
if (idleTimeout > 0)
endPoint.setIdleTimeout(idleTimeout);
if (LOG.isDebugEnabled())
LOG.debug("Successful HTTP2 tunnel on {} via {}", stream, endPoint);
((IStream)stream).setAttachment(endPoint);
HttpConversation conversation = httpRequest.getConversation();
conversation.setAttribute(EndPoint.class.getName(), endPoint);
HttpUpgrader upgrader = (HttpUpgrader)conversation.getAttribute(HttpUpgrader.class.getName());
if (upgrader != null)
upgrade(upgrader, httpResponse, endPoint);
}
HttpRequest httpRequest = exchange.getRequest();
if (MetaData.isTunnel(httpRequest.getMethod(), httpResponse.getStatus()))
{
ClientHTTP2StreamEndPoint endPoint = new ClientHTTP2StreamEndPoint((HTTP2Stream)stream);
long idleTimeout = httpRequest.getIdleTimeout();
if (idleTimeout > 0)
endPoint.setIdleTimeout(idleTimeout);
if (LOG.isDebugEnabled())
LOG.debug("Successful HTTP2 tunnel on {} via {}", stream, endPoint);
((HTTP2Stream)stream).setAttachment(endPoint);
HttpConversation conversation = httpRequest.getConversation();
conversation.setAttribute(EndPoint.class.getName(), endPoint);
HttpUpgrader upgrader = (HttpUpgrader)conversation.getAttribute(HttpUpgrader.class.getName());
if (upgrader != null)
upgrade(upgrader, httpResponse, endPoint);
}
if (responseHeaders(exchange))
{
int status = response.getStatus();
if (frame.isEndStream() || HttpStatus.isInterim(status))
responseSuccess(exchange);
}
notifySuccess.set(frame.isEndStream());
if (responseHeaders(exchange))
{
int status = response.getStatus();
if (frame.isEndStream() || HttpStatus.isInterim(status))
responseSuccess(exchange);
else
{
if (frame.isEndStream())
{
// There is no demand to trigger response success, so add
// a poison pill to trigger it when there will be demand.
notifyContent(exchange, new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
}
}
stream.demand();
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Stalling response processing, no demand after headers on {}", this);
}
}
else // Response trailers.
{
HttpFields trailers = metaData.getFields();
trailers.forEach(httpResponse::trailer);
// Previous DataFrames had endStream=false, so
// add a poison pill to trigger response success
// after all normal DataFrames have been consumed.
notifyContent(exchange, new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
}
}
private void onTrailer(HeadersFrame frame)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return;
HttpFields trailers = frame.getMetaData().getFields();
trailers.forEach(exchange.getResponse()::trailer);
responseSuccess(exchange);
}
private void upgrade(HttpUpgrader upgrader, HttpResponse response, EndPoint endPoint)
@ -190,20 +197,59 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
}
@Override
public void onData(DataFrame frame, Callback callback)
public void onDataAvailable()
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return;
Stream stream = getHttpChannel().getStream();
if (stream == null)
return;
Stream.Data data = stream.readData();
if (data != null)
{
callback.failed(new IOException("terminated"));
ByteBuffer byteBuffer = data.frame().getData();
if (byteBuffer.hasRemaining())
{
notifySuccess.set(data.frame().isEndStream());
Callback callback = Callback.from(Invocable.InvocationType.NON_BLOCKING, data::release, x ->
{
data.release();
if (responseFailure(x))
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
});
boolean proceed = responseContent(exchange, byteBuffer, callback);
if (proceed)
{
if (data.frame().isEndStream())
responseSuccess(exchange);
else
stream.demand();
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Stalling response processing, no demand after {} on {}", data, this);
}
}
else
{
data.release();
if (data.frame().isEndStream())
responseSuccess(exchange);
else
stream.demand();
}
}
else
{
notifyContent(exchange, frame, callback);
stream.demand();
}
}
void onReset(Stream stream, ResetFrame frame)
void onReset(ResetFrame frame)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
@ -227,201 +273,4 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
responseFailure(failure);
callback.succeeded();
}
private void notifyContent(HttpExchange exchange, DataFrame frame, Callback callback)
{
contentNotifier.offer(exchange, frame, callback);
}
private class ContentNotifier
{
private final AutoLock lock = new AutoLock();
private final Queue<DataInfo> queue = new ArrayDeque<>();
private final HttpReceiverOverHTTP2 receiver;
private DataInfo dataInfo;
private boolean active;
private boolean resume;
private boolean stalled;
private ContentNotifier(HttpReceiverOverHTTP2 receiver)
{
this.receiver = receiver;
}
private void offer(HttpExchange exchange, DataFrame frame, Callback callback)
{
DataInfo dataInfo = new DataInfo(exchange, frame, callback);
if (LOG.isDebugEnabled())
LOG.debug("Queueing content {}", dataInfo);
enqueue(dataInfo);
process(false);
}
private void enqueue(DataInfo dataInfo)
{
try (AutoLock l = lock.lock())
{
queue.offer(dataInfo);
}
}
private void process(boolean resume)
{
// Allow only one thread at a time.
boolean busy = active(resume);
if (LOG.isDebugEnabled())
LOG.debug("Resuming({}) processing({}) of content", resume, !busy);
if (busy)
return;
// Process only if there is demand.
try (AutoLock l = lock.lock())
{
if (!resume && demand() <= 0)
{
if (LOG.isDebugEnabled())
LOG.debug("Stalling processing, content available but no demand");
active = false;
stalled = true;
return;
}
}
while (true)
{
if (dataInfo != null)
{
if (dataInfo.frame.isEndStream())
{
receiver.responseSuccess(dataInfo.exchange);
// Return even if active, as reset() will be called later.
return;
}
}
try (AutoLock l = lock.lock())
{
dataInfo = queue.poll();
if (LOG.isDebugEnabled())
LOG.debug("Processing content {}", dataInfo);
if (dataInfo == null)
{
active = false;
return;
}
}
ByteBuffer buffer = dataInfo.frame.getData();
Callback callback = dataInfo.callback;
if (buffer.hasRemaining())
{
boolean proceed = receiver.responseContent(dataInfo.exchange, buffer, Callback.from(callback::succeeded, x -> fail(callback, x)));
if (!proceed)
{
// The call to responseContent() said we should
// stall, but another thread may have just resumed.
boolean stall = stall();
if (LOG.isDebugEnabled())
LOG.debug("Stalling({}) processing", stall);
if (stall)
return;
}
}
else
{
callback.succeeded();
}
}
}
private boolean active(boolean resume)
{
try (AutoLock l = lock.lock())
{
if (active)
{
// There is a thread in process(),
// but it may be about to exit, so
// remember "resume" to signal the
// processing thread to continue.
if (resume)
this.resume = true;
return true;
}
// If there is no demand (i.e. stalled
// and not resuming) then don't process.
if (stalled && !resume)
return true;
// Start processing.
active = true;
stalled = false;
return false;
}
}
/**
* Called when there is no demand, this method checks whether
* the processing should really stop or it should continue.
*
* @return true to stop processing, false to continue processing
*/
private boolean stall()
{
try (AutoLock l = lock.lock())
{
if (resume)
{
// There was no demand, but another thread
// just demanded, continue processing.
resume = false;
return false;
}
// There is no demand, stop processing.
active = false;
stalled = true;
return true;
}
}
private void reset()
{
dataInfo = null;
try (AutoLock l = lock.lock())
{
queue.clear();
active = false;
resume = false;
stalled = false;
}
}
private void fail(Callback callback, Throwable failure)
{
callback.failed(failure);
receiver.responseFailure(failure);
}
private class DataInfo
{
private final HttpExchange exchange;
private final DataFrame frame;
private final Callback callback;
private DataInfo(HttpExchange exchange, DataFrame frame, Callback callback)
{
this.exchange = exchange;
this.frame = frame;
this.callback = callback;
}
@Override
public String toString()
{
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), frame);
}
}
}
}

View File

@ -25,11 +25,11 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
@ -123,8 +123,8 @@ public class HttpSenderOverHTTP2 extends HttpSender
}
HttpChannelOverHTTP2 channel = getHttpChannel();
IStream.FrameList frameList = new IStream.FrameList(headersFrame, dataFrame, trailersFrame);
((ISession)channel.getSession()).newStream(frameList, new HeadersPromise(request, callback), channel.getStreamListener());
HTTP2Stream.FrameList frameList = new HTTP2Stream.FrameList(headersFrame, dataFrame, trailersFrame);
((HTTP2Session)channel.getSession()).newStream(frameList, new HeadersPromise(request, callback), channel.getStreamListener());
}
private HttpFields retrieveTrailers(HttpRequest request)

View File

@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.RateControl;
import org.eclipse.jetty.http2.WindowRateControl;
import org.eclipse.jetty.http2.api.Session;
@ -33,6 +32,7 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.internal.HTTP2Connection;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.generator.Generator;
import org.eclipse.jetty.http2.internal.parser.ServerParser;
import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection;
@ -311,13 +311,13 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
@ManagedObject("The container of HTTP/2 sessions")
public static class HTTP2SessionContainer implements Connection.Listener, Graceful, Dumpable
{
private final Set<ISession> sessions = ConcurrentHashMap.newKeySet();
private final Set<HTTP2Session> sessions = ConcurrentHashMap.newKeySet();
private final AtomicReference<CompletableFuture<Void>> shutdown = new AtomicReference<>();
@Override
public void onOpened(Connection connection)
{
ISession session = ((HTTP2Connection)connection).getSession();
HTTP2Session session = ((HTTP2Connection)connection).getSession();
sessions.add(session);
LifeCycle.start(session);
if (isShutdown())
@ -327,7 +327,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
@Override
public void onClosed(Connection connection)
{
ISession session = ((HTTP2Connection)connection).getSession();
HTTP2Session session = ((HTTP2Connection)connection).getSession();
if (sessions.remove(session))
LifeCycle.stop(session);
}
@ -371,7 +371,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
return shutdown.get() != null;
}
private CompletableFuture<Void> shutdown(ISession session)
private CompletableFuture<Void> shutdown(HTTP2Session session)
{
return session.shutdown();
}

View File

@ -17,16 +17,15 @@ import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http2.HTTP2Cipher;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
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.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.server.internal.HTTP2ServerConnection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
@ -70,7 +69,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
return acceptable;
}
protected class HTTPServerSessionListener extends ServerSessionListener.Adapter implements Stream.Listener
protected class HTTPServerSessionListener implements ServerSessionListener, Stream.Listener
{
private final EndPoint endPoint;
@ -93,26 +92,17 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
getConnection().onNewStream((IStream)stream, frame);
return this;
}
@Override
public void onBeforeData(Stream stream)
{
// Do not notify DATA frame listeners until demanded.
getConnection().onNewStream((HTTP2Stream)stream, frame);
// Do not demand for DATA frames.
// This allows CONNECT requests with pseudo header :protocol
// (e.g. WebSocket over HTTP/2) to buffer DATA frames
// until they upgrade and are ready to process them.
return this;
}
@Override
public boolean onIdleTimeout(Session session)
{
boolean close = super.onIdleTimeout(session);
if (!close)
return false;
long idleTimeout = getConnection().getEndPoint().getIdleTimeout();
return getConnection().onSessionTimeout(new TimeoutException("Session idle timeout " + idleTimeout + " ms"));
}
@ -137,7 +127,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (frame.isEndStream())
getConnection().onTrailers((IStream)stream, frame);
getConnection().onTrailers(stream, frame);
else
close(stream, "invalid_trailers");
}
@ -151,9 +141,9 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
}
@Override
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
getConnection().onData((IStream)stream, frame, callback);
getConnection().onDataAvailable(stream);
}
@Override
@ -173,13 +163,13 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
private void onFailure(Stream stream, Throwable failure, Callback callback)
{
getConnection().onStreamFailure((IStream)stream, failure, callback);
getConnection().onStreamFailure(stream, failure, callback);
}
@Override
public boolean onIdleTimeout(Stream stream, Throwable x)
{
return getConnection().onStreamTimeout((IStream)stream, x);
return getConnection().onStreamTimeout(stream, x);
}
private void close(Stream stream, String reason)

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.util.Callback;
public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionFactory
{
@ -111,9 +112,9 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
delegate.onClose(session, frame);
delegate.onClose(session, frame, callback);
}
@Override
@ -123,9 +124,9 @@ public class RawHTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnecti
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
delegate.onFailure(session, failure);
delegate.onFailure(session, failure, callback);
}
}
}

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.http2.server.internal;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Base64;
@ -29,16 +28,16 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MetaData.Request;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PrefaceFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.internal.HTTP2Channel;
import org.eclipse.jetty.http2.internal.HTTP2Connection;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.internal.parser.ServerParser;
import org.eclipse.jetty.http2.internal.parser.SettingsBodyParser;
import org.eclipse.jetty.io.Connection;
@ -65,7 +64,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
private final HttpConfiguration httpConfig;
private final String id;
public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Connector connector, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener)
public HTTP2ServerConnection(RetainableByteBufferPool retainableByteBufferPool, Connector connector, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, HTTP2Session session, int inputBufferSize, ServerSessionListener listener)
{
super(retainableByteBufferPool, connector.getExecutor(), endPoint, parser, session, inputBufferSize);
this.connector = connector;
@ -83,7 +82,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
@Override
public void onOpen()
{
ISession session = getSession();
HTTP2Session session = getSession();
notifyAccept(session);
for (Frame frame : upgradeFrames)
{
@ -93,7 +92,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
produce();
}
private void notifyAccept(ISession session)
private void notifyAccept(HTTP2Session session)
{
try
{
@ -105,7 +104,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
}
}
public void onNewStream(IStream stream, HeadersFrame frame)
public void onNewStream(HTTP2Stream stream, HeadersFrame frame)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing {} on {}", frame, stream);
@ -119,29 +118,25 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
offerTask(task, false);
}
public void onData(IStream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing {} on {}", frame, stream);
LOG.debug("Processing data available on {}", stream);
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment();
if (channel != null)
{
Runnable task = channel.onData(frame, callback);
Runnable task = channel.onDataAvailable();
if (task != null)
offerTask(task, false);
}
else
{
callback.failed(new IOException("channel_not_found"));
}
}
public void onTrailers(IStream stream, HeadersFrame frame)
public void onTrailers(Stream stream, HeadersFrame frame)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing trailers {} on {}", frame, stream);
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment();
if (channel != null)
{
Runnable task = channel.onTrailer(frame);
@ -150,20 +145,20 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
}
}
public boolean onStreamTimeout(IStream stream, Throwable failure)
public boolean onStreamTimeout(Stream stream, Throwable failure)
{
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment();
boolean result = channel != null && channel.onTimeout(failure, task -> offerTask(task, true));
if (LOG.isDebugEnabled())
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", stream, failure);
return result;
}
public void onStreamFailure(IStream stream, Throwable failure, Callback callback)
public void onStreamFailure(Stream stream, Throwable failure, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing stream failure on {}", stream, failure);
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment();
if (channel != null)
{
Runnable task = channel.onFailure(failure, callback);
@ -182,10 +177,10 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
public boolean onSessionTimeout(Throwable failure)
{
ISession session = getSession();
HTTP2Session session = getSession();
// Compute whether all requests are idle.
boolean result = session.getStreams().stream()
.map(stream -> (IStream)stream)
.map(stream -> (HTTP2Stream)stream)
.map(stream -> (HTTP2Channel.Server)stream.getAttachment())
.filter(Objects::nonNull)
.map(HTTP2Channel.Server::isIdle)
@ -203,7 +198,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
callback.succeeded();
}
public void push(IStream stream, MetaData.Request request)
public void push(HTTP2Stream stream, MetaData.Request request)
{
if (LOG.isDebugEnabled())
LOG.debug("Processing push {} on {}", request, stream);

View File

@ -20,7 +20,6 @@ import java.util.Map;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -32,6 +31,7 @@ import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.internal.generator.Generator;
import org.eclipse.jetty.http2.internal.parser.ServerParser;
import org.eclipse.jetty.io.EndPoint;
@ -82,7 +82,7 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
return;
}
IStream stream = getStream(streamId);
HTTP2Stream stream = getStream(streamId);
MetaData metaData = frame.getMetaData();
if (metaData.isRequest())
@ -169,19 +169,12 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
{
switch (frame.getType())
{
case PREFACE:
onPreface();
break;
case SETTINGS:
case PREFACE -> onPreface();
case SETTINGS ->
// SPEC: the required reply to this SETTINGS frame is the 101 response.
onSettings((SettingsFrame)frame, false);
break;
case HEADERS:
onHeaders((HeadersFrame)frame);
break;
default:
super.onFrame(frame);
break;
case HEADERS -> onHeaders((HeadersFrame)frame);
default -> super.onFrame(frame);
}
}
}

View File

@ -24,7 +24,6 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.Trailers;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
@ -32,6 +31,7 @@ import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Channel;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.HttpChannel;
@ -50,14 +50,14 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
private final AutoLock lock = new AutoLock();
private final HTTP2ServerConnection _connection;
private final HttpChannel _httpChannel;
private final IStream _stream;
private final HTTP2Stream _stream;
private final long _nanoTimeStamp;
private Content.Chunk _chunk;
private MetaData.Response _metaData;
private boolean committed;
private boolean _demand;
public HttpStreamOverHTTP2(HTTP2ServerConnection connection, HttpChannel httpChannel, IStream stream)
public HttpStreamOverHTTP2(HTTP2ServerConnection connection, HttpChannel httpChannel, HTTP2Stream stream)
{
_connection = connection;
_httpChannel = httpChannel;
@ -139,13 +139,16 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
if (chunk != null)
return chunk;
IStream.Data data = _stream.readData();
Stream.Data data = _stream.readData();
if (data == null)
return null;
chunk = createChunk(data);
data.release();
try (AutoLock ignored = lock.lock())
{
_chunk = newChunk(data.frame(), data::complete);
_chunk = chunk;
}
}
}
@ -172,27 +175,23 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
}
else if (demand)
{
_stream.demand(1);
_stream.demand();
}
}
@Override
public Runnable onData(DataFrame frame, Callback callback)
public Runnable onDataAvailable()
{
Content.Chunk chunk = newChunk(frame, callback::succeeded);
try (AutoLock ignored = lock.lock())
{
_demand = false;
_chunk = chunk;
}
if (LOG.isDebugEnabled())
{
LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content",
LOG.debug("HTTP2 Request #{}/{}: data available",
_stream.getId(),
Integer.toHexString(_stream.getSession().hashCode()),
frame.remaining(),
frame.isEndStream() ? "last" : "some");
Integer.toHexString(_stream.getSession().hashCode()));
}
return _httpChannel.onContentAvailable();
@ -218,9 +217,12 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
return _httpChannel.onContentAvailable();
}
private Content.Chunk newChunk(DataFrame frame, Runnable complete)
private Content.Chunk createChunk(Stream.Data data)
{
return Content.Chunk.from(frame.getData(), frame.isEndStream(), complete);
// As we are passing the ByteBuffer to the Chunk we need to retain.
data.retain();
DataFrame frame = data.frame();
return Content.Chunk.from(frame.getData(), frame.isEndStream(), data);
}
@Override
@ -342,7 +344,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
System.lineSeparator(), HttpVersion.HTTP_2, _metaData.getStatus(),
System.lineSeparator(), _metaData.getFields());
}
_stream.send(new IStream.FrameList(headersFrame, dataFrame, trailersFrame), callback);
_stream.send(new HTTP2Stream.FrameList(headersFrame, dataFrame, trailersFrame), callback);
}
private void sendContent(MetaData.Request request, ByteBuffer content, boolean last, Callback callback)
@ -422,7 +424,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
@Override
public void succeeded(Stream pushStream)
{
_connection.push((IStream)pushStream, request);
_connection.push((HTTP2Stream)pushStream, request);
}
@Override
@ -431,7 +433,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
if (LOG.isDebugEnabled())
LOG.debug("Could not HTTP/2 push {}", request, x);
}
}, new Stream.Listener.Adapter()); // TODO: handle reset from the client ?
}, null); // TODO: handle reset from the client ?
}
public Runnable onPushRequest(MetaData.Request request)

View File

@ -269,7 +269,7 @@ public class HttpTransportOverHTTP2
// if (LOG.isDebugEnabled())
// LOG.debug("Could not push {}", request, x);
// }
// }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ?
// }, null); // TODO: handle reset from the client ?
// }
//
// private void sendDataFrame(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback)

View File

@ -15,10 +15,9 @@ package org.eclipse.jetty.http2.server.internal;
import java.util.function.Consumer;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.internal.HTTP2Channel;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.internal.HTTP2StreamEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.util.Callback;
@ -29,15 +28,15 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT
{
private static final Logger LOG = LoggerFactory.getLogger(ServerHTTP2StreamEndPoint.class);
public ServerHTTP2StreamEndPoint(IStream stream)
public ServerHTTP2StreamEndPoint(HTTP2Stream stream)
{
super(stream);
}
@Override
public Runnable onData(DataFrame frame, Callback callback)
public Runnable onDataAvailable()
{
offerData(frame, callback);
processDataAvailable();
return null;
}
@ -59,7 +58,7 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT
result = connection.onIdleExpired();
if (result)
{
offerFailure(failure);
processFailure(failure);
consumer.accept(() -> close(failure));
}
return result;
@ -70,7 +69,7 @@ public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HT
{
if (LOG.isDebugEnabled())
LOG.debug("failure on {}: {}", this, failure);
offerFailure(failure);
processFailure(failure);
close(failure);
return callback::succeeded;
}

View File

@ -15,7 +15,7 @@
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>h2spec-maven-plugin</artifactId>
<version>1.0.10-SNAPSHOT</version>
<version>1.0.10</version>
<configuration>
<mainClass>org.eclipse.jetty.http2.tests.H2SpecServer</mainClass>
<skip>${skipTests}</skip>
@ -24,7 +24,6 @@
<reportsDirectory>${project.build.directory}/h2spec-reports</reportsDirectory>
<excludeSpecs>
<excludeSpec>3.5 - Sends invalid connection preface</excludeSpec>
<excludeSpec>8.1.2.3 - 8.1.2.6. Malformed Requests and Responses</excludeSpec>
</excludeSpecs>
</configuration>
<executions>

View File

@ -53,13 +53,13 @@ public class AsyncIOTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
final CountDownLatch latch = new CountDownLatch(1);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter()
session.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -87,13 +87,13 @@ public class AsyncIOTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
final CountDownLatch latch = new CountDownLatch(1);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter()
session.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -145,13 +145,13 @@ public class AsyncIOTest extends AbstractTest
}
});
Session session = newClient(new Session.Listener.Adapter());
Session session = newClient(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
final CountDownLatch latch = new CountDownLatch(1);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter()
session.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -212,7 +212,7 @@ public class AsyncIOTest extends AbstractTest
}
});
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -227,7 +227,7 @@ public class AsyncIOTest extends AbstractTest
HeadersFrame frame = new HeadersFrame(metaData, null, true);
CountDownLatch latch = new CountDownLatch(1);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter()
session.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onClosed(Stream stream)

View File

@ -56,13 +56,13 @@ public class AsyncServletTest extends AbstractTest
// }
// });
//
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
//
// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// ByteArrayOutputStream buffer = new ByteArrayOutputStream();
// CountDownLatch latch = new CountDownLatch(1);
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -93,13 +93,13 @@ public class AsyncServletTest extends AbstractTest
// long idleTimeout = 1000;
// client.setIdleTimeout(idleTimeout);
//
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// FuturePromise<Stream> promise = new FuturePromise<>();
// CountDownLatch responseLatch = new CountDownLatch(1);
// CountDownLatch failLatch = new CountDownLatch(1);
// session.newStream(frame, promise, new Stream.Listener.Adapter()
// session.newStream(frame, promise, new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -130,12 +130,12 @@ public class AsyncServletTest extends AbstractTest
// long idleTimeout = 1000;
// client.setIdleTimeout(10 * idleTimeout);
//
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// FuturePromise<Stream> promise = new FuturePromise<>();
// CountDownLatch clientLatch = new CountDownLatch(1);
// session.newStream(frame, promise, new Stream.Listener.Adapter()
// session.newStream(frame, promise, new Stream.Listener()
// {
// @Override
// public boolean onIdleTimeout(Stream stream, Throwable x)
@ -178,11 +178,11 @@ public class AsyncServletTest extends AbstractTest
//
// prepareClient();
// client.start();
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// FuturePromise<Stream> promise = new FuturePromise<>();
// session.newStream(frame, promise, new Stream.Listener.Adapter());
// session.newStream(frame, promise, null);
// Stream stream = promise.get(5, TimeUnit.SECONDS);
//
// // Wait for the server to be in ASYNC_WAIT.
@ -288,11 +288,11 @@ public class AsyncServletTest extends AbstractTest
// prepareClient();
// client.start();
//
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
// MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// CountDownLatch clientLatch = new CountDownLatch(1);
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -122,7 +122,7 @@ public class BlockedWritesWithSmallThreadPoolTest
client.start();
FuturePromise<Session> promise = new FuturePromise<>();
client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener.Adapter(), promise);
client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener() {}, promise);
Session session = promise.get(5, SECONDS);
CountDownLatch clientBlockLatch = new CountDownLatch(1);
@ -130,23 +130,25 @@ public class BlockedWritesWithSmallThreadPoolTest
// Send a request to TCP congest the server.
HttpURI uri = HttpURI.build("http://localhost:" + connector.getLocalPort() + "/congest");
MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY);
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
try
{
// Block here to stop reading from the network
// to cause the server to TCP congest.
clientBlockLatch.await(5, SECONDS);
callback.succeeded();
if (frame.isEndStream())
data.release();
stream.demand();
if (data.frame().isEndStream())
clientDataLatch.countDown();
}
catch (InterruptedException x)
{
callback.failed(x);
data.release();
}
}
});
@ -183,23 +185,26 @@ public class BlockedWritesWithSmallThreadPoolTest
{
int contentLength = 16 * 1024 * 1024;
CountDownLatch serverBlockLatch = new CountDownLatch(1);
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter()
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
try
{
// Block here to stop reading from the network
// to cause the client to TCP congest.
serverBlockLatch.await(5, SECONDS);
callback.succeeded();
if (frame.isEndStream())
data.release();
stream.demand();
if (data.frame().isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
@ -207,7 +212,7 @@ public class BlockedWritesWithSmallThreadPoolTest
}
catch (InterruptedException x)
{
callback.failed(x);
data.release();
}
}
};
@ -226,7 +231,7 @@ public class BlockedWritesWithSmallThreadPoolTest
client.start();
FuturePromise<Session> promise = new FuturePromise<>();
client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener.Adapter(), promise);
client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener() {}, promise);
Session session = promise.get(5, SECONDS);
// Send a request to TCP congest the client.
@ -234,7 +239,7 @@ public class BlockedWritesWithSmallThreadPoolTest
MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -53,7 +53,7 @@ public class CloseTest extends AbstractServerTest
{
final CountDownLatch closeLatch = new CountDownLatch(1);
final AtomicReference<Session> sessionRef = new AtomicReference<>();
startServer(new ServerSessionListener.Adapter()
startServer(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -123,7 +123,7 @@ public class CloseTest extends AbstractServerTest
public void testClientSendsGoAwayButDoesNotCloseConnectionServerCloses() throws Exception
{
final AtomicReference<Session> sessionRef = new AtomicReference<>();
startServer(new ServerSessionListener.Adapter()
startServer(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -187,7 +187,7 @@ public class CloseTest extends AbstractServerTest
{
final long idleTimeout = 1000;
final AtomicReference<Session> sessionRef = new AtomicReference<>();
startServer(new ServerSessionListener.Adapter()
startServer(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)

View File

@ -45,7 +45,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest
int iterations = 1024;
int total = threads * runs * iterations;
CountDownLatch serverLatch = new CountDownLatch(total);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -58,7 +58,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest
}
}, h2 -> h2.setMaxConcurrentStreams(total));
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
CyclicBarrier barrier = new CyclicBarrier(threads);
CountDownLatch clientLatch = new CountDownLatch(total);
@ -81,7 +81,7 @@ public class ConcurrentStreamCreationTest extends AbstractTest
{
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, true);
session.newStream(requestFrame, promise, new Stream.Listener.Adapter()
session.newStream(requestFrame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -39,12 +39,12 @@ public class ConnectTimeoutTest extends AbstractTest
int connectTimeout = 1000;
assumeConnectTimeout(host, port, connectTimeout);
start(new ServerSessionListener.Adapter());
start(new ServerSessionListener() {});
http2Client.setConnectTimeout(connectTimeout);
InetSocketAddress address = new InetSocketAddress(host, port);
final CountDownLatch latch = new CountDownLatch(1);
http2Client.connect(address, new Session.Listener.Adapter(), new Promise.Adapter<>()
http2Client.connect(address, new Session.Listener() {}, new Promise.Adapter<>()
{
@Override
public void failed(Throwable x)

View File

@ -22,6 +22,7 @@ import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
@ -44,7 +45,7 @@ public class ConnectTunnelTest extends AbstractTest
@Test
public void testCONNECT() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -56,18 +57,21 @@ public class ConnectTunnelTest extends AbstractTest
assertNull(uri.getScheme());
assertNull(uri.getPath());
assertNotNull(uri.getAuthority());
return new Stream.Listener.Adapter()
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, false), Callback.from(stream::demand));
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
stream.data(frame, callback);
Stream.Data data = stream.readData();
stream.data(data.frame(), Callback.from(data::release));
}
};
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
CountDownLatch latch = new CountDownLatch(1);
byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8);
@ -76,12 +80,14 @@ public class ConnectTunnelTest extends AbstractTest
String authority = host + ":" + port;
MetaData.Request request = new MetaData.Request(HttpMethod.CONNECT.asString(), null, new HostPortHttpField(authority), null, HttpVersion.HTTP_2, HttpFields.EMPTY, -1);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -95,7 +101,7 @@ public class ConnectTunnelTest extends AbstractTest
@Test
public void testCONNECTWithProtocol() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -108,18 +114,21 @@ public class ConnectTunnelTest extends AbstractTest
assertNotNull(uri.getPath());
assertNotNull(uri.getAuthority());
assertNotNull(request.getProtocol());
return new Stream.Listener.Adapter()
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, false), Callback.from(stream::demand));
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
stream.data(frame, callback);
Stream.Data data = stream.readData();
stream.data(data.frame(), Callback.from(data::release));
}
};
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
CountDownLatch latch = new CountDownLatch(1);
byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8);
@ -128,12 +137,14 @@ public class ConnectTunnelTest extends AbstractTest
String authority = host + ":" + port;
MetaData.Request request = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(authority), "/", HttpFields.EMPTY, "websocket");
FuturePromise<Stream> streamPromise = new FuturePromise<>();
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
latch.countDown();
}
});

View File

@ -14,10 +14,18 @@
package org.eclipse.jetty.http2.tests;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@ -27,6 +35,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class ContentLengthTest extends AbstractTest
@ -76,6 +85,37 @@ public class ContentLengthTest extends AbstractTest
assertEquals(data.length, contentLength);
}
@ParameterizedTest
@ValueSource(strings = {"GET", "HEAD", "POST", "PUT"})
public void testClientContentLengthMismatch(String method) throws Exception
{
byte[] data = new byte[512];
start(new Handler.Processor()
{
@Override
public void process(Request request, Response response, Callback callback)
{
Content.Source.consumeAll(request, callback);
}
});
Session clientSession = newClientSession(new Session.Listener() {});
CountDownLatch resetLatch = new CountDownLatch(1);
// Set a wrong Content-Length header.
HttpFields requestHeaders = HttpFields.build().put(HttpHeader.CONTENT_LENGTH, String.valueOf(data.length + 1));
clientSession.newStream(new HeadersFrame(newRequest(method, requestHeaders), null, false), new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
}).thenAccept(stream -> stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(data), true)));
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
}
// TODO
@ParameterizedTest
@ValueSource(strings = {"GET", "HEAD", "POST", "PUT"})

View File

@ -14,10 +14,13 @@
package org.eclipse.jetty.http2.tests;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpFields;
@ -25,7 +28,6 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -40,12 +42,13 @@ import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
import org.junit.jupiter.api.Test;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class DataDemandTest extends AbstractTest
{
@ -54,35 +57,40 @@ public class DataDemandTest extends AbstractTest
{
int length = FlowControlStrategy.DEFAULT_WINDOW_SIZE - 1;
AtomicReference<Stream> serverStreamRef = new AtomicReference<>();
Queue<DataFrame> serverQueue = new ConcurrentLinkedQueue<>();
start(new ServerSessionListener.Adapter()
Deque<Stream.Data> serverQueue = new ConcurrentLinkedDeque<>();
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
serverStreamRef.set(stream);
return new Stream.Listener.Adapter()
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, false), Callback.from(stream::demand));
return new Stream.Listener()
{
@Override
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Don't demand and don't complete callbacks.
serverQueue.offer(frame);
Stream.Data data = stream.readData();
// Don't demand and don't release.
serverQueue.offer(data);
}
};
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request post = newRequest("POST", HttpFields.EMPTY);
FuturePromise<Stream> promise = new FuturePromise<>();
Queue<DataFrame> clientQueue = new ConcurrentLinkedQueue<>();
client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener.Adapter()
Queue<Stream.Data> clientQueue = new ConcurrentLinkedQueue<>();
client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener()
{
@Override
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
clientQueue.offer(frame);
Stream.Data data = stream.readData();
// Don't demand and don't release.
clientQueue.offer(data);
}
});
Stream clientStream = promise.get(5, TimeUnit.SECONDS);
@ -90,7 +98,7 @@ public class DataDemandTest extends AbstractTest
// so that it will be split on the server in multiple frames.
clientStream.data(new DataFrame(clientStream.getId(), ByteBuffer.allocate(length), true), Callback.NOOP);
// The server should receive only 1 DATA frame because it does explicit demand.
// The server should receive only 1 DATA frame because it does 1 explicit demand.
// Wait a bit more to be sure it only receives 1 DATA frame.
Thread.sleep(1000);
assertEquals(1, serverQueue.size());
@ -98,25 +106,20 @@ public class DataDemandTest extends AbstractTest
Stream serverStream = serverStreamRef.get();
assertNotNull(serverStream);
// Demand more DATA frames.
int count = 2;
serverStream.demand(count);
Thread.sleep(1000);
// The server should have received `count` more DATA frames.
assertEquals(1 + count, serverQueue.size());
// Demand 1 more DATA frames.
serverStream.demand();
// The server should have received 1 more DATA frame.
await().atMost(1, TimeUnit.SECONDS).until(serverQueue::size, is(2));
// Demand all the rest.
serverStream.demand(Long.MAX_VALUE);
int loops = 0;
AtomicInteger count = new AtomicInteger(serverQueue.size());
while (true)
{
if (++loops > 100)
fail();
Thread.sleep(100);
serverStream.demand();
await().atMost(1, TimeUnit.SECONDS).until(() -> serverQueue.size() == count.get() + 1);
count.incrementAndGet();
long sum = serverQueue.stream()
.mapToLong(frame -> frame.getData().remaining())
.mapToLong(data -> data.frame().getData().remaining())
.sum();
if (sum == length)
break;
@ -124,50 +127,52 @@ public class DataDemandTest extends AbstractTest
// Even if demanded, the flow control window should not have
// decreased because the callbacks have not been completed.
int recvWindow = ((ISession)serverStream.getSession()).updateRecvWindow(0);
int recvWindow = ((HTTP2Session)serverStream.getSession()).updateRecvWindow(0);
assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE - length, recvWindow);
// Release them all.
serverQueue.forEach(Stream.Data::release);
// Send a large DATA frame to the client.
serverStream.data(new DataFrame(serverStream.getId(), ByteBuffer.allocate(length), true), Callback.NOOP);
// The client should receive only 1 DATA frame because it does explicit demand.
// Wait a bit more to be sure it only receives 1 DATA frame.
Thread.sleep(1000);
assertEquals(1, clientQueue.size());
// Demand more DATA frames.
clientStream.demand(count);
// Demand 1 more DATA frames.
clientStream.demand();
Thread.sleep(1000);
// The client should have received `count` more DATA frames.
assertEquals(1 + count, clientQueue.size());
// The client should have received 1 more DATA frame.
assertEquals(2, clientQueue.size());
// Demand all the rest.
clientStream.demand(Long.MAX_VALUE);
loops = 0;
count.set(clientQueue.size());
while (true)
{
if (++loops > 100)
fail();
Thread.sleep(100);
clientStream.demand();
await().atMost(1, TimeUnit.SECONDS).until(() -> clientQueue.size() == count.get() + 1);
count.incrementAndGet();
long sum = clientQueue.stream()
.mapToLong(frame -> frame.getData().remaining())
.mapToLong(data -> data.frame().getData().remaining())
.sum();
if (sum == length)
break;
}
// Release them all.
clientQueue.forEach(Stream.Data::release);
// Both the client and server streams should be gone now.
assertNull(clientStream.getSession().getStream(clientStream.getId()));
assertNull(serverStream.getSession().getStream(serverStream.getId()));
}
@Test
public void testOnBeforeData() throws Exception
public void testNoDemandNoOnDataAvailable() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -183,13 +188,12 @@ public class DataDemandTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request post = newRequest("GET", HttpFields.EMPTY);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch responseLatch = new CountDownLatch(1);
CountDownLatch beforeDataLatch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
client.newStream(new HeadersFrame(post, null, true), promise, new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(post, null, true), promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -197,37 +201,33 @@ public class DataDemandTest extends AbstractTest
MetaData.Response response = (MetaData.Response)frame.getMetaData();
assertEquals(HttpStatus.OK_200, response.getStatus());
responseLatch.countDown();
}
@Override
public void onBeforeData(Stream stream)
{
beforeDataLatch.countDown();
// Don't demand.
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
assertNotNull(data);
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
Stream clientStream = promise.get(5, TimeUnit.SECONDS);
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
assertTrue(beforeDataLatch.await(5, TimeUnit.SECONDS));
// Should not receive DATA frames until demanded.
assertFalse(latch.await(1, TimeUnit.SECONDS));
// Now demand the first DATA frame.
clientStream.demand(1);
clientStream.demand();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testDemandFromOnHeaders() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -243,28 +243,19 @@ public class DataDemandTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request post = newRequest("GET", HttpFields.EMPTY);
CountDownLatch latch = new CountDownLatch(1);
client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
public void onDataAvailable(Stream stream)
{
stream.demand(1);
}
@Override
public void onBeforeData(Stream stream)
{
// Do not demand from here, we have already demanded in onHeaders().
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
assertNotNull(data);
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -272,9 +263,9 @@ public class DataDemandTest extends AbstractTest
}
@Test
public void testOnBeforeDataDoesNotReenter() throws Exception
public void testDemandFromOnHeadersDoesNotInvokeOnDataAvailable() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -290,27 +281,29 @@ public class DataDemandTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request post = newRequest("GET", HttpFields.EMPTY);
CountDownLatch latch = new CountDownLatch(1);
client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(post, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
private boolean inBeforeData;
private boolean inHeaders;
@Override
public void onBeforeData(Stream stream)
public void onHeaders(Stream stream, HeadersFrame frame)
{
inBeforeData = true;
stream.demand(1);
inBeforeData = false;
inHeaders = true;
stream.demand();
inHeaders = false;
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
assertFalse(inBeforeData);
callback.succeeded();
if (frame.isEndStream())
assertFalse(inHeaders);
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -320,19 +313,21 @@ public class DataDemandTest extends AbstractTest
@Test
public void testSynchronousDemandDoesNotStackOverflow() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
stream.demand(1);
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
@ -342,11 +337,11 @@ public class DataDemandTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request post = newRequest("POST", HttpFields.EMPTY);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(post, null, false), promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -31,8 +31,6 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -56,11 +54,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class FlowControlStalledTest
{
protected ServerConnector connector;
protected HTTP2Client client;
protected Server server;
private ServerConnector connector;
private HTTP2Client client;
private Server server;
protected void start(FlowControlStrategy.Factory flowControlFactory, ServerSessionListener listener) throws Exception
private void start(FlowControlStrategy.Factory flowControlFactory, ServerSessionListener listener) throws Exception
{
QueuedThreadPool serverExecutor = new QueuedThreadPool();
serverExecutor.setName("server");
@ -83,7 +81,7 @@ public class FlowControlStalledTest
client.start();
}
protected Session newClient(Session.Listener listener) throws Exception
private Session newClient(Session.Listener listener) throws Exception
{
String host = "localhost";
int port = connector.getLocalPort();
@ -93,7 +91,7 @@ public class FlowControlStalledTest
return promise.get(5, TimeUnit.SECONDS);
}
protected MetaData.Request newRequest(String method, String target, HttpFields fields)
private MetaData.Request newRequest(String method, String target, HttpFields fields)
{
String host = "localhost";
int port = connector.getLocalPort();
@ -118,19 +116,19 @@ public class FlowControlStalledTest
start(() -> new BufferingFlowControlStrategy(0.5f)
{
@Override
public void onStreamStalled(IStream stream)
public void onStreamStalled(Stream stream)
{
super.onStreamStalled(stream);
stallLatch.get().countDown();
}
@Override
protected void onStreamUnstalled(IStream stream)
protected void onStreamUnstalled(Stream stream)
{
super.onStreamUnstalled(stream);
unstallLatch.countDown();
}
}, new ServerSessionListener.Adapter()
}, new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -156,24 +154,28 @@ public class FlowControlStalledTest
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
stream.demand();
return null;
}
});
// Use a large session window so that only the stream gets stalled.
client.setInitialSessionRecvWindow(5 * FlowControlStrategy.DEFAULT_WINDOW_SIZE);
Session client = newClient(new Session.Listener.Adapter());
Session client = newClient(new Session.Listener() {});
CountDownLatch latch = new CountDownLatch(1);
Queue<Callback> callbacks = new ArrayDeque<>();
Queue<Stream.Data> dataQueue = new ArrayDeque<>();
MetaData.Request request = newRequest("GET", "/stall", HttpFields.EMPTY);
client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callbacks.offer(callback);
if (frame.isEndStream())
Stream.Data data = stream.readData();
// Do not release.
dataQueue.offer(data);
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -185,16 +187,16 @@ public class FlowControlStalledTest
stallLatch.set(new CountDownLatch(1));
request = newRequest("GET", "/", HttpFields.EMPTY);
client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter());
client.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), null);
assertFalse(stallLatch.get().await(1, TimeUnit.SECONDS));
// Consume all data.
while (!latch.await(10, TimeUnit.MILLISECONDS))
{
Callback callback = callbacks.poll();
if (callback != null)
callback.succeeded();
Stream.Data data = dataQueue.poll();
if (data != null)
data.release();
}
// Make sure the unstall callback is invoked.
@ -209,19 +211,19 @@ public class FlowControlStalledTest
start(() -> new BufferingFlowControlStrategy(0.5f)
{
@Override
public void onSessionStalled(ISession session)
public void onSessionStalled(Session session)
{
super.onSessionStalled(session);
stallLatch.get().countDown();
}
@Override
protected void onSessionUnstalled(ISession session)
protected void onSessionUnstalled(Session session)
{
super.onSessionUnstalled(session);
unstallLatch.countDown();
}
}, new ServerSessionListener.Adapter()
}, new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -247,13 +249,14 @@ public class FlowControlStalledTest
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
stream.demand();
return null;
}
});
// Use a large stream window so that only the session gets stalled.
client.setInitialStreamRecvWindow(5 * FlowControlStrategy.DEFAULT_WINDOW_SIZE);
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -265,15 +268,18 @@ public class FlowControlStalledTest
});
CountDownLatch latch = new CountDownLatch(1);
Queue<Callback> callbacks = new ArrayDeque<>();
Queue<Stream.Data> dataQueue = new ArrayDeque<>();
MetaData.Request request = newRequest("GET", "/stall", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callbacks.offer(callback);
if (frame.isEndStream())
Stream.Data data = stream.readData();
// Do not release.
dataQueue.offer(data);
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -285,16 +291,16 @@ public class FlowControlStalledTest
stallLatch.set(new CountDownLatch(1));
request = newRequest("GET", "/", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter());
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), null);
assertFalse(stallLatch.get().await(1, TimeUnit.SECONDS));
// Consume all data.
// Release all data.
while (!latch.await(10, TimeUnit.MILLISECONDS))
{
Callback callback = callbacks.poll();
if (callback != null)
callback.succeeded();
Stream.Data data = dataQueue.poll();
if (data != null)
data.release();
}
// Make sure the unstall callback is invoked.

View File

@ -37,8 +37,6 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -137,7 +135,7 @@ public abstract class FlowControlStrategyTest
CountDownLatch stream1Latch = new CountDownLatch(1);
CountDownLatch stream2Latch = new CountDownLatch(1);
CountDownLatch settingsLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -182,7 +180,7 @@ public abstract class FlowControlStrategyTest
}
});
HTTP2Session clientSession = (HTTP2Session)newClient(new Session.Listener.Adapter());
HTTP2Session clientSession = (HTTP2Session)newClient(new Session.Listener() {});
assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientSession.getSendWindow());
assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientSession.getRecvWindow());
@ -190,7 +188,7 @@ public abstract class FlowControlStrategyTest
MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY);
FuturePromise<Stream> promise1 = new FuturePromise<>();
clientSession.newStream(new HeadersFrame(request1, null, true), promise1, new Stream.Listener.Adapter());
clientSession.newStream(new HeadersFrame(request1, null, true), promise1, null);
HTTP2Stream clientStream1 = (HTTP2Stream)promise1.get(5, TimeUnit.SECONDS);
assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientStream1.getSendWindow());
@ -214,7 +212,7 @@ public abstract class FlowControlStrategyTest
// Now create a new stream, it must pick up the new value.
MetaData.Request request2 = newRequest("POST", HttpFields.EMPTY);
FuturePromise<Stream> promise2 = new FuturePromise<>();
clientSession.newStream(new HeadersFrame(request2, null, true), promise2, new Stream.Listener.Adapter());
clientSession.newStream(new HeadersFrame(request2, null, true), promise2, null);
HTTP2Stream clientStream2 = (HTTP2Stream)promise2.get(5, TimeUnit.SECONDS);
assertEquals(FlowControlStrategy.DEFAULT_WINDOW_SIZE, clientStream2.getSendWindow());
@ -233,8 +231,8 @@ public abstract class FlowControlStrategyTest
// We get 3 data frames: the first of 1024 and 2 of 512 each
// after the flow control window has been reduced.
CountDownLatch dataLatch = new CountDownLatch(3);
AtomicReference<Callback> callbackRef = new AtomicReference<>();
start(new ServerSessionListener.Adapter()
AtomicReference<Stream.Data> dataRef = new AtomicReference<>();
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
@ -242,29 +240,31 @@ public abstract class FlowControlStrategyTest
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true);
stream.headers(responseFrame, Callback.NOOP);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
private final AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
dataLatch.countDown();
int dataFrameCount = dataFrames.incrementAndGet();
if (dataFrameCount == 1)
{
callbackRef.set(callback);
dataRef.set(data);
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, size);
stream.getSession().settings(new SettingsFrame(settings, false), Callback.NOOP);
// Do not succeed the callback here.
// Do not release the data here.
}
else if (dataFrameCount > 1)
{
// Consume the data.
callback.succeeded();
// Release the data.
data.release();
}
stream.demand();
}
};
}
@ -272,7 +272,7 @@ public abstract class FlowControlStrategyTest
// Two SETTINGS frames, the initial one and the one we send from the server.
CountDownLatch settingsLatch = new CountDownLatch(2);
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
@ -283,7 +283,7 @@ public abstract class FlowControlStrategyTest
MetaData.Request request = newRequest("POST", HttpFields.EMPTY);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(new HeadersFrame(request, null, false), promise, new Stream.Listener.Adapter());
session.newStream(new HeadersFrame(request, null, false), promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
// Send first chunk that exceeds the window.
@ -299,8 +299,8 @@ public abstract class FlowControlStrategyTest
assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
// Consume the data arrived to server, this will resume flow control on the client.
callbackRef.get().succeeded();
// Release the data arrived to server, this will resume flow control on the client.
dataRef.get().release();
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
@ -311,7 +311,7 @@ public abstract class FlowControlStrategyTest
int windowSize = 1536;
int length = 5 * windowSize;
CountDownLatch settingsLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
@ -335,7 +335,7 @@ public abstract class FlowControlStrategyTest
}
});
Session session = newClient(new Session.Listener.Adapter());
Session session = newClient(new Session.Listener() {});
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize);
@ -346,30 +346,33 @@ public abstract class FlowControlStrategyTest
assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
CountDownLatch dataLatch = new CountDownLatch(1);
Exchanger<Callback> exchanger = new Exchanger<>();
Exchanger<Stream.Data> exchanger = new Exchanger<>();
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener()
{
private final AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
try
{
int dataFrames = this.dataFrames.incrementAndGet();
if (dataFrames == 1 || dataFrames == 2)
{
// Do not consume the data frame.
// Do not release the Data.
// We should then be flow-control stalled.
exchanger.exchange(callback);
exchanger.exchange(data);
stream.demand();
}
else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5)
{
// Consume totally.
callback.succeeded();
if (frame.isEndStream())
data.release();
stream.demand();
if (data.frame().isEndStream())
dataLatch.countDown();
}
else
@ -379,22 +382,22 @@ public abstract class FlowControlStrategyTest
}
catch (InterruptedException x)
{
callback.failed(x);
data.release();
}
}
});
Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
Stream.Data data = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the first chunk.
callback.succeeded();
// Release the first chunk.
data.release();
callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
data = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the second chunk.
callback.succeeded();
// Release the second chunk.
data.release();
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
@ -403,10 +406,10 @@ public abstract class FlowControlStrategyTest
public void testClientFlowControlOneBigWrite() throws Exception
{
int windowSize = 1536;
Exchanger<Callback> exchanger = new Exchanger<>();
Exchanger<Stream.Data> exchanger = new Exchanger<>();
CountDownLatch settingsLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -422,13 +425,15 @@ public abstract class FlowControlStrategyTest
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(responseFrame, Callback.NOOP);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
private final AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
try
{
int dataFrames = this.dataFrames.incrementAndGet();
@ -436,13 +441,15 @@ public abstract class FlowControlStrategyTest
{
// Do not consume the data frame.
// We should then be flow-control stalled.
exchanger.exchange(callback);
exchanger.exchange(data);
stream.demand();
}
else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5)
{
// Consume totally.
callback.succeeded();
if (frame.isEndStream())
data.release();
stream.demand();
if (data.frame().isEndStream())
dataLatch.countDown();
}
else
@ -452,14 +459,14 @@ public abstract class FlowControlStrategyTest
}
catch (InterruptedException x)
{
callback.failed(x);
data.release();
}
}
};
}
});
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
@ -480,22 +487,22 @@ public abstract class FlowControlStrategyTest
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true);
stream.data(dataFrame, Callback.NOOP);
Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
Stream.Data data = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the first chunk.
callback.succeeded();
data.release();
callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
data = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the second chunk.
callback.succeeded();
data.release();
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
private void checkThatWeAreFlowControlStalled(Exchanger<Callback> exchanger)
private void checkThatWeAreFlowControlStalled(Exchanger<Stream.Data> exchanger)
{
assertThrows(TimeoutException.class,
() -> exchanger.exchange(null, 1, TimeUnit.SECONDS));
@ -505,51 +512,56 @@ public abstract class FlowControlStrategyTest
public void testSessionStalledStallsNewStreams() throws Exception
{
int windowSize = 1024;
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
MetaData.Request request = (MetaData.Request)requestFrame.getMetaData();
if ("POST".equalsIgnoreCase(request.getMethod()))
if (HttpMethod.POST.is(request.getMethod()))
{
// Send data to consume most of the session window.
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE - windowSize);
DataFrame dataFrame = new DataFrame(stream.getId(), data, true);
stream.data(dataFrame, Callback.NOOP);
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, false))
.thenCompose(s ->
{
// Send data to consume most of the session window.
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE - windowSize);
DataFrame dataFrame = new DataFrame(s.getId(), data, true);
return s.data(dataFrame);
});
return null;
}
else
{
// For every stream, send down half the window size of data.
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(responseFrame, completable);
completable.thenRun(() ->
{
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(windowSize / 2), true);
stream.data(dataFrame, Callback.NOOP);
});
stream.headers(new HeadersFrame(stream.getId(), metaData, null, false))
.thenCompose(s ->
{
DataFrame dataFrame = new DataFrame(s.getId(), ByteBuffer.allocate(windowSize / 2), true);
return s.data(dataFrame);
});
return null;
}
}
});
Session session = newClient(new Session.Listener.Adapter());
Session session = newClient(new Session.Listener() {});
// First request is just to consume most of the session window.
List<Callback> callbacks1 = new ArrayList<>();
List<Stream.Data> dataList1 = new ArrayList<>();
CountDownLatch prepareLatch = new CountDownLatch(1);
MetaData.Request request1 = newRequest("POST", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
// Do not consume the data to reduce the session window.
callbacks1.add(callback);
if (frame.isEndStream())
dataList1.add(data);
stream.demand();
if (data.frame().isEndStream())
prepareLatch.countDown();
}
});
@ -557,37 +569,43 @@ public abstract class FlowControlStrategyTest
// Second request will consume half of the remaining the session window.
MetaData.Request request2 = newRequest("GET", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Do not consume it to stall flow control.
stream.readData();
stream.demand();
// Do not release it to stall flow control.
}
});
// Third request will consume the whole session window, which is now stalled.
// A fourth request will not be able to receive data.
MetaData.Request request3 = newRequest("GET", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request3, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request3, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Do not consume it to stall flow control.
stream.readData();
stream.demand();
// Do not release it to stall flow control.
}
});
// Fourth request is now stalled.
CountDownLatch latch = new CountDownLatch(1);
MetaData.Request request4 = newRequest("GET", HttpFields.EMPTY);
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request4, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -597,10 +615,7 @@ public abstract class FlowControlStrategyTest
// Consume the data of the first response.
// This will open up the session window, allowing the fourth stream to send data.
for (Callback callback : callbacks1)
{
callback.succeeded();
}
dataList1.forEach(Stream.Data::release);
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -611,7 +626,7 @@ public abstract class FlowControlStrategyTest
byte[] data = new byte[1024 * 1024];
new Random().nextBytes(data);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
@ -629,22 +644,25 @@ public abstract class FlowControlStrategyTest
}
});
Session session = newClient(new Session.Listener.Adapter());
Session session = newClient(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
byte[] bytes = new byte[data.length];
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener()
{
private int received;
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
DataFrame frame = data.frame();
int remaining = frame.remaining();
frame.getData().get(bytes, received, remaining);
this.received += remaining;
callback.succeeded();
data.release();
stream.demand();
if (frame.isEndStream())
latch.countDown();
}
@ -657,7 +675,7 @@ public abstract class FlowControlStrategyTest
@Test
public void testClientSendingInitialSmallWindow() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -666,19 +684,21 @@ public abstract class FlowControlStrategyTest
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(responseFrame, completable);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
completable.thenRun(() -> stream.data(frame, callback));
Stream.Data data = stream.readData();
completable.thenRun(() -> stream.data(data.frame(), Callback.from(Callback.from(data::release), stream::demand)));
}
};
}
});
int initialWindow = 16;
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -698,14 +718,16 @@ public abstract class FlowControlStrategyTest
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
Promise.Completable<Stream> completable = new Promise.Completable<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, completable, new Stream.Listener.Adapter()
session.newStream(requestFrame, completable, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
responseContent.put(frame.getData());
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
responseContent.put(data.frame().getData());
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -727,31 +749,34 @@ public abstract class FlowControlStrategyTest
{
// On server, we don't consume the data.
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Do not succeed the callback.
// Read but do not release the Data.
stream.readData();
stream.demand();
}
};
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -761,9 +786,10 @@ public abstract class FlowControlStrategyTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
@ -771,7 +797,7 @@ public abstract class FlowControlStrategyTest
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
CompletableFuture<Stream> completable = new CompletableFuture<>();
session.newStream(requestFrame, Promise.from(completable), new Stream.Listener.Adapter());
session.newStream(requestFrame, Promise.from(completable), null);
Stream stream = completable.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
CountDownLatch dataLatch = new CountDownLatch(1);
@ -819,39 +845,42 @@ public abstract class FlowControlStrategyTest
{
// On server, we don't consume the data.
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
// Enlarge the session window.
((ISession)session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
return super.onPreface(session);
((HTTP2Session)session).updateRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
return null;
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Do not succeed the callback.
// Read but do not release the Data.
stream.readData();
stream.demand();
}
};
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
Session session = newClient(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -861,9 +890,10 @@ public abstract class FlowControlStrategyTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
@ -871,7 +901,7 @@ public abstract class FlowControlStrategyTest
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
session.newStream(requestFrame, streamPromise, null);
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
CountDownLatch dataLatch = new CountDownLatch(1);
@ -914,43 +944,44 @@ public abstract class FlowControlStrategyTest
public void testFlowControlWhenServerResetsStream() throws Exception
{
// On server, don't consume the data and immediately reset.
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
MetaData.Request request = (MetaData.Request)frame.getMetaData();
if (HttpMethod.GET.is(request.getMethod()))
return new Stream.Listener.Adapter();
return new Stream.Listener.Adapter()
return null;
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Fail the callback to enlarge the session window.
Stream.Data data = stream.readData();
// Release the data to enlarge the session window.
// More data frames will be discarded because the
// stream is reset, and automatically consumed to
// keep the session window large for other streams.
callback.failed(new Throwable());
data.release();
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
}
};
}
});
Session session = newClient(new Session.Listener.Adapter());
Session session = newClient(new Session.Listener() {});
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch resetLatch = new CountDownLatch(1);
session.newStream(frame, streamPromise, new Stream.Listener.Adapter()
session.newStream(frame, streamPromise, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
});
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
@ -980,22 +1011,25 @@ public abstract class FlowControlStrategyTest
@Test
public void testNoWindowUpdateForRemotelyClosedStream() throws Exception
{
List<Callback> callbacks = new ArrayList<>();
start(new ServerSessionListener.Adapter()
List<Stream.Data> dataList = new ArrayList<>();
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callbacks.add(callback);
if (frame.isEndStream())
Stream.Data data = stream.readData();
dataList.add(data);
stream.demand();
if (data.frame().isEndStream())
{
// Succeed the callbacks when the stream is already remotely closed.
callbacks.forEach(Callback::succeeded);
// Release the Data when the stream is already remotely closed.
dataList.forEach(Stream.Data::release);
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
@ -1009,7 +1043,7 @@ public abstract class FlowControlStrategyTest
client.setFlowControlStrategyFactory(() -> new BufferingFlowControlStrategy(0.5F)
{
@Override
public void onWindowUpdate(ISession session, IStream stream, WindowUpdateFrame frame)
public void onWindowUpdate(Session session, Stream stream, WindowUpdateFrame frame)
{
if (frame.getStreamId() == 0)
sessionWindowUpdates.add(frame);
@ -1019,12 +1053,12 @@ public abstract class FlowControlStrategyTest
}
});
Session session = newClient(new Session.Listener.Adapter());
Session session = newClient(new Session.Listener() {});
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, streamPromise, new Stream.Listener.Adapter()
session.newStream(frame, streamPromise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -24,13 +24,13 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
@ -84,22 +84,22 @@ public class FlowControlWindowsTest
server.stop();
}
protected ISession newClient(Session.Listener listener) throws Exception
protected HTTP2Session newClient(Session.Listener listener) throws Exception
{
String host = "localhost";
int port = connector.getLocalPort();
InetSocketAddress address = new InetSocketAddress(host, port);
FuturePromise<Session> promise = new FuturePromise<>();
client.connect(address, listener, promise);
return (ISession)promise.get(5, TimeUnit.SECONDS);
return (HTTP2Session)promise.get(5, TimeUnit.SECONDS);
}
@Test
public void testClientFlowControlWindows() throws Exception
{
start(new ServerSessionListener.Adapter());
start(new ServerSessionListener() {});
ISession clientSession = newClient(new Session.Listener.Adapter());
HTTP2Session clientSession = newClient(new Session.Listener() {});
// Wait while client and server exchange SETTINGS and WINDOW_UPDATE frames.
Thread.sleep(1000);
@ -112,8 +112,8 @@ public class FlowControlWindowsTest
MetaData.Request request = new MetaData.Request(HttpMethod.GET.asString(), HttpScheme.HTTP.asString(), hostPort, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1);
HeadersFrame frame = new HeadersFrame(request, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
clientSession.newStream(frame, promise, new Stream.Listener.Adapter());
IStream clientStream = (IStream)promise.get(5, TimeUnit.SECONDS);
clientSession.newStream(frame, promise, null);
HTTP2Stream clientStream = (HTTP2Stream)promise.get(5, TimeUnit.SECONDS);
int streamSendWindow = clientStream.updateSendWindow(0);
assertEquals(serverStreamRecvWindow, streamSendWindow);
@ -124,32 +124,32 @@ public class FlowControlWindowsTest
@Test
public void testServerFlowControlWindows() throws Exception
{
AtomicReference<ISession> sessionRef = new AtomicReference<>();
AtomicReference<HTTP2Session> sessionRef = new AtomicReference<>();
CountDownLatch sessionLatch = new CountDownLatch(1);
AtomicReference<IStream> streamRef = new AtomicReference<>();
AtomicReference<HTTP2Stream> streamRef = new AtomicReference<>();
CountDownLatch streamLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
{
sessionRef.set((ISession)session);
sessionRef.set((HTTP2Session)session);
sessionLatch.countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
streamRef.set((IStream)stream);
streamRef.set((HTTP2Stream)stream);
streamLatch.countDown();
return null;
}
});
ISession clientSession = newClient(new Session.Listener.Adapter());
HTTP2Session clientSession = newClient(new Session.Listener() {});
assertTrue(sessionLatch.await(5, TimeUnit.SECONDS));
ISession serverSession = sessionRef.get();
HTTP2Session serverSession = sessionRef.get();
// Wait while client and server exchange SETTINGS and WINDOW_UPDATE frames.
Thread.sleep(1000);
@ -161,10 +161,10 @@ public class FlowControlWindowsTest
HostPortHttpField hostPort = new HostPortHttpField("localhost:" + connector.getLocalPort());
MetaData.Request request = new MetaData.Request(HttpMethod.GET.asString(), HttpScheme.HTTP.asString(), hostPort, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1);
HeadersFrame frame = new HeadersFrame(request, null, true);
clientSession.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter());
clientSession.newStream(frame, new Promise.Adapter<>(), null);
assertTrue(streamLatch.await(5, TimeUnit.SECONDS));
IStream serverStream = streamRef.get();
HTTP2Stream serverStream = streamRef.get();
int streamSendWindow = serverStream.updateSendWindow(0);
assertEquals(clientStreamRecvWindow, streamSendWindow);

View File

@ -27,8 +27,6 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.CloseState;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.SimpleFlowControlStrategy;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
@ -40,6 +38,7 @@ import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
@ -55,7 +54,7 @@ public class GoAwayTest extends AbstractTest
{
CountDownLatch serverLatch = new CountDownLatch(1);
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -67,23 +66,25 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientLatch.countDown();
callback.succeeded();
}
});
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -108,7 +109,7 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverGoAwayLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -126,15 +127,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -143,15 +145,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
MetaData.Request request1 = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
CountDownLatch streamFailureLatch = new CountDownLatch(1);
clientSession.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request1, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -162,7 +165,7 @@ public class GoAwayTest extends AbstractTest
// The client sends the second request and should eventually fail it
// locally since it has a larger streamId, and the server discarded it.
MetaData.Request request2 = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
clientSession.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request2, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onFailure(Stream stream, int error, String reason, Throwable failure, Callback callback)
@ -190,7 +193,7 @@ public class GoAwayTest extends AbstractTest
CountDownLatch serverGoAwayLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -208,17 +211,18 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (!frame.isGraceful())
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -230,15 +234,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (!frame.isGraceful())
clientCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientLatch = new CountDownLatch(1);
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -272,7 +277,7 @@ public class GoAwayTest extends AbstractTest
CountDownLatch serverCloseLatch = new CountDownLatch(1);
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
AtomicReference<Stream> serverStreamRef = new AtomicReference<>();
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -294,17 +299,18 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (!frame.isGraceful())
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -316,15 +322,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (!frame.isGraceful())
clientCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientLatch = new CountDownLatch(1);
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -372,7 +379,7 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverGoAwayLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -383,14 +390,16 @@ public class GoAwayTest extends AbstractTest
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.demand();
AtomicInteger dataFrames = new AtomicInteger();
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
// Do not consume the data for this stream (i.e. don't succeed the callback).
// Only send the response when receiving the first DATA frame.
stream.readData();
// Do not release the Data for this stream.
// Only send the response after reading the first DATA frame.
if (dataFrames.incrementAndGet() == 1)
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
@ -407,9 +416,10 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
}, h2 ->
{
@ -422,7 +432,7 @@ public class GoAwayTest extends AbstractTest
CountDownLatch clientSettingsLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
@ -437,9 +447,10 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
@ -450,12 +461,12 @@ public class GoAwayTest extends AbstractTest
// This is necessary because the server session window is smaller than the
// default and the server cannot send a WINDOW_UPDATE with a negative value.
((ISession)clientSession).updateSendWindow(flowControlWindow - FlowControlStrategy.DEFAULT_WINDOW_SIZE);
((HTTP2Session)clientSession).updateSendWindow(flowControlWindow - FlowControlStrategy.DEFAULT_WINDOW_SIZE);
MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY);
HeadersFrame headersFrame1 = new HeadersFrame(request1, null, false);
DataFrame dataFrame1 = new DataFrame(ByteBuffer.allocate(flowControlWindow / 2), false);
((ISession)clientSession).newStream(new IStream.FrameList(headersFrame1, dataFrame1, null), new Promise.Adapter<>(), new Stream.Listener.Adapter()
((HTTP2Session)clientSession).newStream(new HTTP2Stream.FrameList(headersFrame1, dataFrame1, null), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream clientStream1, HeadersFrame frame)
@ -470,7 +481,7 @@ public class GoAwayTest extends AbstractTest
MetaData.Request request2 = newRequest("POST", HttpFields.EMPTY);
HeadersFrame headersFrame2 = new HeadersFrame(request2, null, false);
DataFrame dataFrame2 = new DataFrame(ByteBuffer.allocate(flowControlWindow / 2), true);
((ISession)clientStream1.getSession()).newStream(new IStream.FrameList(headersFrame2, dataFrame2, null), new Promise.Adapter<>()
((HTTP2Session)clientStream1.getSession()).newStream(new HTTP2Stream.FrameList(headersFrame2, dataFrame2, null), new Promise.Adapter<>()
{
@Override
public void succeeded(Stream clientStream2)
@ -480,7 +491,7 @@ public class GoAwayTest extends AbstractTest
// the server and be able to complete this stream.
clientStream1.data(new DataFrame(clientStream1.getId(), ByteBuffer.allocate(flowControlWindow / 2), true), Callback.NOOP);
}
}, new Adapter());
}, new Stream.Listener() {});
}
});
@ -500,7 +511,7 @@ public class GoAwayTest extends AbstractTest
CountDownLatch serverStreamLatch = new CountDownLatch(1);
CountDownLatch serverGoAwayLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -517,15 +528,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -534,15 +546,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientLatch = new CountDownLatch(1);
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -584,7 +597,7 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Stream> serverStreamRef = new AtomicReference<>();
CountDownLatch serverStreamLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -599,15 +612,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -624,15 +638,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientLatch = new CountDownLatch(1);
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -669,25 +684,24 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Stream> serverStreamRef = new AtomicReference<>();
CountDownLatch serverGoAwayLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
serverStreamRef.set(stream);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), callback);
}
else
{
callback.succeeded();
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}
};
@ -708,16 +722,17 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -729,14 +744,15 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
FuturePromise<Stream> promise = new FuturePromise<>();
clientSession.newStream(new HeadersFrame(request, null, false), promise, new Stream.Listener.Adapter());
clientSession.newStream(new HeadersFrame(request, null, false), promise, null);
Stream clientStream = promise.get(5, TimeUnit.SECONDS);
// Send a graceful GOAWAY from the client.
@ -763,17 +779,18 @@ public class GoAwayTest extends AbstractTest
{
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverSessionRef.set(session);
serverCloseLatch.countDown();
callback.succeeded();
}
});
Session clientSession = newClientSession(new Session.Listener.Adapter());
Session clientSession = newClientSession(new Session.Listener() {});
// TODO: get rid of sleep!
// Wait for the SETTINGS frames to be exchanged.
Thread.sleep(500);
@ -789,7 +806,7 @@ public class GoAwayTest extends AbstractTest
{
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -798,13 +815,14 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
newClientSession(new Session.Listener.Adapter()
newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -833,7 +851,7 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverIdleTimeoutLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -850,15 +868,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -868,9 +887,10 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
@ -892,7 +912,7 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -911,16 +931,17 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -932,20 +953,22 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientResetLatch = new CountDownLatch(1);
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
// Send request headers but not data.
clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
clientResetLatch.countDown();
callback.succeeded();
}
});
@ -968,7 +991,7 @@ public class GoAwayTest extends AbstractTest
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -992,15 +1015,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -1009,19 +1033,21 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
CountDownLatch streamResetLatch = new CountDownLatch(1);
clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
streamResetLatch.countDown();
callback.succeeded();
}
});
@ -1043,7 +1069,7 @@ public class GoAwayTest extends AbstractTest
{
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverCloseLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -1055,15 +1081,16 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
serverCloseLatch.countDown();
callback.succeeded();
}
});
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -1072,20 +1099,22 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
MetaData.Request request = newRequest(HttpMethod.GET.asString(), HttpFields.EMPTY);
CountDownLatch clientResetLatch = new CountDownLatch(1);
clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request, null, false), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
clientResetLatch.countDown();
callback.succeeded();
}
});

View File

@ -14,10 +14,17 @@
package org.eclipse.jetty.http2.tests;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* HTTP/2 server to run the 'h2spec' tool against.
@ -39,6 +46,18 @@ public class H2SpecServer
connector.setPort(port);
server.addConnector(connector);
// H2Spec requires the server to read the request
// content and respond with 200 and some content.
server.setHandler(new Handler.Processor()
{
@Override
public void process(Request request, Response response, Callback callback)
{
Content.Source.consumeAll(request, Callback.NOOP);
response.write(true, UTF_8.encode("hello"), callback);
}
});
server.start();
}
}

View File

@ -555,7 +555,7 @@ public class HTTP2ServerTest extends AbstractServerTest
private void testRequestWithContinuationFrames(PriorityFrame priorityFrame, Callable<ByteBufferPool.Lease> frames) throws Exception
{
final CountDownLatch serverLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
startServer(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)

View File

@ -78,12 +78,12 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -106,7 +106,7 @@ public class HTTP2Test extends AbstractTest
@Test
public void testRequestNoContentResponseEmptyContent() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -124,12 +124,12 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -138,13 +138,15 @@ public class HTTP2Test extends AbstractTest
assertEquals(stream.getId(), frame.getStreamId());
MetaData.Response response = (MetaData.Response)frame.getMetaData();
assertEquals(200, response.getStatus());
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
assertTrue(frame.isEndStream());
callback.succeeded();
Stream.Data data = stream.readData();
assertTrue(data.frame().isEndStream());
data.release();
latch.countDown();
}
});
@ -155,7 +157,7 @@ public class HTTP2Test extends AbstractTest
@Test
public void testRequestNoContentResponseContent() throws Exception
{
final byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8);
byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8);
start(new Handler.Processor()
{
@Override
@ -165,12 +167,12 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
final CountDownLatch latch = new CountDownLatch(2);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(2);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -183,16 +185,19 @@ public class HTTP2Test extends AbstractTest
MetaData.Response response = (MetaData.Response)frame.getMetaData();
assertEquals(200, response.getStatus());
stream.demand();
latch.countDown();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
DataFrame frame = data.frame();
assertTrue(frame.isEndStream());
assertEquals(ByteBuffer.wrap(content), frame.getData());
callback.succeeded();
data.release();
latch.countDown();
}
});
@ -212,25 +217,27 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
CountDownLatch latch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
Promise.Completable<Stream> streamCompletable = new Promise.Completable<>();
session.newStream(frame, streamCompletable, new Stream.Listener.Adapter()
session.newStream(frame, streamCompletable, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
streamCompletable.thenCompose(stream ->
{
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(1024), false);
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(512), false);
Callback.Completable dataCompletable = new Callback.Completable();
stream.data(dataFrame, dataCompletable);
return dataCompletable.thenApply(y -> stream);
@ -246,7 +253,7 @@ public class HTTP2Test extends AbstractTest
@Test
public void testMultipleRequests() throws Exception
{
final String downloadBytes = "X-Download";
String downloadBytes = "X-Download";
start(new Handler.Processor()
{
@Override
@ -260,7 +267,7 @@ public class HTTP2Test extends AbstractTest
});
int requests = 20;
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
Random random = new Random();
HttpFields fields = HttpFields.build()
@ -268,16 +275,18 @@ public class HTTP2Test extends AbstractTest
.put("User-Agent", "HTTP2Client/" + Jetty.VERSION);
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
final CountDownLatch latch = new CountDownLatch(requests);
CountDownLatch latch = new CountDownLatch(requests);
for (int i = 0; i < requests; ++i)
{
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.countDown();
}
});
@ -289,7 +298,7 @@ public class HTTP2Test extends AbstractTest
@Test
public void testCustomResponseCode() throws Exception
{
final int status = 475;
int status = 475;
start(new Handler.Processor()
{
@Override
@ -300,11 +309,11 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -322,9 +331,9 @@ public class HTTP2Test extends AbstractTest
@Test
public void testHostHeader() throws Exception
{
final String host = "fooBar";
final int port = 1313;
final String authority = host + ":" + port;
String host = "fooBar";
int port = 1313;
String authority = host + ":" + port;
start(new Handler.Processor()
{
@Override
@ -336,12 +345,12 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HostPortHttpField hostHeader = new HostPortHttpField(authority);
MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP.asString(), hostHeader, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -359,15 +368,16 @@ public class HTTP2Test extends AbstractTest
@Test
public void testServerSendsGoAwayOnStop() throws Exception
{
start(new ServerSessionListener.Adapter());
start(new ServerSessionListener() {});
CountDownLatch closeLatch = new CountDownLatch(1);
newClientSession(new Session.Listener.Adapter()
newClientSession(new Session.Listener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
});
@ -382,16 +392,17 @@ public class HTTP2Test extends AbstractTest
public void testClientSendsGoAwayOnStop() throws Exception
{
CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
});
newClientSession(new Session.Listener.Adapter());
newClientSession(new Session.Listener() {});
sleep(1000);
@ -404,7 +415,7 @@ public class HTTP2Test extends AbstractTest
public void testMaxConcurrentStreams() throws Exception
{
int maxStreams = 2;
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -424,7 +435,7 @@ public class HTTP2Test extends AbstractTest
});
CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
@ -437,7 +448,7 @@ public class HTTP2Test extends AbstractTest
MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY);
FuturePromise<Stream> promise1 = new FuturePromise<>();
CountDownLatch exchangeLatch1 = new CountDownLatch(2);
session.newStream(new HeadersFrame(request1, null, false), promise1, new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request1, null, false), promise1, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -451,7 +462,7 @@ public class HTTP2Test extends AbstractTest
MetaData.Request request2 = newRequest("GET", HttpFields.EMPTY);
FuturePromise<Stream> promise2 = new FuturePromise<>();
CountDownLatch exchangeLatch2 = new CountDownLatch(2);
session.newStream(new HeadersFrame(request2, null, false), promise2, new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request2, null, false), promise2, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -473,7 +484,7 @@ public class HTTP2Test extends AbstractTest
if (x instanceof IllegalStateException)
maxStreamsLatch.countDown();
}
}, new Stream.Listener.Adapter());
}, null);
assertTrue(maxStreamsLatch.await(5, TimeUnit.SECONDS));
assertEquals(2, session.getStreams().size());
@ -500,7 +511,7 @@ public class HTTP2Test extends AbstractTest
{
exchangeLatch4.countDown();
}
}, new Stream.Listener.Adapter()
}, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -528,7 +539,7 @@ public class HTTP2Test extends AbstractTest
@Test
public void testInvalidAPIUsageOnClient() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -536,13 +547,16 @@ public class HTTP2Test extends AbstractTest
Callback.Completable completable = new Callback.Completable();
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, false), completable);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
{
completable.thenRun(() ->
{
@ -555,19 +569,21 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, false);
Promise.Completable<Stream> completable = new Promise.Completable<>();
CountDownLatch completeLatch = new CountDownLatch(2);
session.newStream(frame, completable, new Stream.Listener.Adapter()
session.newStream(frame, completable, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
completeLatch.countDown();
}
});
@ -624,7 +640,7 @@ public class HTTP2Test extends AbstractTest
{
long sleep = 1000;
CountDownLatch completeLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -642,14 +658,7 @@ public class HTTP2Test extends AbstractTest
sleep(2 * sleep);
return super.getMetaData();
}
}, new Callback()
{
@Override
public void succeeded()
{
stream.data(dataFrame, NOOP);
}
});
}, Callback.from(() -> stream.data(dataFrame, Callback.NOOP), Throwable::printStackTrace));
}).start();
// Wait for the headers() call to happen.
@ -674,17 +683,18 @@ public class HTTP2Test extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
completeLatch.countDown();
}
});
@ -695,7 +705,7 @@ public class HTTP2Test extends AbstractTest
@Test
public void testCleanGoAwayDoesNotTriggerFailureNotification() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -711,23 +721,25 @@ public class HTTP2Test extends AbstractTest
CountDownLatch closeLatch = new CountDownLatch(1);
CountDownLatch failureLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
failureLatch.countDown();
callback.succeeded();
}
});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame request = new HeadersFrame(metaData, null, true);
session.newStream(request, new Promise.Adapter<>(), new Stream.Listener.Adapter());
session.newStream(request, new Promise.Adapter<>(), null);
// Make sure onClose() is called.
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
@ -747,13 +759,13 @@ public class HTTP2Test extends AbstractTest
});
// A bad header in the request should fail on the client.
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HttpFields requestFields = HttpFields.build()
.put(":custom", "special");
MetaData.Request metaData = newRequest("GET", requestFields);
HeadersFrame request = new HeadersFrame(metaData, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(request, promise, new Stream.Listener.Adapter());
session.newStream(request, promise, null);
ExecutionException x = assertThrows(ExecutionException.class, () -> promise.get(5, TimeUnit.SECONDS));
assertThat(x.getCause(), instanceOf(HpackException.StreamException.class));
}
@ -772,17 +784,18 @@ public class HTTP2Test extends AbstractTest
});
// Good request with bad header in the response.
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame request = new HeadersFrame(metaData, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch resetLatch = new CountDownLatch(1);
session.newStream(request, promise, new Stream.Listener.Adapter()
session.newStream(request, promise, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
@ -815,19 +828,20 @@ public class HTTP2Test extends AbstractTest
});
// Good request with bad header in the response.
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", "/flush", HttpFields.EMPTY);
HeadersFrame request = new HeadersFrame(metaData, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch resetLatch = new CountDownLatch(1);
session.newStream(request, promise, new Stream.Listener.Adapter()
session.newStream(request, promise, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
// Cannot receive a 500 because we force the flush on the server, so
// the response is committed even if the server was not able to write it.
resetLatch.countDown();
callback.succeeded();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
@ -842,7 +856,7 @@ public class HTTP2Test extends AbstractTest
AtomicReference<Session> serverSessionRef = new AtomicReference<>();
CountDownLatch serverSessionLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -854,14 +868,17 @@ public class HTTP2Test extends AbstractTest
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
stream.demand();
dataLatch.countDown();
if (frame.isEndStream())
if (data.frame().isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
@ -876,7 +893,7 @@ public class HTTP2Test extends AbstractTest
CountDownLatch clientGracefulGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientCloseLatch = new CountDownLatch(1);
Session clientSession = newClientSession(new Session.Listener.Adapter()
Session clientSession = newClientSession(new Session.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
@ -888,9 +905,10 @@ public class HTTP2Test extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
clientCloseLatch.countDown();
callback.succeeded();
}
});
assertTrue(serverSessionLatch.await(5, TimeUnit.SECONDS));
@ -901,7 +919,7 @@ public class HTTP2Test extends AbstractTest
MetaData.Request metaData1 = newRequest("GET", HttpFields.EMPTY);
HeadersFrame request1 = new HeadersFrame(metaData1, null, false);
FuturePromise<Stream> promise1 = new FuturePromise<>();
Stream.Listener.Adapter listener = new Stream.Listener.Adapter()
Stream.Listener listener = new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -943,7 +961,7 @@ public class HTTP2Test extends AbstractTest
MetaData.Request metaData3 = new MetaData.Request("GET", HttpScheme.HTTP.asString(), authority3, "/", HttpVersion.HTTP_2, HttpFields.EMPTY, -1);
HeadersFrame request3 = new HeadersFrame(metaData3, null, true);
FuturePromise<Stream> promise3 = new FuturePromise<>();
clientSession.newStream(request3, promise3, new Stream.Listener.Adapter());
clientSession.newStream(request3, promise3, null);
assertThrows(ExecutionException.class, () -> promise3.get(5, TimeUnit.SECONDS));
// Finish the previous requests and expect the responses.

View File

@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.LongConsumer;
import java.util.function.UnaryOperator;
import org.eclipse.jetty.client.HttpClient;
@ -83,6 +84,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -122,17 +124,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
public void testRequestAbortSendsResetFrame() throws Exception
{
CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
};
}
@ -149,7 +152,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
public void testResponseAbortSendsResetFrame() throws Exception
{
CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -165,12 +168,13 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
}
});
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
};
}
@ -208,10 +212,74 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testDelayDemandAfterHeaders() throws Exception
{
start(new Handler.Processor()
{
@Override
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
Callback.Completable completable = new Callback.Completable();
response.write(false, ByteBuffer.allocate(1), completable);
completable.whenComplete((r, x) ->
{
if (x != null)
callback.failed(x);
else
response.write(true, ByteBuffer.allocate(2), callback);
});
}
});
AtomicReference<LongConsumer> demandRef = new AtomicReference<>();
CountDownLatch beforeContentLatch = new CountDownLatch(1);
AtomicInteger contentCount = new AtomicInteger();
CountDownLatch latch = new CountDownLatch(1);
httpClient.newRequest("localhost", connector.getLocalPort())
.onResponseContentDemanded(new org.eclipse.jetty.client.api.Response.DemandedContentListener()
{
@Override
public void onBeforeContent(org.eclipse.jetty.client.api.Response response, LongConsumer demand)
{
// Do not demand.
demandRef.set(demand);
beforeContentLatch.countDown();
}
@Override
public void onContent(org.eclipse.jetty.client.api.Response response, LongConsumer demand, ByteBuffer content, Callback callback)
{
contentCount.incrementAndGet();
callback.succeeded();
demand.accept(1);
}
})
.timeout(5, TimeUnit.SECONDS)
.send(result ->
{
assertTrue(result.isSucceeded());
assertEquals(HttpStatus.OK_200, result.getResponse().getStatus());
latch.countDown();
});
assertTrue(beforeContentLatch.await(5, TimeUnit.SECONDS));
// Verify that the response is not completed yet.
assertFalse(latch.await(1, TimeUnit.SECONDS));
assertEquals(0, contentCount.get());
// Demand to receive the content.
demandRef.get().accept(1);
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertEquals(2, contentCount.get());
}
@Test
public void testLastStreamId() throws Exception
{
prepareServer(new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter()
prepareServer(new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -370,17 +438,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
long idleTimeout = 1000;
CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
};
}
@ -402,17 +471,18 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
public void testRequestIdleTimeoutSendsResetFrame() throws Exception
{
CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
};
}
@ -561,7 +631,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
public void test204WithContent() throws Exception
{
byte[] bytes = "No Content".getBytes(StandardCharsets.UTF_8);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -588,7 +658,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
@Test
public void testInvalidResponseHPack() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)

View File

@ -23,7 +23,6 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -60,7 +59,7 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerEnforcingIdleTimeout() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
@ -74,13 +73,14 @@ public class IdleTimeoutTest extends AbstractTest
});
connector.setIdleTimeout(idleTimeout);
final CountDownLatch latch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
latch.countDown();
callback.succeeded();
}
});
@ -93,7 +93,7 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
}, null);
assertTrue(latch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
}
@ -101,7 +101,7 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -112,13 +112,14 @@ public class IdleTimeoutTest extends AbstractTest
});
connector.setIdleTimeout(idleTimeout);
final CountDownLatch latch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
latch.countDown();
callback.succeeded();
}
});
@ -132,7 +133,7 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
}, null);
assertTrue(latch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
}
@ -140,7 +141,7 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerNotEnforcingIdleTimeoutWithinCallback() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -159,17 +160,18 @@ public class IdleTimeoutTest extends AbstractTest
});
connector.setIdleTimeout(idleTimeout);
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClientSession(new ServerSessionListener.Adapter()
CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClientSession(new ServerSessionListener()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
});
final CountDownLatch replyLatch = new CountDownLatch(1);
CountDownLatch replyLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>()
@ -179,7 +181,7 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter()
}, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -197,8 +199,8 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testClientEnforcingIdleTimeout() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -211,14 +213,15 @@ public class IdleTimeoutTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
});
http2Client.setIdleTimeout(idleTimeout);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>()
@ -228,7 +231,7 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
}, null);
assertTrue(closeLatch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
assertTrue(session.isClosed());
@ -237,8 +240,8 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testClientEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -248,14 +251,15 @@ public class IdleTimeoutTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
});
http2Client.setIdleTimeout(idleTimeout);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>()
@ -265,7 +269,7 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
}, null);
assertTrue(closeLatch.await(5 * idleTimeout, TimeUnit.MILLISECONDS));
}
@ -273,8 +277,8 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testClientNotEnforcingIdleTimeoutWithinCallback() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch closeLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -287,16 +291,17 @@ public class IdleTimeoutTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
closeLatch.countDown();
callback.succeeded();
}
});
http2Client.setIdleTimeout(idleTimeout);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
final CountDownLatch replyLatch = new CountDownLatch(1);
CountDownLatch replyLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>()
@ -306,7 +311,7 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter()
}, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -326,7 +331,7 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testClientEnforcingStreamIdleTimeout() throws Exception
{
final int idleTimeout = 1000;
int idleTimeout = 1000;
start(new Handler.Processor()
{
@Override
@ -337,10 +342,10 @@ public class IdleTimeoutTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
final CountDownLatch dataLatch = new CountDownLatch(1);
final CountDownLatch timeoutLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(1);
CountDownLatch timeoutLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>()
@ -350,12 +355,13 @@ public class IdleTimeoutTest extends AbstractTest
{
stream.setIdleTimeout(idleTimeout);
}
}, new Stream.Listener.Adapter()
}, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
dataLatch.countDown();
}
@ -381,14 +387,14 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerEnforcingStreamIdleTimeout() throws Exception
{
final CountDownLatch timeoutLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch timeoutLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(idleTimeout);
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public boolean onIdleTimeout(Stream stream, Throwable x)
@ -400,17 +406,18 @@ public class IdleTimeoutTest extends AbstractTest
}
});
final CountDownLatch resetLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter());
CountDownLatch resetLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
// Stream does not end here, but we won't send any DATA frame.
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.countDown();
callback.succeeded();
}
});
@ -426,14 +433,15 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testServerStreamIdleTimeoutIsNotEnforcedWhenReceiving() throws Exception
{
final CountDownLatch timeoutLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch timeoutLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(idleTimeout);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public boolean onIdleTimeout(Stream stream, Throwable x)
@ -445,15 +453,15 @@ public class IdleTimeoutTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(requestFrame, promise, new Stream.Listener.Adapter());
final Stream stream = promise.get(5, TimeUnit.SECONDS);
session.newStream(requestFrame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
sleep(idleTimeout / 2);
final CountDownLatch dataLatch = new CountDownLatch(1);
CountDownLatch dataLatch = new CountDownLatch(1);
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), new Callback()
{
private int sends;
@ -462,15 +470,9 @@ public class IdleTimeoutTest extends AbstractTest
public void succeeded()
{
sleep(idleTimeout / 2);
final boolean last = ++sends == 2;
boolean last = ++sends == 2;
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), last), !last ? this : new Callback()
{
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
@Override
public void succeeded()
{
@ -478,6 +480,12 @@ public class IdleTimeoutTest extends AbstractTest
assertEquals(1, timeoutLatch.getCount());
dataLatch.countDown();
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
});
}
});
@ -490,8 +498,8 @@ public class IdleTimeoutTest extends AbstractTest
@Test
public void testClientStreamIdleTimeoutIsNotEnforcedWhenSending() throws Exception
{
final CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -508,7 +516,7 @@ public class IdleTimeoutTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> promise = new FuturePromise<>()
@ -520,8 +528,8 @@ public class IdleTimeoutTest extends AbstractTest
super.succeeded(stream);
}
};
session.newStream(requestFrame, promise, new Stream.Listener.Adapter());
final Stream stream = promise.get(5, TimeUnit.SECONDS);
session.newStream(requestFrame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
Callback.Completable completable1 = new Callback.Completable();
sleep(idleTimeout / 2);
@ -589,12 +597,12 @@ public class IdleTimeoutTest extends AbstractTest
// to make sure it does not fire spuriously.
connector.setIdleTimeout(3 * delay);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, promise, new Stream.Listener.Adapter()
session.newStream(requestFrame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -649,7 +657,7 @@ public class IdleTimeoutTest extends AbstractTest
prepareClient();
http2Client.start();
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
// Send requests until one is queued on the server but not dispatched.
int count = 0;
@ -661,7 +669,7 @@ public class IdleTimeoutTest extends AbstractTest
MetaData.Request request = newRequest("GET", "/" + count, HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter());
client.newStream(frame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(10);
stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP);
@ -675,16 +683,17 @@ public class IdleTimeoutTest extends AbstractTest
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
client.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
callback.succeeded();
resetLatch.countDown();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(((ISession)client).updateSendWindow(0));
ByteBuffer data = ByteBuffer.allocate(((HTTP2Session)client).updateSendWindow(0));
stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP);
assertTrue(resetLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
@ -692,7 +701,7 @@ public class IdleTimeoutTest extends AbstractTest
// Wait for WINDOW_UPDATEs to be processed by the client.
sleep(1000);
assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0));
assertThat(((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0));
}
private void sleep(long value)

View File

@ -29,7 +29,6 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -37,6 +36,7 @@ import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
@ -57,7 +57,7 @@ public class InterleavingTest extends AbstractTest
{
CountDownLatch serverStreamsLatch = new CountDownLatch(2);
List<Stream> serverStreams = new ArrayList<>();
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -69,7 +69,7 @@ public class InterleavingTest extends AbstractTest
});
int maxFrameSize = Frame.DEFAULT_MAX_LENGTH + 1;
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -80,13 +80,16 @@ public class InterleavingTest extends AbstractTest
}
});
BlockingQueue<DataFrameCallback> dataFrames = new LinkedBlockingDeque<>();
Stream.Listener streamListener = new Stream.Listener.Adapter()
BlockingQueue<Stream.Data> dataQueue = new LinkedBlockingDeque<>();
Stream.Listener streamListener = new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
dataFrames.offer(new DataFrameCallback(frame, callback));
Stream.Data data = stream.readData();
// Do not release.
dataQueue.offer(data);
stream.demand();
}
};
@ -110,9 +113,9 @@ public class InterleavingTest extends AbstractTest
serverStream1.headers(new HeadersFrame(serverStream1.getId(), response1, null, false), Callback.NOOP);
Random random = new Random();
byte[] content1 = new byte[2 * ((ISession)serverStream1.getSession()).updateSendWindow(0)];
byte[] content1 = new byte[2 * ((HTTP2Session)serverStream1.getSession()).updateSendWindow(0)];
random.nextBytes(content1);
byte[] content2 = new byte[2 * ((ISession)serverStream2.getSession()).updateSendWindow(0)];
byte[] content2 = new byte[2 * ((HTTP2Session)serverStream2.getSession()).updateSendWindow(0)];
random.nextBytes(content2);
MetaData.Response response2 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
@ -143,11 +146,11 @@ public class InterleavingTest extends AbstractTest
int finished = 0;
while (finished < 2)
{
DataFrameCallback dataFrameCallback = dataFrames.poll(5, TimeUnit.SECONDS);
if (dataFrameCallback == null)
Stream.Data data = dataQueue.poll(5, TimeUnit.SECONDS);
if (data == null)
fail();
DataFrame dataFrame = dataFrameCallback.frame;
DataFrame dataFrame = data.frame();
int streamId = dataFrame.getStreamId();
int length = dataFrame.remaining();
streamLengths.add(new StreamLength(streamId, length));
@ -156,7 +159,7 @@ public class InterleavingTest extends AbstractTest
BufferUtil.writeTo(dataFrame.getData(), contents.get(streamId));
dataFrameCallback.callback.succeeded();
data.release();
}
// Verify that the content has been sent properly.
@ -196,18 +199,6 @@ public class InterleavingTest extends AbstractTest
});
}
private static class DataFrameCallback
{
private final DataFrame frame;
private final Callback callback;
private DataFrameCallback(DataFrame frame, Callback callback)
{
this.frame = frame;
this.callback = callback;
}
}
private static class StreamLength
{
private final int stream;

View File

@ -449,7 +449,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
public void testTCPCongestedStreamTimesOut() throws Exception
{
CountDownLatch request1Latch = new CountDownLatch(1);
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter()
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -475,8 +475,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}
// Return a Stream listener that consumes the content.
return new Stream.Listener.Adapter();
return null;
}
});
http2.setMaxConcurrentStreams(2);
@ -559,7 +558,7 @@ public class MaxConcurrentStreamsTest extends AbstractTest
public void testDifferentMaxConcurrentStreamsForDifferentConnections() throws Exception
{
long processing = 125;
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener.Adapter()
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), new ServerSessionListener()
{
private Session session1;
private Session session2;
@ -728,9 +727,9 @@ public class MaxConcurrentStreamsTest extends AbstractTest
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
listener.onClose(session, frame);
listener.onClose(session, frame, callback);
}
@Override
@ -740,9 +739,9 @@ public class MaxConcurrentStreamsTest extends AbstractTest
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
listener.onFailure(session, failure);
listener.onFailure(session, failure, callback);
}
}
}

View File

@ -52,7 +52,7 @@ public class MaxPushedStreamsTest extends AbstractTest
int maxPushed = 2;
CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -77,7 +77,7 @@ public class MaxPushedStreamsTest extends AbstractTest
.map(pushFrame ->
{
Promise.Completable<Stream> promise = new Promise.Completable<>();
stream.push(pushFrame, promise, new Stream.Listener.Adapter());
stream.push(pushFrame, promise, null);
return promise;
})
// ... wait for the pushed streams...
@ -88,13 +88,14 @@ public class MaxPushedStreamsTest extends AbstractTest
{
PushPromiseFrame extraPushFrame = new PushPromiseFrame(stream.getId(), 0, newRequest("GET", "/push_extra", HttpFields.EMPTY));
FuturePromise<Stream> extraPromise = new FuturePromise<>();
stream.push(extraPushFrame, extraPromise, new Stream.Listener.Adapter()
stream.push(extraPushFrame, extraPromise, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
assertEquals(ErrorCode.REFUSED_STREAM_ERROR.code, frame.getError());
resetLatch.countDown();
callback.succeeded();
}
});
return streams;
@ -116,10 +117,10 @@ public class MaxPushedStreamsTest extends AbstractTest
});
http2Client.setMaxConcurrentPushedStreams(maxPushed);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(new HeadersFrame(request, null, true), new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -31,12 +31,12 @@ public class PingTest extends AbstractTest
@Test
public void testPing() throws Exception
{
start(new ServerSessionListener.Adapter());
start(new ServerSessionListener() {});
final byte[] payload = new byte[8];
new Random().nextBytes(payload);
final CountDownLatch latch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public void onPing(Session session, PingFrame frame)

View File

@ -72,7 +72,7 @@ public class PrefaceTest extends AbstractTest
@Test
public void testServerPrefaceReplySentAfterClientPreface() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onAccept(Session session)
@ -91,7 +91,7 @@ public class PrefaceTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -114,7 +114,7 @@ public class PrefaceTest extends AbstractTest
CountDownLatch latch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -130,7 +130,7 @@ public class PrefaceTest extends AbstractTest
@Test
public void testClientPrefaceReplySentAfterServerPreface() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -197,8 +197,10 @@ public class PrefaceTest extends AbstractTest
assertEquals(2, settings.size());
SettingsFrame frame1 = settings.poll();
assertNotNull(frame1);
assertFalse(frame1.isReply());
SettingsFrame frame2 = settings.poll();
assertNotNull(frame2);
assertTrue(frame2.isReply());
}
}
@ -215,7 +217,7 @@ public class PrefaceTest extends AbstractTest
@Override
protected ServerSessionListener newSessionListener(Connector connector, EndPoint endPoint)
{
return new ServerSessionListener.Adapter()
return new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -249,13 +251,14 @@ public class PrefaceTest extends AbstractTest
{
socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()));
String upgradeRequest =
"GET /one HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: Upgrade, HTTP2-Settings\r\n" +
"Upgrade: h2c\r\n" +
"HTTP2-Settings: \r\n" +
"\r\n";
String upgradeRequest = """
GET /one HTTP/1.1\r
Host: localhost\r
Connection: Upgrade, HTTP2-Settings\r
Upgrade: h2c\r
HTTP2-Settings: \r
\r
""";
ByteBuffer upgradeBuffer = ByteBuffer.wrap(upgradeRequest.getBytes(StandardCharsets.ISO_8859_1));
socket.write(upgradeBuffer);
@ -355,12 +358,13 @@ public class PrefaceTest extends AbstractTest
CountDownLatch failureLatch = new CountDownLatch(1);
Promise.Completable<Session> promise = new Promise.Completable<>();
InetSocketAddress address = new InetSocketAddress("localhost", server.getLocalPort());
http2Client.connect(address, new Session.Listener.Adapter()
http2Client.connect(address, new Session.Listener()
{
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
failureLatch.countDown();
callback.succeeded();
}
}, promise);
@ -389,7 +393,7 @@ public class PrefaceTest extends AbstractTest
@Test
public void testInvalidClientPreface() throws Exception
{
start(new ServerSessionListener.Adapter());
start(new ServerSessionListener() {});
try (Socket client = new Socket("localhost", connector.getLocalPort()))
{

View File

@ -139,13 +139,13 @@ public class PriorKnowledgeHTTP2OverTLSTest
});
int port = connector.getLocalPort();
MetaData.Response response = http2Client.connect(http2Client.getClientConnector().getSslContextFactory(), new InetSocketAddress("localhost", port), new Session.Listener.Adapter())
MetaData.Response response = http2Client.connect(http2Client.getClientConnector().getSslContextFactory(), new InetSocketAddress("localhost", port), new Session.Listener() {})
.thenCompose(session ->
{
CompletableFuture<MetaData.Response> responsePromise = new CompletableFuture<>();
HttpURI.Mutable uri = HttpURI.build("https://localhost:" + port + "/path");
MetaData.Request request = new MetaData.Request("GET", uri, HttpVersion.HTTP_2, HttpFields.EMPTY);
return session.newStream(new HeadersFrame(request, null, true), new Stream.Listener.Adapter()
return session.newStream(new HeadersFrame(request, null, true), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -38,7 +38,7 @@ public class PriorityTest extends AbstractTest
@Test
public void testPriorityBeforeHeaders() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -50,7 +50,7 @@ public class PriorityTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
int streamId = session.priority(new PriorityFrame(0, 13, false), Callback.NOOP);
assertTrue(streamId > 0);
@ -65,7 +65,7 @@ public class PriorityTest extends AbstractTest
assertEquals(streamId, result.getId());
latch.countDown();
}
}, new Stream.Listener.Adapter()
}, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -83,7 +83,7 @@ public class PriorityTest extends AbstractTest
{
CountDownLatch beforeRequests = new CountDownLatch(1);
CountDownLatch afterRequests = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -106,7 +106,7 @@ public class PriorityTest extends AbstractTest
});
CountDownLatch responses = new CountDownLatch(2);
Stream.Listener.Adapter listener = new Stream.Listener.Adapter()
Stream.Listener listener = new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -116,7 +116,7 @@ public class PriorityTest extends AbstractTest
}
};
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData metaData1 = newRequest("GET", "/one", HttpFields.EMPTY);
HeadersFrame headersFrame1 = new HeadersFrame(metaData1, null, true);
FuturePromise<Stream> promise1 = new FuturePromise<>();
@ -145,7 +145,7 @@ public class PriorityTest extends AbstractTest
{
PriorityFrame priorityFrame = new PriorityFrame(13, 200, true);
CountDownLatch latch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -164,10 +164,10 @@ public class PriorityTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData metaData = newRequest("GET", "/one", HttpFields.EMPTY);
HeadersFrame headersFrame = new HeadersFrame(metaData, priorityFrame, true);
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -100,14 +100,14 @@ public class ProxyProtocolTest
channel.write(ByteBuffer.wrap(request1.getBytes(StandardCharsets.UTF_8)));
FuturePromise<Session> promise = new FuturePromise<>();
client.accept(null, channel, new Session.Listener.Adapter(), promise);
client.accept(null, channel, new Session.Listener() {}, promise);
Session session = promise.get(5, TimeUnit.SECONDS);
String uri = "http://localhost:" + connector.getLocalPort() + "/";
MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from(uri), HttpVersion.HTTP_2, HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -149,14 +149,14 @@ public class ProxyProtocolTest
channel.write(ByteBuffer.wrap(StringUtil.fromHexString(request1)));
FuturePromise<Session> promise = new FuturePromise<>();
client.accept(null, channel, new Session.Listener.Adapter(), promise);
client.accept(null, channel, new Session.Listener() {}, promise);
Session session = promise.get(5, TimeUnit.SECONDS);
String uri = "http://localhost:" + connector.getLocalPort() + "/";
MetaData.Request metaData = new MetaData.Request("GET", HttpURI.from(uri), HttpVersion.HTTP_2, HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -124,10 +124,10 @@ public class ProxyTest
// startClient();
//
// CountDownLatch clientLatch = new CountDownLatch(1);
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
// MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -162,10 +162,10 @@ public class ProxyTest
// startClient();
//
// CountDownLatch clientLatch = new CountDownLatch(1);
// Session session = newClient(new Session.Listener.Adapter());
// Session session = newClient(new Session.Listener() {});
// MetaData.Request metaData = newRequest("GET", "/", HttpFields.EMPTY);
// HeadersFrame frame = new HeadersFrame(metaData, null, true);
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)

View File

@ -68,13 +68,13 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary and secondary resource to build the cache.
// final String referrerURI = newURI(primaryResource);
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -86,7 +86,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build()
// .put(HttpHeader.REFERER, referrerURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -104,7 +104,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource, HttpFields.EMPTY);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(2);
// final CountDownLatch pushLatch = new CountDownLatch(2);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -169,7 +169,7 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary and secondary resource to build the cache.
// // The referrerURI does not point to the primary resource, so there will be no
@ -178,7 +178,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -190,7 +190,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build()
// .put(HttpHeader.REFERER, referrerURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -208,7 +208,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
@ -257,14 +257,14 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary and secondary resource to build the cache.
// final String primaryURI = newURI(primaryResource);
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -276,7 +276,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build()
// .put(HttpHeader.REFERER, primaryURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -294,7 +294,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
@ -330,7 +330,7 @@ public class PushCacheFilterTest extends AbstractTest
// secondaryFields.put(HttpHeader.REFERER, primaryURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// final CountDownLatch secondaryResponseLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -360,14 +360,14 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary and secondary resource to build the cache.
// final String primaryURI = newURI(primaryResource);
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -378,7 +378,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build();
// secondaryFields.put(HttpHeader.REFERER, primaryURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -398,7 +398,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -451,14 +451,14 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary, secondary and tertiary resource to build the cache.
// final String primaryURI = newURI(primaryResource);
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(2);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -471,7 +471,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields1 = HttpFields.build()
// .put(HttpHeader.REFERER, primaryURI);
// MetaData.Request secondaryRequest1 = newRequest("GET", secondaryResource1, secondaryFields1);
// session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest1, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -500,7 +500,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields2 = HttpFields.build()
// .put(HttpHeader.REFERER, primaryURI);
// MetaData.Request secondaryRequest2 = newRequest("GET", secondaryResource2, secondaryFields2);
// session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest2, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -522,7 +522,7 @@ public class PushCacheFilterTest extends AbstractTest
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch primaryPushesLatch = new CountDownLatch(3);
// final CountDownLatch recursiveLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -574,7 +574,7 @@ public class PushCacheFilterTest extends AbstractTest
// CountDownLatch secondaryResponseLatch = new CountDownLatch(1);
// CountDownLatch secondaryPushLatch = new CountDownLatch(1);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource1, HttpFields.EMPTY);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -641,13 +641,13 @@ public class PushCacheFilterTest extends AbstractTest
// });
// final String primaryURI = newURI(primaryResource);
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Login with the wrong credentials, causing a redirect to self.
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource + "?credentials=wrong", primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -684,7 +684,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource + "?credentials=secret", primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -733,14 +733,14 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary and secondary resource to build the cache.
// final String primaryURI = newURI(primaryResource);
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -751,7 +751,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build();
// secondaryFields.put(HttpHeader.REFERER, primaryURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onHeaders(Stream stream, HeadersFrame frame)
@ -771,7 +771,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
@ -826,14 +826,14 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter());
// final Session session = newClient(new Session.Listener() {});
//
// // Request for the primary and secondary resource to build the cache.
// final String referrerURI = newURI(primaryResource);
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -845,7 +845,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build();
// secondaryFields.put(HttpHeader.REFERER, referrerURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -863,7 +863,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("POST", primaryResource, primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
@ -912,7 +912,7 @@ public class PushCacheFilterTest extends AbstractTest
// }
// });
//
// final Session session = newClient(new Session.Listener.Adapter()
// final Session session = newClient(new Session.Listener()
// {
// @Override
// public Map<Integer, Integer> onPreface(Session session)
@ -928,7 +928,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable primaryFields = HttpFields.build();
// MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch warmupLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -940,7 +940,7 @@ public class PushCacheFilterTest extends AbstractTest
// HttpFields.Mutable secondaryFields = HttpFields.build();
// secondaryFields.put(HttpHeader.REFERER, referrerURI);
// MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(secondaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public void onData(Stream stream, DataFrame frame, Callback callback)
@ -958,7 +958,7 @@ public class PushCacheFilterTest extends AbstractTest
// primaryRequest = newRequest("GET", primaryResource, primaryFields);
// final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
// final CountDownLatch pushLatch = new CountDownLatch(1);
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener.Adapter()
// session.newStream(new HeadersFrame(primaryRequest, null, true), new Promise.Adapter<>(), new Stream.Listener()
// {
// @Override
// public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)

View File

@ -51,7 +51,7 @@ public class PushedResourcesTest extends AbstractTest
{
String pushPath = "/secondary";
CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -67,12 +67,13 @@ public class PushedResourcesTest extends AbstractTest
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
}
}, new Stream.Listener.Adapter()
}, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
latch.countDown();
callback.succeeded();
}
});
return null;

View File

@ -112,14 +112,14 @@ public class RawHTTP2ProxyTest
byte[] data1 = new byte[1024];
new Random().nextBytes(data1);
ByteBuffer buffer1 = ByteBuffer.wrap(data1);
Server server1 = startServer("server1", new ServerSessionListener.Adapter()
Server server1 = startServer("server1", new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("SERVER1 received {}", frame);
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -149,21 +149,23 @@ public class RawHTTP2ProxyTest
}
});
ServerConnector connector1 = (ServerConnector)server1.getAttribute("connector");
Server server2 = startServer("server2", new ServerSessionListener.Adapter()
Server server2 = startServer("server2", new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("SERVER2 received {}", frame);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
if (LOGGER.isDebugEnabled())
LOGGER.debug("SERVER2 received {}", frame);
callback.succeeded();
LOGGER.debug("SERVER2 received {}", data);
data.release();
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
Callback.Completable completable1 = new Callback.Completable();
HeadersFrame reply = new HeadersFrame(stream.getId(), response, null, false);
@ -173,10 +175,10 @@ public class RawHTTP2ProxyTest
completable1.thenCompose(ignored ->
{
Callback.Completable completable2 = new Callback.Completable();
DataFrame data = new DataFrame(stream.getId(), buffer1.slice(), false);
DataFrame dataFrame = new DataFrame(stream.getId(), buffer1.slice(), false);
if (LOGGER.isDebugEnabled())
LOGGER.debug("SERVER2 sending {}", data);
stream.data(data, completable2);
LOGGER.debug("SERVER2 sending {}", dataFrame);
stream.data(dataFrame, completable2);
return completable2;
}).thenRun(() ->
{
@ -198,7 +200,7 @@ public class RawHTTP2ProxyTest
HTTP2Client client = startClient("client");
FuturePromise<Session> clientPromise = new FuturePromise<>();
client.connect(proxyAddress, new Session.Listener.Adapter(), clientPromise);
client.connect(proxyAddress, new Session.Listener() {}, clientPromise);
Session clientSession = clientPromise.get(5, TimeUnit.SECONDS);
// Send a request with trailers for server1.
@ -207,23 +209,27 @@ public class RawHTTP2ProxyTest
MetaData.Request request1 = new MetaData.Request("GET", HttpURI.from("http://localhost/server1"), HttpVersion.HTTP_2, fields1);
FuturePromise<Stream> streamPromise1 = new FuturePromise<>();
CountDownLatch latch1 = new CountDownLatch(1);
clientSession.newStream(new HeadersFrame(request1, null, false), streamPromise1, new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request1, null, false), streamPromise1, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("CLIENT received {}", frame);
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
DataFrame frame = data.frame();
if (LOGGER.isDebugEnabled())
LOGGER.debug("CLIENT received {}", frame);
assertEquals(buffer1.slice(), frame.getData());
callback.succeeded();
data.release();
latch1.countDown();
stream.demand();
}
});
Stream stream1 = streamPromise1.get(5, TimeUnit.SECONDS);
@ -235,7 +241,7 @@ public class RawHTTP2ProxyTest
MetaData.Request request2 = new MetaData.Request("GET", HttpURI.from("http://localhost/server1"), HttpVersion.HTTP_2, fields2);
FuturePromise<Stream> streamPromise2 = new FuturePromise<>();
CountDownLatch latch2 = new CountDownLatch(1);
clientSession.newStream(new HeadersFrame(request2, null, false), streamPromise2, new Stream.Listener.Adapter()
clientSession.newStream(new HeadersFrame(request2, null, false), streamPromise2, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -244,14 +250,17 @@ public class RawHTTP2ProxyTest
LOGGER.debug("CLIENT received {}", frame);
if (frame.isEndStream())
latch2.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
if (LOGGER.isDebugEnabled())
LOGGER.debug("CLIENT received {}", frame);
callback.succeeded();
LOGGER.debug("CLIENT received {}", data.frame());
data.release();
stream.demand();
}
});
Stream stream2 = streamPromise2.get(5, TimeUnit.SECONDS);
@ -261,7 +270,7 @@ public class RawHTTP2ProxyTest
assertTrue(latch2.await(5, TimeUnit.SECONDS));
}
private static class ClientToProxySessionListener extends ServerSessionListener.Adapter
private static class ClientToProxySessionListener implements ServerSessionListener
{
private final Map<Integer, ClientToProxyToServer> forwarders = new ConcurrentHashMap<>();
private final HTTP2Client client;
@ -282,15 +291,17 @@ public class RawHTTP2ProxyTest
int port = Integer.parseInt(fields.get("X-Target"));
ClientToProxyToServer clientToProxyToServer = forwarders.computeIfAbsent(port, p -> new ClientToProxyToServer("localhost", p, client));
clientToProxyToServer.offer(stream, frame, Callback.NOOP);
stream.demand();
return clientToProxyToServer;
}
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Received {} on {}", frame, session);
// TODO
callback.succeeded();
}
@Override
@ -303,11 +314,12 @@ public class RawHTTP2ProxyTest
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Failure on " + session, failure);
// TODO
callback.succeeded();
}
}
@ -478,6 +490,7 @@ public class RawHTTP2ProxyTest
if (LOGGER.isDebugEnabled())
LOGGER.debug("CPS received {} on {}", frame, stream);
offer(stream, frame, NOOP);
stream.demand();
}
@Override
@ -488,19 +501,22 @@ public class RawHTTP2ProxyTest
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
if (LOGGER.isDebugEnabled())
LOGGER.debug("CPS received {} on {}", frame, stream);
offer(stream, frame, callback);
LOGGER.debug("CPS read {} on {}", data, stream);
offer(stream, data.frame(), Callback.from(data::release));
stream.demand();
}
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("CPS received {} on {}", frame, stream);
// TODO: drain the queue for that stream, and notify server.
callback.succeeded();
}
@Override
@ -513,14 +529,15 @@ public class RawHTTP2ProxyTest
}
}
private static class ServerToProxySessionListener extends Session.Listener.Adapter
private static class ServerToProxySessionListener implements Session.Listener
{
@Override
public void onClose(Session session, GoAwayFrame frame)
public void onClose(Session session, GoAwayFrame frame, Callback callback)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Received {} on {}", frame, session);
// TODO
callback.succeeded();
}
@Override
@ -533,11 +550,12 @@ public class RawHTTP2ProxyTest
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Failure on " + session, failure);
// TODO
callback.succeeded();
}
}
@ -632,6 +650,7 @@ public class RawHTTP2ProxyTest
if (LOGGER.isDebugEnabled())
LOGGER.debug("SPC received {} on {}", frame, stream);
offer(stream, frame, NOOP);
stream.demand();
}
@Override
@ -644,19 +663,22 @@ public class RawHTTP2ProxyTest
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
if (LOGGER.isDebugEnabled())
LOGGER.debug("SPC received {} on {}", frame, stream);
offer(stream, frame, callback);
LOGGER.debug("SPC read {} on {}", data, stream);
offer(stream, data.frame(), Callback.from(data::release));
stream.demand();
}
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("SPC received {} on {}", frame, stream);
// TODO: drain queue, reset client stream.
callback.succeeded();
}
@Override
@ -665,7 +687,7 @@ public class RawHTTP2ProxyTest
if (LOGGER.isDebugEnabled())
LOGGER.debug("SPC idle timeout for {}", stream);
// TODO:
return false;
return true;
}
private void link(Stream proxyToServerStream, Stream clientToProxyStream)

View File

@ -28,7 +28,6 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Test;
@ -54,7 +53,7 @@ public class RequestTrailersTest extends AbstractTest
private void testEmptyTrailers(String content) throws Exception
{
CountDownLatch trailersLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -62,7 +61,7 @@ public class RequestTrailersTest extends AbstractTest
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true);
stream.headers(responseFrame, Callback.NOOP);
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -89,20 +88,23 @@ public class RequestTrailersTest extends AbstractTest
@Test
public void testEmptyTrailersWithAsyncContent() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame dataFrame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
stream.demand();
// We should not receive an empty HEADERS frame for the
// trailers, but instead a DATA frame with endStream=true.
if (dataFrame.isEndStream())
if (data.frame().isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true);
@ -138,20 +140,22 @@ public class RequestTrailersTest extends AbstractTest
@Test
public void testEmptyTrailersWithEmptyAsyncContent() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame dataFrame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
// We should not receive an empty HEADERS frame for the
// trailers, but instead a DATA frame with endStream=true.
if (dataFrame.isEndStream())
if (data.frame().isEndStream())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true);

View File

@ -29,7 +29,6 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
@ -81,7 +80,7 @@ public class ResponseTrailerTest extends AbstractTest
int port = connector.getLocalPort();
InetSocketAddress address = new InetSocketAddress(host, port);
FuturePromise<Session> sessionPromise = new FuturePromise<>();
http2Client.connect(address, new Session.Listener.Adapter(), sessionPromise);
http2Client.connect(address, new Session.Listener() {}, sessionPromise);
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
HttpURI uri = HttpURI.from("http://" + host + ":" + port + "/");
@ -89,7 +88,7 @@ public class ResponseTrailerTest extends AbstractTest
HeadersFrame frame = new HeadersFrame(request, null, true);
BlockingQueue<HeadersFrame> headers = new LinkedBlockingQueue<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -97,13 +96,15 @@ public class ResponseTrailerTest extends AbstractTest
headers.offer(frame);
if (frame.isEndStream())
latch.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
super.onData(stream, frame, callback);
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
latch.countDown();
}
});

View File

@ -39,12 +39,13 @@ public class SessionFailureTest extends AbstractTest
public void testWrongPreface() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
latch.countDown();
callback.succeeded();
}
});
@ -74,7 +75,7 @@ public class SessionFailureTest extends AbstractTest
{
final CountDownLatch writeLatch = new CountDownLatch(1);
final CountDownLatch serverFailureLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -94,19 +95,21 @@ public class SessionFailureTest extends AbstractTest
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
serverFailureLatch.countDown();
callback.succeeded();
}
});
final CountDownLatch clientFailureLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
clientFailureLatch.countDown();
callback.succeeded();
}
});
HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true);

View File

@ -70,7 +70,7 @@ public class SmallThreadPoolLoadTest extends AbstractTest
start(new LoadHandler());
// Only one connection to the server.
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
int runs = 10;
int iterations = 512;
@ -143,30 +143,34 @@ public class SmallThreadPoolLoadTest extends AbstractTest
HeadersFrame requestFrame = new HeadersFrame(request, null, download);
FuturePromise<Stream> promise = new FuturePromise<>();
CountDownLatch requestLatch = new CountDownLatch(1);
CountDownLatch responseLatch = new CountDownLatch(1);
AtomicBoolean reset = new AtomicBoolean();
session.newStream(requestFrame, promise, new Stream.Listener.Adapter()
session.newStream(requestFrame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (frame.isEndStream())
requestLatch.countDown();
responseLatch.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
requestLatch.countDown();
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
responseLatch.countDown();
}
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
reset.set(true);
requestLatch.countDown();
responseLatch.countDown();
callback.succeeded();
}
});
if (!download)
@ -175,7 +179,7 @@ public class SmallThreadPoolLoadTest extends AbstractTest
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(contentLength), true), Callback.NOOP);
}
boolean success = requestLatch.await(5, TimeUnit.SECONDS);
boolean success = responseLatch.await(5, TimeUnit.SECONDS);
if (success)
latch.countDown();
else

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.http2.tests;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -47,19 +48,19 @@ public class StreamCloseTest extends AbstractTest
@Test
public void testRequestClosedRemotelyClosesStream() throws Exception
{
final CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
assertTrue(((HTTP2Stream)stream).isRemotelyClosed());
assertTrue(stream.isRemotelyClosed());
latch.countDown();
return null;
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, null);
@ -71,11 +72,11 @@ public class StreamCloseTest extends AbstractTest
@Test
public void testRequestClosedResponseClosedClosesStream() throws Exception
{
final CountDownLatch latch = new CountDownLatch(2);
start(new ServerSessionListener.Adapter()
CountDownLatch latch = new CountDownLatch(2);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(final Stream stream, HeadersFrame frame)
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
HeadersFrame response = new HeadersFrame(stream.getId(), metaData, null, true);
@ -93,10 +94,10 @@ public class StreamCloseTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter()
session.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -113,31 +114,33 @@ public class StreamCloseTest extends AbstractTest
@Test
public void testRequestDataClosedResponseDataClosedClosesStream() throws Exception
{
final CountDownLatch serverDataLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch serverDataLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
HeadersFrame response = new HeadersFrame(stream.getId(), metaData, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(response, completable);
return new Stream.Listener.Adapter()
CompletableFuture<Stream> completable = stream.headers(response);
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(final Stream stream, DataFrame frame, final Callback callback)
public void onDataAvailable(Stream stream)
{
assertTrue(((HTTP2Stream)stream).isRemotelyClosed());
Stream.Data data = stream.readData();
completable.thenRun(() -> stream.data(frame, new Callback()
assertTrue(stream.isRemotelyClosed());
completable.thenRun(() -> stream.data(data.frame(), new Callback()
{
@Override
public void succeeded()
{
assertTrue(stream.isClosed());
assertEquals(0, stream.getSession().getStreams().size());
callback.succeeded();
data.release();
serverDataLatch.countDown();
}
}));
@ -146,25 +149,26 @@ public class StreamCloseTest extends AbstractTest
}
});
final CountDownLatch completeLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter());
CountDownLatch completeLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener() {});
HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter()
session.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
Stream.Data data = stream.readData();
// The sent data callback may not be notified yet here.
callback.succeeded();
data.release();
completeLatch.countDown();
}
});
final Stream stream = promise.get(5, TimeUnit.SECONDS);
Stream stream = promise.get(5, TimeUnit.SECONDS);
assertFalse(stream.isClosed());
assertFalse(((HTTP2Stream)stream).isLocallyClosed());
final CountDownLatch clientDataLatch = new CountDownLatch(1);
CountDownLatch clientDataLatch = new CountDownLatch(1);
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(new byte[512]), true), new Callback()
{
@Override
@ -185,8 +189,8 @@ public class StreamCloseTest extends AbstractTest
@Test
public void testPushedStreamIsClosed() throws Exception
{
final CountDownLatch serverLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch serverLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -195,10 +199,10 @@ public class StreamCloseTest extends AbstractTest
stream.push(pushFrame, new Promise.Adapter<>()
{
@Override
public void succeeded(final Stream pushedStream)
public void succeeded(Stream pushedStream)
{
// When created, pushed stream must be implicitly remotely closed.
assertTrue(((HTTP2Stream)pushedStream).isRemotelyClosed());
assertTrue(pushedStream.isRemotelyClosed());
// Send some data with endStream = true.
pushedStream.data(new DataFrame(pushedStream.getId(), ByteBuffer.allocate(16), true), new Callback()
{
@ -210,29 +214,31 @@ public class StreamCloseTest extends AbstractTest
}
});
}
}, new Stream.Listener.Adapter());
}, null);
HeadersFrame response = new HeadersFrame(stream.getId(), new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY), null, true);
stream.headers(response, Callback.NOOP);
return null;
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true);
final CountDownLatch clientLatch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch clientLatch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
{
assertTrue(((HTTP2Stream)pushedStream).isLocallyClosed());
return new Adapter()
pushedStream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream pushedStream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream pushedStream)
{
Stream.Data data = pushedStream.readData();
assertTrue(pushedStream.isClosed());
callback.succeeded();
data.release();
clientLatch.countDown();
}
};
@ -246,36 +252,37 @@ public class StreamCloseTest extends AbstractTest
@Test
public void testPushedStreamResetIsClosed() throws Exception
{
final CountDownLatch serverLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
CountDownLatch serverLatch = new CountDownLatch(1);
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(final Stream stream, HeadersFrame frame)
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
PushPromiseFrame pushFrame = new PushPromiseFrame(stream.getId(), 0, newRequest("GET", HttpFields.EMPTY));
stream.push(pushFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
stream.push(pushFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onReset(Stream pushedStream, ResetFrame frame)
public void onReset(Stream pushedStream, ResetFrame frame, Callback callback)
{
assertTrue(pushedStream.isReset());
assertTrue(pushedStream.isClosed());
HeadersFrame response = new HeadersFrame(stream.getId(), new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY), null, true);
stream.headers(response, Callback.NOOP);
serverLatch.countDown();
callback.succeeded();
}
});
return null;
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HeadersFrame frame = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true);
final CountDownLatch clientLatch = new CountDownLatch(2);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
CountDownLatch clientLatch = new CountDownLatch(2);
session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public Stream.Listener onPush(final Stream pushedStream, PushPromiseFrame frame)
public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
{
pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), new Callback()
{
@ -305,9 +312,9 @@ public class StreamCloseTest extends AbstractTest
public void testFailedSessionClosesIdleStream() throws Exception
{
AtomicReference<Session> sessionRef = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
final List<Stream> streams = new ArrayList<>();
start(new ServerSessionListener.Adapter()
CountDownLatch latch = new CountDownLatch(1);
List<Stream> streams = new ArrayList<>();
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -326,22 +333,23 @@ public class StreamCloseTest extends AbstractTest
}
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
sessionRef.set(session);
latch.countDown();
callback.succeeded();
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
// First stream will be idle on server.
HeadersFrame request1 = new HeadersFrame(newRequest("HEAD", HttpFields.EMPTY), null, true);
session.newStream(request1, new Promise.Adapter<>(), new Stream.Listener.Adapter());
session.newStream(request1, new Promise.Adapter<>(), null);
// Second stream will fail on server.
HeadersFrame request2 = new HeadersFrame(newRequest("GET", HttpFields.EMPTY), null, true);
session.newStream(request2, new Promise.Adapter<>(), new Stream.Listener.Adapter());
session.newStream(request2, new Promise.Adapter<>(), null);
assertTrue(latch.await(5, TimeUnit.SECONDS));
Session serverSession = sessionRef.get();

View File

@ -48,7 +48,7 @@ public class StreamCountTest extends AbstractTest
@Test
public void testServerAllowsOneStreamEnforcedByClient() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
@ -61,19 +61,18 @@ public class StreamCountTest extends AbstractTest
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback);
}
else
{
callback.succeeded();
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), Callback.NOOP);
}
}
};
@ -81,7 +80,7 @@ public class StreamCountTest extends AbstractTest
});
CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
@ -96,7 +95,7 @@ public class StreamCountTest extends AbstractTest
HeadersFrame frame1 = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise1 = new FuturePromise<>();
CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(frame1, streamPromise1, new Stream.Listener.Adapter()
session.newStream(frame1, streamPromise1, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -109,7 +108,7 @@ public class StreamCountTest extends AbstractTest
HeadersFrame frame2 = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise2 = new FuturePromise<>();
session.newStream(frame2, streamPromise2, new Stream.Listener.Adapter());
session.newStream(frame2, streamPromise2, null);
assertThrows(ExecutionException.class,
() -> streamPromise2.get(5, TimeUnit.SECONDS));
@ -121,27 +120,25 @@ public class StreamCountTest extends AbstractTest
@Test
public void testServerAllowsOneStreamEnforcedByServer() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
HTTP2Session session = (HTTP2Session)stream.getSession();
session.setMaxRemoteStreams(1);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
if (data.frame().isEndStream())
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback);
}
else
{
callback.succeeded();
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), Callback.NOOP);
}
}
};
@ -149,7 +146,7 @@ public class StreamCountTest extends AbstractTest
});
CountDownLatch sessionResetLatch = new CountDownLatch(2);
Session session = newClientSession(new Session.Listener.Adapter()
Session session = newClientSession(new Session.Listener()
{
@Override
public void onReset(Session session, ResetFrame frame)
@ -162,7 +159,7 @@ public class StreamCountTest extends AbstractTest
HeadersFrame frame1 = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise1 = new FuturePromise<>();
CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(frame1, streamPromise1, new Stream.Listener.Adapter()
session.newStream(frame1, streamPromise1, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -177,12 +174,13 @@ public class StreamCountTest extends AbstractTest
HeadersFrame frame2 = new HeadersFrame(metaData, null, false);
FuturePromise<Stream> streamPromise2 = new FuturePromise<>();
AtomicReference<CountDownLatch> resetLatch = new AtomicReference<>(new CountDownLatch(1));
session.newStream(frame2, streamPromise2, new Stream.Listener.Adapter()
session.newStream(frame2, streamPromise2, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
resetLatch.get().countDown();
callback.succeeded();
}
});

View File

@ -45,8 +45,6 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
@ -60,6 +58,7 @@ import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.internal.ErrorCode;
import org.eclipse.jetty.http2.internal.HTTP2Flusher;
import org.eclipse.jetty.http2.internal.HTTP2Session;
import org.eclipse.jetty.http2.internal.HTTP2Stream;
import org.eclipse.jetty.http2.internal.generator.Generator;
import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
@ -96,13 +95,13 @@ public class StreamResetTest extends AbstractTest
@Test
public void testStreamSendingResetIsRemoved() throws Exception
{
start(new ServerSessionListener.Adapter());
start(new ServerSessionListener() {});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(requestFrame, promise, new Stream.Listener.Adapter());
client.newStream(requestFrame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code);
FutureCallback resetCallback = new FutureCallback();
@ -117,30 +116,31 @@ public class StreamResetTest extends AbstractTest
{
final AtomicReference<Stream> streamRef = new AtomicReference<>();
final CountDownLatch resetLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
assertNotNull(stream);
assertTrue(stream.isReset());
streamRef.set(stream);
resetLatch.countDown();
callback.succeeded();
}
};
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(requestFrame, promise, new Stream.Listener.Adapter());
client.newStream(requestFrame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code);
stream.reset(resetFrame, Callback.NOOP);
@ -160,7 +160,7 @@ public class StreamResetTest extends AbstractTest
{
final CountDownLatch serverResetLatch = new CountDownLatch(1);
final CountDownLatch serverDataLatch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
@ -169,12 +169,14 @@ public class StreamResetTest extends AbstractTest
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(responseFrame, completable);
return new Stream.Listener.Adapter()
stream.demand();
return new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
completable.thenRun(() ->
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), true), new Callback()
{
@ -187,10 +189,10 @@ public class StreamResetTest extends AbstractTest
}
@Override
public void onReset(Stream s, ResetFrame frame)
public void onReset(Stream s, ResetFrame frame, Callback callback)
{
// Simulate that there is pending data to send.
IStream stream = (IStream)s;
HTTP2Stream stream = (HTTP2Stream)s;
List<Frame> frames = List.of(new DataFrame(s.getId(), ByteBuffer.allocate(16), true));
stream.getSession().frames(stream, frames, new Callback()
{
@ -200,29 +202,32 @@ public class StreamResetTest extends AbstractTest
serverResetLatch.countDown();
}
});
callback.succeeded();
}
};
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request1 = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame1 = new HeadersFrame(request1, null, false);
FuturePromise<Stream> promise1 = new FuturePromise<>();
final CountDownLatch stream1HeadersLatch = new CountDownLatch(1);
final CountDownLatch stream1DataLatch = new CountDownLatch(1);
client.newStream(requestFrame1, promise1, new Stream.Listener.Adapter()
client.newStream(requestFrame1, promise1, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
stream1HeadersLatch.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
stream1DataLatch.countDown();
}
});
@ -233,12 +238,13 @@ public class StreamResetTest extends AbstractTest
HeadersFrame requestFrame2 = new HeadersFrame(request2, null, false);
FuturePromise<Stream> promise2 = new FuturePromise<>();
final CountDownLatch stream2DataLatch = new CountDownLatch(1);
client.newStream(requestFrame2, promise2, new Stream.Listener.Adapter()
client.newStream(requestFrame2, promise2, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
Stream.Data data = stream.readData();
data.release();
stream2DataLatch.countDown();
}
});
@ -311,10 +317,10 @@ public class StreamResetTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, true);
client.newStream(frame, new FuturePromise<>(), new Stream.Listener.Adapter()
client.newStream(frame, new FuturePromise<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -398,10 +404,10 @@ public class StreamResetTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, true);
client.newStream(frame, new FuturePromise<>(), new Stream.Listener.Adapter()
client.newStream(frame, new FuturePromise<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -438,17 +444,17 @@ public class StreamResetTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter());
client.newStream(frame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
stream.data(new DataFrame(stream.getId(), data, false), Callback.from(dataLatch::countDown));
// The server does not read the data, so the flow control window should be zero.
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
assertEquals(0, ((ISession)client).updateSendWindow(0));
assertEquals(0, ((HTTP2Session)client).updateSendWindow(0));
// Now reset the stream.
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
@ -457,7 +463,7 @@ public class StreamResetTest extends AbstractTest
// it, and for the client to process the window updates.
Thread.sleep(1000);
assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0));
assertThat(((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0));
}
@Test
@ -500,7 +506,7 @@ public class StreamResetTest extends AbstractTest
prepareClient();
httpClient.start();
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
// Send requests until one is queued on the server but not dispatched.
AtomicReference<CountDownLatch> latch = new AtomicReference<>();
@ -514,7 +520,7 @@ public class StreamResetTest extends AbstractTest
MetaData.Request request = newRequest("GET", "/" + count, HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
client.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -522,13 +528,16 @@ public class StreamResetTest extends AbstractTest
MetaData.Response response = (MetaData.Response)frame.getMetaData();
if (response.getStatus() == HttpStatus.OK_200)
latch.get().countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
callback.succeeded();
if (frame.isEndStream())
Stream.Data data = stream.readData();
data.release();
stream.demand();
if (data.frame().isEndStream())
latch.get().countDown();
}
});
@ -547,9 +556,9 @@ public class StreamResetTest extends AbstractTest
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
// This request will get no event from the server since it's reset by the client.
client.newStream(frame, promise, new Stream.Listener.Adapter());
client.newStream(frame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(((ISession)client).updateSendWindow(0));
ByteBuffer data = ByteBuffer.allocate(((HTTP2Session)client).updateSendWindow(0));
stream.data(new DataFrame(stream.getId(), data, false), new Callback()
{
@Override
@ -560,7 +569,7 @@ public class StreamResetTest extends AbstractTest
});
// Wait for WINDOW_UPDATEs to be processed by the client.
await().atMost(1000, TimeUnit.SECONDS).until(() -> ((ISession)client).updateSendWindow(0), Matchers.greaterThan(0));
await().atMost(1000, TimeUnit.SECONDS).until(() -> ((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0));
latch.set(new CountDownLatch(2 * streams.size()));
// Notify all blocked threads to wakeup.
@ -591,12 +600,12 @@ public class StreamResetTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter());
client.newStream(frame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
CountDownLatch dataLatch = new CountDownLatch(1);
@ -610,13 +619,13 @@ public class StreamResetTest extends AbstractTest
});
// The server does not read the data, so the flow control window should be zero.
assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
assertEquals(0, ((ISession)client).updateSendWindow(0));
assertEquals(0, ((HTTP2Session)client).updateSendWindow(0));
// Wait for the server process the exception, and
// for the client to process the window updates.
Thread.sleep(2000);
assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0));
assertThat(((HTTP2Session)client).updateSendWindow(0), Matchers.greaterThan(0));
}
}
@ -650,21 +659,23 @@ public class StreamResetTest extends AbstractTest
}
});
Deque<Callback> dataQueue = new ArrayDeque<>();
Deque<Stream.Data> dataQueue = new ArrayDeque<>();
AtomicLong received = new AtomicLong();
CountDownLatch latch = new CountDownLatch(1);
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
client.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
dataQueue.offer(callback);
Stream.Data data = stream.readData();
dataQueue.offer(data);
// Do not consume the data yet.
if (received.addAndGet(frame.getData().remaining()) == windowSize)
stream.demand();
if (received.addAndGet(data.frame().getData().remaining()) == windowSize)
latch.countDown();
}
});
@ -673,7 +684,7 @@ public class StreamResetTest extends AbstractTest
// Reset and consume.
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
dataQueue.forEach(Callback::succeeded);
dataQueue.forEach(Stream.Data::release);
assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
}
@ -701,17 +712,21 @@ public class StreamResetTest extends AbstractTest
AtomicLong received = new AtomicLong();
CountDownLatch latch = new CountDownLatch(1);
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
client.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
if (received.addAndGet(frame.getData().remaining()) == windowSize)
Stream.Data data = stream.readData();
// Do not release to stall the flow control window.
if (received.addAndGet(data.frame().getData().remaining()) == windowSize)
latch.countDown();
else
stream.demand();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
@ -750,21 +765,23 @@ public class StreamResetTest extends AbstractTest
}
});
Deque<Callback> dataQueue = new ArrayDeque<>();
Deque<Stream.Data> dataQueue = new ArrayDeque<>();
AtomicLong received = new AtomicLong();
CountDownLatch latch = new CountDownLatch(1);
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter()
client.newStream(frame, promise, new Stream.Listener()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
dataQueue.offer(callback);
Stream.Data data = stream.readData();
dataQueue.offer(data);
// Do not consume the data yet.
if (received.addAndGet(frame.getData().remaining()) == windowSize)
stream.demand();
if (received.addAndGet(data.frame().getData().remaining()) == windowSize)
latch.countDown();
}
});
@ -773,7 +790,7 @@ public class StreamResetTest extends AbstractTest
// Reset and consume.
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
dataQueue.forEach(Callback::succeeded);
dataQueue.forEach(Stream.Data::release);
assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
}
@ -804,12 +821,12 @@ public class StreamResetTest extends AbstractTest
}
});
Session client = newClientSession(new Session.Listener.Adapter());
Session client = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame frame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(frame, promise, new Stream.Listener.Adapter());
client.newStream(frame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer content = ByteBuffer.wrap(new byte[1024]);
stream.data(new DataFrame(stream.getId(), content, true), Callback.NOOP);
@ -1016,16 +1033,16 @@ public class StreamResetTest extends AbstractTest
http2.setFlowControlStrategyFactory(() -> new BufferingFlowControlStrategy(ratio)
{
@Override
protected void sendWindowUpdate(IStream stream, ISession session, WindowUpdateFrame frame)
protected void sendWindowUpdate(Session session, Stream stream, List<WindowUpdateFrame> frames)
{
// Before sending the window update, reset from the client side.
if (stream != null)
streamRef.get().reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
super.sendWindowUpdate(stream, session, frame);
super.sendWindowUpdate(session, stream, frames);
}
});
};
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -1034,24 +1051,24 @@ public class StreamResetTest extends AbstractTest
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
Callback.Completable completable = new Callback.Completable();
stream.headers(responseFrame, completable);
// Consume the request content as it arrives.
return new Stream.Listener.Adapter();
return null;
}
}, http2Factory);
CountDownLatch failureLatch = new CountDownLatch(1);
Session client = newClientSession(new Session.Listener.Adapter()
Session client = newClientSession(new Session.Listener()
{
@Override
public void onFailure(Session session, Throwable failure)
public void onFailure(Session session, Throwable failure, Callback callback)
{
failureLatch.countDown();
callback.succeeded();
}
});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(requestFrame, promise, new Stream.Listener.Adapter());
client.newStream(requestFrame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
streamRef.set(stream);
// Send enough bytes to trigger the server to send a window update.

View File

@ -60,7 +60,7 @@ public class TrailersTest extends AbstractTest
public void testTrailersSentByClient() throws Exception
{
CountDownLatch latch = new CountDownLatch(1);
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -68,7 +68,7 @@ public class TrailersTest extends AbstractTest
MetaData.Request request = (MetaData.Request)frame.getMetaData();
assertFalse(frame.isEndStream());
assertTrue(request.getFields().contains("X-Request"));
return new Stream.Listener.Adapter()
return new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -82,14 +82,14 @@ public class TrailersTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HttpFields.Mutable requestFields = HttpFields.build();
requestFields.put("X-Request", "true");
MetaData.Request request = newRequest("GET", requestFields);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
session.newStream(requestFrame, streamPromise, null);
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
// Send the trailers.
@ -151,7 +151,7 @@ public class TrailersTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
HttpFields.Mutable requestFields = HttpFields.build();
requestFields.put("X-Request", "true");
@ -159,7 +159,7 @@ public class TrailersTest extends AbstractTest
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()
session.newStream(requestFrame, streamPromise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -194,7 +194,7 @@ public class TrailersTest extends AbstractTest
@Test
public void testTrailersSentByServer() throws Exception
{
start(new ServerSessionListener.Adapter()
start(new ServerSessionListener()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@ -219,11 +219,11 @@ public class TrailersTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, true);
CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener()
{
private boolean responded;
@ -269,12 +269,12 @@ public class TrailersTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("GET", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, true);
CountDownLatch latch = new CountDownLatch(1);
List<Frame> frames = new ArrayList<>();
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
session.newStream(requestFrame, new Promise.Adapter<>(), new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
@ -282,13 +282,15 @@ public class TrailersTest extends AbstractTest
frames.add(frame);
if (frame.isEndStream())
latch.countDown();
stream.demand();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
public void onDataAvailable(Stream stream)
{
frames.add(frame);
callback.succeeded();
Stream.Data data = stream.readData();
frames.add(data.frame());
data.release();
}
});
@ -318,11 +320,11 @@ public class TrailersTest extends AbstractTest
}
});
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("POST", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(requestFrame, promise, new Stream.Listener.Adapter());
session.newStream(requestFrame, promise, null);
Stream stream = promise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.wrap(StringUtil.getUtf8Bytes("hello"));
Callback.Completable completable = new Callback.Completable();
@ -362,16 +364,17 @@ public class TrailersTest extends AbstractTest
});
CountDownLatch clientLatch = new CountDownLatch(1);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("POST", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(requestFrame, promise, new Stream.Listener.Adapter()
session.newStream(requestFrame, promise, new Stream.Listener()
{
@Override
public void onReset(Stream stream, ResetFrame frame)
public void onReset(Stream stream, ResetFrame frame, Callback callback)
{
clientLatch.countDown();
callback.succeeded();
}
});
Stream stream = promise.get(5, TimeUnit.SECONDS);
@ -409,11 +412,11 @@ public class TrailersTest extends AbstractTest
AtomicReference<MetaData.Response> responseRef = new AtomicReference<>();
AtomicReference<MetaData> trailersRef = new AtomicReference<>();
CountDownLatch clientLatch = new CountDownLatch(2);
Session session = newClientSession(new Session.Listener.Adapter());
Session session = newClientSession(new Session.Listener() {});
MetaData.Request request = newRequest("POST", HttpFields.EMPTY);
HeadersFrame requestFrame = new HeadersFrame(request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(requestFrame, promise, new Stream.Listener.Adapter()
session.newStream(requestFrame, promise, new Stream.Listener()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)

View File

@ -1,3 +1,4 @@
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.http2.LEVEL=DEBUG
org.eclipse.jetty.http2.hpack.LEVEL=INFO

View File

@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.frames.DataFrame;
import org.eclipse.jetty.http3.frames.HeadersFrame;
import org.eclipse.jetty.io.Retainable;
/**
* <p>A {@link Stream} represents a bidirectional exchange of data within a {@link Session}.</p>
@ -72,7 +73,7 @@ public interface Stream
* </ul>
* <p>When the returned {@link Stream.Data} object is not {@code null},
* applications <em>must</em> call, either immediately or later (possibly
* asynchronously) {@link Stream.Data#complete()} to notify the
* asynchronously) {@link Stream.Data#release()} to notify the
* implementation that the bytes have been processed.</p>
* <p>{@link Stream.Data} objects may be stored away for later, asynchronous,
* processing (for example, to process them only when all of them have been
@ -190,7 +191,7 @@ public interface Stream
* // Process the content.
* process(data.getByteBuffer());
* // Notify that the content has been consumed.
* data.complete();
* data.release();
* if (!data.isLast())
* {
* // Demand to be called back.
@ -305,7 +306,7 @@ public interface Stream
* // Process the content.
* process(data.getByteBuffer());
* // Notify that the content has been consumed.
* data.complete();
* data.release();
* if (!data.isLast())
* {
* // Demand to be called back.
@ -362,27 +363,21 @@ public interface Stream
/**
* <p>A {@link Stream.Data} instance associates a {@link ByteBuffer}
* containing request bytes or response bytes with a completion event
* that applications <em>must</em> trigger when the bytes have been
* processed.</p>
* containing request bytes or response bytes.</p>
*
* @see Stream#readData()
*/
public static class Data
public abstract static class Data implements Retainable
{
private final DataFrame frame;
private final Runnable complete;
public Data(DataFrame frame, Runnable complete)
public Data(DataFrame frame)
{
this.frame = Objects.requireNonNull(frame);
this.complete = Objects.requireNonNull(complete);
}
/**
* @return the {@link ByteBuffer} containing the data bytes
*
* @see #complete()
*/
public ByteBuffer getByteBuffer()
{
@ -398,17 +393,6 @@ public interface Stream
return frame.isLast();
}
/**
* <p>The method that applications <em>must</em> invoke to
* signal that the data bytes have been processed.</p>
*
* @see #getByteBuffer()
*/
public void complete()
{
complete.run();
}
@Override
public String toString()
{

View File

@ -209,7 +209,7 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
// Release the network buffer here (if empty), since the application may
// not be reading more bytes, to avoid to keep around a consumed buffer.
tryReleaseBuffer(false);
return new Stream.Data(frame, () -> completeReadData(current));
return new StreamData(frame, current);
}
else
{
@ -251,13 +251,6 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
}
}
private void completeReadData(RetainableByteBuffer buffer)
{
buffer.release();
if (LOG.isDebugEnabled())
LOG.debug("released retained {}", buffer);
}
public void demand()
{
boolean hasData;
@ -452,6 +445,29 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
return String.format("%s[demand=%b,stalled=%b,parserDataMode=%b]", super.toConnectionString(), hasDemand(), isStalled(), parserDataMode);
}
private static class StreamData extends Stream.Data
{
private final RetainableByteBuffer retainable;
public StreamData(DataFrame frame, RetainableByteBuffer retainable)
{
super(frame);
this.retainable = retainable;
}
@Override
public void retain()
{
retainable.retain();
}
@Override
public boolean release()
{
return retainable.release();
}
}
private class MessageListener extends ParserListener.Wrapper
{
private MessageListener(ParserListener listener)

View File

@ -13,8 +13,8 @@
package org.eclipse.jetty.http3.client.http.internal;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
@ -34,7 +34,8 @@ import org.slf4j.LoggerFactory;
public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client.Listener
{
private static final Logger LOG = LoggerFactory.getLogger(HttpReceiverOverHTTP3.class);
private boolean notifySuccess;
private final AtomicBoolean notifySuccess = new AtomicBoolean();
protected HttpReceiverOverHTTP3(HttpChannelOverHTTP3 channel)
{
@ -58,7 +59,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client
if (exchange == null)
return;
if (notifySuccess)
if (notifySuccess.get())
responseSuccess(exchange);
else
getHttpChannel().getStream().demand();
@ -86,6 +87,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client
// TODO: add support for HttpMethod.CONNECT.
notifySuccess.set(frame.isLast());
if (responseHeaders(exchange))
{
int status = response.getStatus();
@ -98,7 +100,6 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client
{
if (LOG.isDebugEnabled())
LOG.debug("stalling response processing, no demand after headers on {}", this);
notifySuccess = frame.isLast();
}
}
}
@ -110,55 +111,45 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client
if (exchange == null)
return;
try
Stream.Data data = stream.readData();
if (data != null)
{
Stream.Data data = stream.readData();
if (data != null)
ByteBuffer byteBuffer = data.getByteBuffer();
if (byteBuffer.hasRemaining())
{
ByteBuffer byteBuffer = data.getByteBuffer();
if (byteBuffer.hasRemaining())
notifySuccess.set(data.isLast());
Callback callback = Callback.from(Invocable.InvocationType.NON_BLOCKING, data::release, x ->
{
Callback callback = Callback.from(Invocable.InvocationType.NON_BLOCKING, data::complete, x ->
{
data.complete();
if (responseFailure(x))
stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x);
});
boolean proceed = responseContent(exchange, byteBuffer, callback);
if (proceed)
{
if (data.isLast())
responseSuccess(exchange);
else
stream.demand();
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("stalling response processing, no demand after {} on {}", data, this);
notifySuccess = data.isLast();
}
}
else
data.release();
if (responseFailure(x))
stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x);
});
boolean proceed = responseContent(exchange, byteBuffer, callback);
if (proceed)
{
data.complete();
if (data.isLast())
responseSuccess(exchange);
else
stream.demand();
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("stalling response processing, no demand after {} on {}", data, this);
}
}
else
{
stream.demand();
data.release();
if (data.isLast())
responseSuccess(exchange);
else
stream.demand();
}
}
catch (Throwable x)
else
{
Throwable failure = x;
if (x instanceof UncheckedIOException)
failure = x.getCause();
exchange.getRequest().abort(failure);
stream.demand();
}
}

View File

@ -143,9 +143,12 @@ public class HttpStreamOverHTTP3 implements HttpStream
if (data == null)
return null;
chunk = createChunk(data);
data.release();
try (AutoLock ignored = lock.lock())
{
this.chunk = newChunk(data);
this.chunk = chunk;
}
}
}
@ -186,10 +189,14 @@ public class HttpStreamOverHTTP3 implements HttpStream
return null;
}
Content.Chunk chunk = createChunk(data);
data.release();
try (AutoLock ignored = lock.lock())
{
chunk = newChunk(data);
this.chunk = chunk;
}
return httpChannel.onContentAvailable();
}
@ -209,9 +216,11 @@ public class HttpStreamOverHTTP3 implements HttpStream
return httpChannel.onContentAvailable();
}
private Content.Chunk newChunk(Stream.Data data)
private Content.Chunk createChunk(Stream.Data data)
{
return Content.Chunk.from(data.getByteBuffer(), data.isLast(), data::complete);
// As we are passing the ByteBuffer to the Chunk we need to retain.
data.retain();
return Content.Chunk.from(data.getByteBuffer(), data.isLast(), data);
}
@Override

View File

@ -224,7 +224,7 @@ public class ClientServerTest extends AbstractClientServerTest
return;
}
// Recycle the ByteBuffer in data.frame.
data.complete();
data.release();
// Call me again immediately.
stream.demand();
if (data.isLast())
@ -294,8 +294,8 @@ public class ClientServerTest extends AbstractClientServerTest
}
// Echo it back, then demand only when the write is finished.
stream.data(new DataFrame(data.getByteBuffer(), data.isLast()))
// Always complete.
.whenComplete((s, x) -> data.complete())
// Always release.
.whenComplete((s, x) -> data.release())
// Demand only if successful.
.thenRun(stream::demand);
}
@ -330,7 +330,7 @@ public class ClientServerTest extends AbstractClientServerTest
{
// Consume data.
byteBuffer.put(data.getByteBuffer());
data.complete();
data.release();
if (data.isLast())
clientDataLatch.countDown();
}

View File

@ -462,7 +462,7 @@ public class DataDemandTest extends AbstractClientServerTest
if (data != null)
{
// Consume the data.
data.complete();
data.release();
if (data.isLast())
{
dataLatch.countDown();
@ -558,7 +558,7 @@ public class DataDemandTest extends AbstractClientServerTest
}
else
{
data.complete();
data.release();
if (data.isLast())
lastDataLatch.countDown();
else

View File

@ -76,7 +76,7 @@ public class ExternalServerTest
System.err.println("RESPONSE DATA = " + data);
if (data != null)
{
data.complete();
data.release();
if (data.isLast())
{
requestLatch.countDown();

View File

@ -578,7 +578,7 @@ public class GoAwayTest extends AbstractClientServerTest
{
Stream.Data data = stream.readData();
if (data != null)
data.complete();
data.release();
if (data != null && data.isLast())
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_3, HttpStatus.OK_200, HttpFields.EMPTY);
@ -1277,7 +1277,7 @@ public class GoAwayTest extends AbstractClientServerTest
Stream.Data data = stream.readData();
if (data != null)
{
data.complete();
data.release();
if (data.isLast())
dataLatch.countDown();
}
@ -1332,7 +1332,7 @@ public class GoAwayTest extends AbstractClientServerTest
Stream.Data data = stream.readData();
if (data != null)
{
data.complete();
data.release();
if (data.isLast())
dataLatch.countDown();
}

View File

@ -123,7 +123,7 @@ public class HandlerClientServerTest extends AbstractClientServerTest
copy.put(byteBuffer);
copy.flip();
clientReceivedBuffers.add(copy);
data.complete();
data.release();
if (data.isLast())
{

View File

@ -28,7 +28,6 @@ import org.eclipse.jetty.io.content.ContentSinkOutputStream;
import org.eclipse.jetty.io.content.ContentSinkSubscriber;
import org.eclipse.jetty.io.content.ContentSourceInputStream;
import org.eclipse.jetty.io.content.ContentSourcePublisher;
import org.eclipse.jetty.io.content.ContentSourceTransformer;
import org.eclipse.jetty.io.internal.ByteBufferChunk;
import org.eclipse.jetty.io.internal.ContentCopier;
import org.eclipse.jetty.io.internal.ContentSourceByteBuffer;
@ -269,8 +268,11 @@ public class Content
* will continue to return the same error instance.</p>
* <p>Once a read returns a {@link Chunk#isLast() last chunk}, further reads will
* continue to return a last chunk (although the instance may be different).</p>
* <p>Chunks of content that have been consumed by the content reader code must
* be {@link Chunk#release() released}.</p>
* <p>The content reader code must ultimately arrange for a call to
* {@link Chunk#release()} on the returned {@link Chunk}.</p>
* <p>Additionally, prior to the ultimate call to {@link Chunk#release()}, the reader
* code may make additional calls to {@link Chunk#retain()}, that must ultimately
* be matched by a correspondent number of calls to {@link Chunk#release()}.</p>
* <p>Concurrent reads from different threads are not recommended, as they are
* inherently in a race condition.</p>
* <p>Reads performed outside the invocation context of a
@ -281,6 +283,7 @@ public class Content
*
* @return a chunk of content, possibly an error instance, or {@code null}
* @see #demand(Runnable)
* @see Retainable
*/
Chunk read();
@ -316,41 +319,6 @@ public class Content
* @param failure the cause of the failure
*/
void fail(Throwable failure);
/**
* <p>A wrapper of a nested source of content, that may transform the chunks obtained from
* the nested source.</p>
* <p>Typical implementations may split/coalesce the chunks read from the nested source,
* or encode/decode (for example gzip) them.</p>
* <p>Implementations should override {@link #transform(Chunk)} with the transformation
* logic.</p>
*/
abstract class Transformer extends ContentSourceTransformer
{
public Transformer(Content.Source rawSource)
{
super(rawSource);
}
/**
* <p>Transforms the input chunk parameter into an output chunk.</p>
* <p>The input chunk parameter may be {@code null}, a signal to implementations
* to try to produce an output chunk, if possible, from previous input chunks.
* For example, a single compressed input chunk may be transformed into multiple
* uncompressed output chunks.</p>
* <p>Implementations should return an {@link Chunk.Error error chunk} in case
* of transformation errors.</p>
* <p>Exceptions thrown by this method are equivalent to returning an error chunk.</p>
* <p>Implementations of this method must arrange to {@link Chunk#release() release}
* the input chunk, unless they return it as is.
* The output chunk is released by the code that uses this Transformer.</p>
*
* @param rawChunk the input chunk to transform
* @return the transformed output chunk
*/
@Override
protected abstract Chunk transform(Chunk rawChunk);
}
}
/**
@ -428,7 +396,7 @@ public class Content
* to release the {@code ByteBuffer} back into a pool), or the
* {@link #release()} method overridden.</p>
*/
public interface Chunk
public interface Chunk extends Retainable
{
/**
* <p>An empty, non-last, chunk.</p>
@ -440,7 +408,8 @@ public class Content
Content.Chunk EOF = ByteBufferChunk.EOF;
/**
* <p>Creates a last/non-last Chunk with the given ByteBuffer.</p>
* <p>Creates a Chunk with the given ByteBuffer.</p>
* <p>The returned Chunk must be {@link #release() released}.</p>
*
* @param byteBuffer the ByteBuffer with the bytes of this Chunk
* @param last whether the Chunk is the last one
@ -448,15 +417,15 @@ public class Content
*/
static Chunk from(ByteBuffer byteBuffer, boolean last)
{
return new ByteBufferChunk(byteBuffer, last);
return new ByteBufferChunk.WithReferenceCount(byteBuffer, last);
}
/**
* <p>Creates a last/non-last Chunk with the given ByteBuffer.</p>
* <p>Creates a Chunk with the given ByteBuffer.</p>
* <p>The returned Chunk must be {@link #release() released}.</p>
*
* @param byteBuffer the ByteBuffer with the bytes of this Chunk
* @param last whether the Chunk is the last one
* @param releaser the code to run when this Chunk is released
* @return a new Chunk
*/
static Chunk from(ByteBuffer byteBuffer, boolean last, Runnable releaser)
@ -466,6 +435,7 @@ public class Content
/**
* <p>Creates a last/non-last Chunk with the given ByteBuffer.</p>
* <p>The returned Chunk must be {@link #release() released}.</p>
*
* @param byteBuffer the ByteBuffer with the bytes of this Chunk
* @param last whether the Chunk is the last one
@ -477,6 +447,21 @@ public class Content
return new ByteBufferChunk.ReleasedByConsumer(byteBuffer, last, Objects.requireNonNull(releaser));
}
/**
* <p>Creates a last/non-last Chunk with the given ByteBuffer, linked to the given Retainable.</p>
* <p>The {@link #retain()} and {@link #release()} methods of this Chunk will delegate to the
* given Retainable.</p>
*
* @param byteBuffer the ByteBuffer with the bytes of this Chunk
* @param last whether the Chunk is the last one
* @param retainable the Retainable this Chunk links to
* @return a new Chunk
*/
static Chunk from(ByteBuffer byteBuffer, boolean last, Retainable retainable)
{
return new ByteBufferChunk.WithRetainable(byteBuffer, last, Objects.requireNonNull(retainable));
}
/**
* <p>Creates an {@link Error error chunk} with the given failure.</p>
*
@ -537,9 +522,31 @@ public class Content
boolean isLast();
/**
* <p>Releases the resources associated to this Chunk.</p>
* <p>Returns a new {@code Chunk} whose {@code ByteBuffer} is a slice, with the given
* position and limit, of the {@code ByteBuffer} of the source {@code Chunk}.</p>
* <p>The returned {@code Chunk} retains the source {@code Chunk} and it is linked
* to it via {@link #from(ByteBuffer, boolean, Retainable)}.</p>
*
* @param source the original chunk
* @param position the position at which the slice begins
* @param limit the limit at which the slice ends
* @param last whether the new Chunk is last
* @return a new {@code Chunk} retained from the source {@code Chunk} with a slice
* of the source {@code Chunk}'s {@code ByteBuffer}
*/
void release();
default Chunk slice(Chunk source, int position, int limit, boolean last)
{
ByteBuffer sourceBuffer = source.getByteBuffer();
int sourceLimit = sourceBuffer.limit();
sourceBuffer.limit(limit);
int sourcePosition = sourceBuffer.position();
sourceBuffer.position(position);
ByteBuffer slice = sourceBuffer.slice();
sourceBuffer.limit(sourceLimit);
sourceBuffer.position(sourcePosition);
source.retain();
return from(slice, last, source);
}
/**
* @return the number of bytes remaining in this Chunk
@ -635,8 +642,15 @@ public class Content
}
@Override
public void release()
public void retain()
{
throw new UnsupportedOperationException();
}
@Override
public boolean release()
{
return true;
}
}
}

View File

@ -0,0 +1,146 @@
//
// ========================================================================
// 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.io;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>A reference counted resource, for example one that is borrowed from a pool,
* that may be retained an additional number of times, and released a correspondent
* number of times, over its lifecycle.</p>
* <p>The resource is typically implicitly retained when it is first created.
* It may be retained more times (thus incrementing its reference count) and released
* (thus decrementing its reference count), until the reference count goes to zero.</p>
*/
public interface Retainable
{
/**
* <p>Retains this resource, incrementing the reference count.</p>
*/
void retain();
/**
* <p>Releases this resource, decrementing the reference count.</p>
* <p>This method returns {@code true} when the reference count goes to zero,
* {@code false} otherwise.</p>
*
* @return whether the invocation of this method decremented the reference count to zero
*/
boolean release();
class Wrapper implements Retainable
{
private final Retainable wrapped;
public Wrapper(Retainable wrapped)
{
this.wrapped = Objects.requireNonNull(wrapped);
}
public Retainable getWrapped()
{
return wrapped;
}
@Override
public void retain()
{
getWrapped().retain();
}
@Override
public boolean release()
{
return getWrapped().release();
}
@Override
public String toString()
{
return "%s@%x[%s]".formatted(getClass().getSimpleName(), hashCode(), getWrapped());
}
}
/**
* <p>A reference count implementation for a {@link Retainable} resource.</p>
* <p>The reference count is initialized to 1 when the resource is created,
* and therefore it is implicitly retained and needs a call to {@link #release()}.</p>
* <p>Additional calls to {@link #retain()} must be matched by correspondent
* calls to {@link #release()}.</p>
* <p>When the reference count goes to zero, the resource may be pooled.
* When the resource is acquired from the pool, {@link #acquire()} should be
* called to set the reference count to {@code 1}.</p>
*/
class ReferenceCounter implements Retainable
{
private final AtomicInteger references;
public ReferenceCounter()
{
this(1);
}
protected ReferenceCounter(int initialCount)
{
references = new AtomicInteger(initialCount);
}
/**
* <p>Updates the reference count from {@code 0} to {@code 1}.</p>
* <p>This method should only be used when this resource is acquired
* from a pool.</p>
*/
protected void acquire()
{
if (references.getAndUpdate(c -> c == 0 ? 1 : c) != 0)
throw new IllegalStateException("acquired while in use " + this);
}
@Override
public void retain()
{
if (references.getAndUpdate(c -> c == 0 ? 0 : c + 1) == 0)
throw new IllegalStateException("released " + this);
}
@Override
public boolean release()
{
int ref = references.updateAndGet(c ->
{
if (c == 0)
throw new IllegalStateException("already released " + this);
return c - 1;
});
return ref == 0;
}
/**
* <p>Returns whether {@link #retain()} has been called at least one more time than {@link #release()}.</p>
*
* @return whether this buffer is retained
*/
public boolean isRetained()
{
return references.get() > 1;
}
@Override
public String toString()
{
return String.format("%s@%x[r=%d]", getClass().getSimpleName(), hashCode(), references.get());
}
}
}

View File

@ -14,12 +14,10 @@
package org.eclipse.jetty.io;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Retainable;
/**
* <p>A pooled ByteBuffer which maintains a reference count that is
@ -33,15 +31,15 @@ import org.eclipse.jetty.util.Retainable;
* </ul>
* <p>Calling {@link #release()} on a out of pool and retained instance does not re-pool it while that re-pools it on a out of pool but not retained instance.</p>
*/
public class RetainableByteBuffer implements Retainable
public class RetainableByteBuffer extends Retainable.ReferenceCounter
{
private final ByteBuffer buffer;
private final AtomicInteger references = new AtomicInteger();
private final Consumer<RetainableByteBuffer> releaser;
private final AtomicLong lastUpdate = new AtomicLong(System.nanoTime());
RetainableByteBuffer(ByteBuffer buffer, Consumer<RetainableByteBuffer> releaser)
{
super(0);
this.releaser = releaser;
this.buffer = buffer;
}
@ -61,61 +59,26 @@ public class RetainableByteBuffer implements Retainable
return lastUpdate.getOpaque();
}
/**
* Checks if {@link #retain()} has been called at least one more time than {@link #release()}.
* @return true if this buffer is retained, false otherwise.
*/
public boolean isRetained()
{
return references.get() > 1;
}
public boolean isDirect()
{
return buffer.isDirect();
}
/**
* Increments the retained counter of this buffer. It must be done internally by
* the pool right after creation and after each un-pooling.
* The reason why this method exists on top of {@link #retain()} is to be able to
* have some safety checks that must know why the ref counter is being incremented.
*/
void acquire()
protected void acquire()
{
if (references.getAndUpdate(c -> c == 0 ? 1 : c) != 0)
throw new IllegalStateException("re-pooled while still used " + this);
// Overridden for visibility.
super.acquire();
}
/**
* Increments the retained counter of this buffer.
*/
@Override
public void retain()
{
if (references.getAndUpdate(c -> c == 0 ? 0 : c + 1) == 0)
throw new IllegalStateException("released " + this);
}
/**
* Decrements the retained counter of this buffer.
* @return true if the buffer was re-pooled, false otherwise.
*/
public boolean release()
{
int ref = references.updateAndGet(c ->
{
if (c == 0)
throw new IllegalStateException("already released " + this);
return c - 1;
});
if (ref == 0)
boolean released = super.release();
if (released)
{
lastUpdate.setOpaque(System.nanoTime());
releaser.accept(this);
return true;
}
return false;
return released;
}
public int remaining()
@ -141,6 +104,6 @@ public class RetainableByteBuffer implements Retainable
@Override
public String toString()
{
return String.format("%s@%x{%s,r=%d}", getClass().getSimpleName(), hashCode(), BufferUtil.toDetailString(buffer), references.get());
return "%s[%s]".formatted(super.toString(), BufferUtil.toDetailString(buffer));
}
}

View File

@ -18,16 +18,13 @@ import java.util.Objects;
import org.eclipse.jetty.io.Content;
/**
* <p>
* This abstract {@link Content.Source} wraps another {@link Content.Source} and implementors need only to provide
* the {@link #transform(Content.Chunk)} method, which is used to transform {@link Content.Chunk} read from the
* wrapped source.
* </p>
* <p>
* The {@link #demand(Runnable)} conversation is passed directly to the wrapped {@link Content.Source}, which means
* that transformations that may fully consume bytes read can result in a null return from {@link Content.Source#read()}
* even after a callback to the demand {@link Runnable} (as per spurious invocation in {@link Content.Source#demand(Runnable)}.
* </p>
* <p>This abstract {@link Content.Source} wraps another {@link Content.Source} and implementers need only
* to implement the {@link #transform(Content.Chunk)} method, which is used to transform {@link Content.Chunk}
* read from the wrapped source.</p>
* <p>The {@link #demand(Runnable)} conversation is passed directly to the wrapped {@link Content.Source},
* which means that transformations that may fully consume bytes read can result in a null return from
* {@link Content.Source#read()} even after a callback to the demand {@link Runnable} (as per spurious
* invocation in {@link Content.Source#demand(Runnable)}.</p>
*/
public abstract class ContentSourceTransformer implements Content.Source
{
@ -63,7 +60,8 @@ public abstract class ContentSourceTransformer implements Content.Source
transformedChunk = process(rawChunk);
// Release of rawChunk must be done by transform().
if (rawChunk != null && rawChunk != transformedChunk)
rawChunk.release();
rawChunk = null;
if (transformedChunk != null)
@ -117,29 +115,34 @@ public abstract class ContentSourceTransformer implements Content.Source
}
catch (Throwable x)
{
if (rawChunk != null)
rawChunk.release();
fail(x);
return Content.Chunk.from(x);
}
}
/**
* Content chunk transformation method.
* <p>
* This method is called during a {@link Content.Source#read()} to transform a raw chunk to a chunk that
* will be returned from the read call. The caller of {@link Content.Source#read()} method is always
* responsible for calling {@link Content.Chunk#release()} on the returned chunk, which may be:
* <p>Transforms the input chunk parameter into an output chunk.</p>
* <p>When this method produces a non-{@code null}, non-last chunk,
* it is subsequently invoked with a {@code null} input chunk to try to
* produce more output chunks from the previous input chunk.
* For example, a single compressed input chunk may be transformed into
* multiple uncompressed output chunks.</p>
* <p>The input chunk is released as soon as this method returns, so
* implementations that must hold onto the input chunk must arrange to call
* {@link Content.Chunk#retain()} and its correspondent {@link Content.Chunk#release()}.</p>
* <p>Implementations should return an {@link Content.Chunk.Error error chunk} in case
* of transformation errors.</p>
* <p>Exceptions thrown by this method are equivalent to returning an error chunk.</p>
* <p>Implementations of this method may return:</p>
* <ul>
* <li>the <code>rawChunk</code>. This is typically done for {@link Content.Chunk.Error}s,
* when {@link Content.Chunk#isLast()} is true, or if no transformation is required.</li>
* <li>a new (or predefined) {@link Content.Chunk} derived from the <code>rawChunk</code>. The transform is
* responsible for calling {@link Content.Chunk#release()} on the <code>rawChunk</code>, either during the call
* to {@link Content.Source#read()} or subsequently.</li>
* <li>null if the <code>rawChunk</code> is fully consumed and/or requires additional chunks to be transformed.</li>
* <li>{@code null}, if more input chunks are necessary to produce an output chunk</li>
* <li>the {@code inputChunk} itself, typically in case of {@link Content.Chunk.Error}s,
* or when no transformation is required</li>
* <li>a new {@link Content.Chunk} derived from {@code inputChunk}.</li>
* </ul>
* @param rawChunk A chunk read from the wrapped {@link Content.Source}. It is always non null.
* @return The transformed chunk or null.
*
* @param inputChunk a chunk read from the wrapped {@link Content.Source}
* @return a transformed chunk or {@code null}
*/
protected abstract Content.Chunk transform(Content.Chunk rawChunk);
protected abstract Content.Chunk transform(Content.Chunk inputChunk);
}

View File

@ -19,6 +19,8 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.NoopByteBufferPool;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;
@ -37,7 +39,7 @@ public class InputStreamContentSource implements Content.Source
private final AutoLock lock = new AutoLock();
private final SerializedInvoker invoker = new SerializedInvoker();
private final InputStream inputStream;
private final ByteBufferPool bufferPool;
private final RetainableByteBufferPool bufferPool;
private int bufferSize = 4096;
private Runnable demandCallback;
private Content.Chunk.Error errorChunk;
@ -45,13 +47,18 @@ public class InputStreamContentSource implements Content.Source
public InputStreamContentSource(InputStream inputStream)
{
this(inputStream, null);
this(inputStream, (ByteBufferPool)null);
}
public InputStreamContentSource(InputStream inputStream, ByteBufferPool bufferPool)
{
this(inputStream, (bufferPool == null ? ByteBufferPool.NOOP : bufferPool).asRetainableByteBufferPool());
}
public InputStreamContentSource(InputStream inputStream, RetainableByteBufferPool bufferPool)
{
this.inputStream = inputStream;
this.bufferPool = bufferPool == null ? ByteBufferPool.NOOP : bufferPool;
this.bufferPool = bufferPool == null ? ByteBufferPool.NOOP.asRetainableByteBufferPool() : bufferPool;
}
public int getBufferSize()
@ -77,7 +84,8 @@ public class InputStreamContentSource implements Content.Source
try
{
ByteBuffer buffer = bufferPool.acquire(getBufferSize(), false);
RetainableByteBuffer streamBuffer = bufferPool.acquire(getBufferSize(), false);
ByteBuffer buffer = streamBuffer.getBuffer();
int read = inputStream.read(buffer.array(), buffer.arrayOffset(), buffer.capacity());
if (read < 0)
{
@ -87,7 +95,7 @@ public class InputStreamContentSource implements Content.Source
else
{
buffer.limit(read);
return Content.Chunk.from(buffer, false, bufferPool::release);
return Content.Chunk.from(buffer, false, streamBuffer);
}
}
catch (Throwable x)

View File

@ -24,13 +24,15 @@ import java.nio.file.StandardOpenOption;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.RetainableByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.SerializedInvoker;
/**
* <p>A {@link Content.Source} that provides the file content of the passed {@link Path}</p>
* <p>A {@link Content.Source} that provides the file content of the passed {@link Path}.</p>
*/
public class PathContentSource implements Content.Source
{
@ -38,8 +40,8 @@ public class PathContentSource implements Content.Source
private final SerializedInvoker invoker = new SerializedInvoker();
private final Path path;
private final long length;
private final RetainableByteBufferPool byteBufferPool;
private int bufferSize = 4096;
private ByteBufferPool byteBufferPool;
private boolean useDirectByteBuffers = true;
private ReadableByteChannel channel;
private long totalRead;
@ -47,6 +49,16 @@ public class PathContentSource implements Content.Source
private Content.Chunk.Error errorChunk;
public PathContentSource(Path path) throws IOException
{
this(path, (ByteBufferPool)null);
}
public PathContentSource(Path path, ByteBufferPool byteBufferPool) throws IOException
{
this(path, (byteBufferPool == null ? ByteBufferPool.NOOP : byteBufferPool).asRetainableByteBufferPool());
}
public PathContentSource(Path path, RetainableByteBufferPool byteBufferPool) throws IOException
{
if (!Files.isRegularFile(path))
throw new NoSuchFileException(path.toString());
@ -54,6 +66,7 @@ public class PathContentSource implements Content.Source
throw new AccessDeniedException(path.toString());
this.path = path;
this.length = Files.size(path);
this.byteBufferPool = byteBufferPool == null ? ByteBufferPool.NOOP.asRetainableByteBufferPool() : byteBufferPool;
}
public Path getPath()
@ -77,16 +90,6 @@ public class PathContentSource implements Content.Source
this.bufferSize = bufferSize;
}
public ByteBufferPool getByteBufferPool()
{
return byteBufferPool;
}
public void setByteBufferPool(ByteBufferPool byteBufferPool)
{
this.byteBufferPool = byteBufferPool;
}
public boolean isUseDirectByteBuffers()
{
return useDirectByteBuffers;
@ -123,9 +126,8 @@ public class PathContentSource implements Content.Source
if (!channel.isOpen())
return Content.Chunk.EOF;
ByteBuffer byteBuffer = byteBufferPool == null
? BufferUtil.allocate(getBufferSize(), isUseDirectByteBuffers())
: byteBufferPool.acquire(getBufferSize(), isUseDirectByteBuffers());
RetainableByteBuffer retainableBuffer = byteBufferPool.acquire(getBufferSize(), isUseDirectByteBuffers());
ByteBuffer byteBuffer = retainableBuffer.getBuffer();
int read;
try
@ -146,7 +148,7 @@ public class PathContentSource implements Content.Source
if (last)
IO.close(channel);
return Content.Chunk.from(byteBuffer, last, this::release);
return Content.Chunk.from(byteBuffer, last, retainableBuffer);
}
@Override
@ -204,12 +206,6 @@ public class PathContentSource implements Content.Source
}
}
private void release(ByteBuffer byteBuffer)
{
if (byteBufferPool != null)
byteBufferPool.release(byteBuffer);
}
protected boolean rewind()
{
try (AutoLock ignored = lock.lock())

Some files were not shown because too many files have changed in this diff Show More