5959import java .util .List ;
6060import java .util .Map ;
6161import java .util .concurrent .CompletableFuture ;
62+ import java .util .concurrent .CompletionException ;
6263import java .util .concurrent .ConcurrentHashMap ;
6364import java .util .concurrent .ConcurrentMap ;
6465import java .util .function .BiPredicate ;
7172import jdk .httpclient .test .lib .http2 .Http2TestServerConnection ;
7273
7374import static java .nio .charset .StandardCharsets .UTF_8 ;
74- import static org .testng .Assert .assertEquals ;
75+ import static org .testng .Assert .* ;
7576
7677public class PushPromiseContinuation {
7778
@@ -102,7 +103,7 @@ public void setup() throws Exception {
102103
103104 // Need to have a custom exchange supplier to manage the server's push
104105 // promise with continuation flow
105- server .setExchangeSupplier (Http2LPPTestExchangeImpl ::new );
106+ server .setExchangeSupplier (Http2PushPromiseContinuationExchangeImpl ::new );
106107
107108 System .err .println ("PushPromiseContinuation: Server listening on port " + server .getAddress ().getPort ());
108109 server .start ();
@@ -171,6 +172,31 @@ public void testThreeContinuations() {
171172 verify (resp );
172173 }
173174
175+ @ Test
176+ public void testSendHeadersOnPushPromiseStream () throws Exception {
177+ // This test server sends a push promise that should be followed by a continuation but
178+ // incorrectly sends on Response Headers while the client awaits the continuation.
179+ Http2TestServer faultyServer = new Http2TestServer (false , 0 );
180+ faultyServer .addHandler (new ServerPushHandler (), "/" );
181+ faultyServer .setExchangeSupplier (Http2PushPromiseHeadersExchangeImpl ::new );
182+ System .err .println ("PushPromiseContinuation: FaultyServer listening on port " + faultyServer .getAddress ().getPort ());
183+ faultyServer .start ();
184+
185+ int faultyPort = faultyServer .getAddress ().getPort ();
186+ URI faultyUri = new URI ("http://localhost:" + faultyPort + "/" );
187+
188+ HttpClient client = HttpClient .newHttpClient ();
189+ // Server is making a request to an incorrect URI
190+ HttpRequest hreq = HttpRequest .newBuilder (faultyUri ).version (HttpClient .Version .HTTP_2 ).GET ().build ();
191+ CompletableFuture <HttpResponse <String >> cf =
192+ client .sendAsync (hreq , HttpResponse .BodyHandlers .ofString (UTF_8 ), pph );
193+
194+ CompletionException t = expectThrows (CompletionException .class , () -> cf .join ());
195+ assertEquals (t .getCause ().getClass (), IOException .class , "Expected an IOException but got " + t .getCause ());
196+ System .err .println ("Client received the following expected exception: " + t .getCause ());
197+ faultyServer .stop ();
198+ }
199+
174200 private void verify (HttpResponse <String > resp ) {
175201 assertEquals (resp .statusCode (), 200 );
176202 assertEquals (resp .body (), mainResponseBody );
@@ -191,15 +217,46 @@ private void verify(HttpResponse<String> resp) {
191217 }
192218 }
193219
194- static class Http2LPPTestExchangeImpl extends Http2TestExchangeImpl {
220+ static class Http2PushPromiseHeadersExchangeImpl extends Http2TestExchangeImpl {
221+
222+ Http2PushPromiseHeadersExchangeImpl (int streamid , String method , HttpHeaders reqheaders , HttpHeadersBuilder rspheadersBuilder , URI uri , InputStream is , SSLSession sslSession , BodyOutputStream os , Http2TestServerConnection conn , boolean pushAllowed ) {
223+ super (streamid , method , reqheaders , rspheadersBuilder , uri , is , sslSession , os , conn , pushAllowed );
224+ }
225+
226+
227+ @ Override
228+ public void serverPush (URI uri , HttpHeaders headers , InputStream content ) {
229+ HttpHeadersBuilder headersBuilder = new HttpHeadersBuilder ();
230+ headersBuilder .setHeader (":method" , "GET" );
231+ headersBuilder .setHeader (":scheme" , uri .getScheme ());
232+ headersBuilder .setHeader (":authority" , uri .getAuthority ());
233+ headersBuilder .setHeader (":path" , uri .getPath ());
234+ for (Map .Entry <String ,List <String >> entry : headers .map ().entrySet ()) {
235+ for (String value : entry .getValue ())
236+ headersBuilder .addHeader (entry .getKey (), value );
237+ }
238+ HttpHeaders combinedHeaders = headersBuilder .build ();
239+ OutgoingPushPromise pp = new OutgoingPushPromise (streamid , uri , combinedHeaders , content );
240+ // Indicates to the client that a continuation should be expected
241+ pp .setFlag (0x0 );
242+ try {
243+ conn .addToOutputQ (pp );
244+ // writeLoop will spin up thread to read the InputStream
245+ } catch (IOException ex ) {
246+ System .err .println ("TestServer: pushPromise exception: " + ex );
247+ }
248+ }
249+ }
250+
251+ static class Http2PushPromiseContinuationExchangeImpl extends Http2TestExchangeImpl {
195252
196253 HttpHeadersBuilder pushPromiseHeadersBuilder ;
197254 List <ContinuationFrame > cfs ;
198255
199- Http2LPPTestExchangeImpl (int streamid , String method , HttpHeaders reqheaders ,
200- HttpHeadersBuilder rspheadersBuilder , URI uri , InputStream is ,
201- SSLSession sslSession , BodyOutputStream os ,
202- Http2TestServerConnection conn , boolean pushAllowed ) {
256+ Http2PushPromiseContinuationExchangeImpl (int streamid , String method , HttpHeaders reqheaders ,
257+ HttpHeadersBuilder rspheadersBuilder , URI uri , InputStream is ,
258+ SSLSession sslSession , BodyOutputStream os ,
259+ Http2TestServerConnection conn , boolean pushAllowed ) {
203260 super (streamid , method , reqheaders , rspheadersBuilder , uri , is , sslSession , os , conn , pushAllowed );
204261 }
205262
@@ -253,18 +310,15 @@ public void serverPush(URI uri, HttpHeaders headers, InputStream content) {
253310 HttpHeaders pushPromiseHeaders = pushPromiseHeadersBuilder .build ();
254311 testHeaders = testHeadersBuilder .build ();
255312 // Create the Push Promise Frame
256- OutgoingPushPromise pp = new OutgoingPushPromise (streamid , uri , pushPromiseHeaders , content );
313+ OutgoingPushPromise pp = new OutgoingPushPromise (streamid , uri , pushPromiseHeaders , content , cfs );
314+
257315 // Indicates to the client that a continuation should be expected
258316 pp .setFlag (0x0 );
259317
260318 try {
261319 // Schedule push promise and continuation for sending
262320 conn .addToOutputQ (pp );
263321 System .err .println ("Server: Scheduled a Push Promise to Send" );
264- for (ContinuationFrame cf : cfs ) {
265- conn .addToOutputQ (cf );
266- System .err .println ("Server: Scheduled a Continuation to Send" );
267- }
268322 } catch (IOException ex ) {
269323 System .err .println ("Server: pushPromise exception: " + ex );
270324 }
0 commit comments