@@ -269,6 +269,210 @@ end
269269 end
270270end
271271
272+ @testset " HTTP/2 client surfaces a reset after buffered response DATA" begin
273+ for payload in (" x" , " partial" , repeat (" z" , 4097 ))
274+ listener = ND. listen (" tcp" , " 127.0.0.1:0" ; backlog = 8 )
275+ laddr = NC. addr (listener):: NC.SocketAddrV4
276+ address = ND. join_host_port (" 127.0.0.1" , Int (laddr. port))
277+ allow_reset = Channel {Nothing} (1 )
278+ finish = Channel {Nothing} (1 )
279+ server_task = errormonitor (Threads. @spawn begin
280+ accepted_conn = NC. accept (listener)
281+ reader = HT. _ConnReader (accepted_conn)
282+ server_encoder = HT. Encoder ()
283+ server_decoder = HT. Decoder ()
284+ try
285+ _ = _read_exact_h2_tcp! (accepted_conn, length (HT. _H2_PREFACE))
286+ _ = HT. read_frame! (reader)
287+ _write_frame_to_conn! (accepted_conn, HT. SettingsFrame (false , Pair{UInt16, UInt32}[]))
288+ _ = HT. read_frame! (reader)
289+ headers_frame = _read_next_headers_frame! (reader)
290+ hf = headers_frame:: HT.HeadersFrame
291+ _ = HT. decode_header_block (server_decoder, hf. header_block_fragment)
292+ encoded = HT. encode_header_block (server_encoder, HT. HeaderField[HT. HeaderField (" :status" , " 200" , false )])
293+ _write_frame_to_conn! (accepted_conn, HT. HeadersFrame (hf. stream_id, false , true , encoded))
294+ _write_frame_to_conn! (accepted_conn, HT. DataFrame (hf. stream_id, false , collect (codeunits (payload))))
295+ take! (allow_reset)
296+ _write_frame_to_conn! (accepted_conn, HT. RSTStreamFrame (hf. stream_id, UInt32 (0x8 )))
297+ take! (finish)
298+ finally
299+ HTTP. @try_ignore NC. close (accepted_conn)
300+ end
301+ return nothing
302+ end )
303+ h2_conn = HT. connect_h2! (address; secure = false )
304+ try
305+ request = HT. Request (" GET" , " /partial-reset" ; host = address, body = HT. EmptyBody (), content_length = 0 )
306+ response = HT. h2_roundtrip! (h2_conn, request)
307+ put! (allow_reset, nothing )
308+ state = (response. body:: HT.H2Body ). state
309+ reset_received = timedwait (() -> begin
310+ lock (state. lock)
311+ try
312+ return state. stream_error != = nothing
313+ finally
314+ unlock (state. lock)
315+ end
316+ end , 5.0 ; pollint = 0.001 )
317+ @test reset_received != :timed_out
318+
319+ buf = Vector {UInt8} (undef, length (payload))
320+ @test HT. body_read! (response. body, buf) == length (payload)
321+ @test String (copy (buf)) == payload
322+ err = try
323+ HT. body_read! (response. body, buf)
324+ nothing
325+ catch e
326+ e
327+ end
328+ @test err isa HT. H2StreamResetError
329+ @test (err:: HT.H2StreamResetError ). error_code == UInt32 (0x8 )
330+ @test HT. body_closed (response. body)
331+ @test HT. _stream_state (h2_conn, (response. body:: HT.H2Body ). stream_id) === nothing
332+ @test isempty (HT. get_request_context (request). cancel_callbacks)
333+ @test HT. _h2_conn_reusable (h2_conn)
334+ finally
335+ isready (allow_reset) || put! (allow_reset, nothing )
336+ put! (finish, nothing )
337+ _wait_task_h2! (server_task)
338+ close (h2_conn)
339+ HTTP. @try_ignore NC. close (listener)
340+ end
341+ end
342+ end
343+
344+ @testset " HTTP/2 client keeps a complete response after a late reset" begin
345+ listener = ND. listen (" tcp" , " 127.0.0.1:0" ; backlog = 8 )
346+ laddr = NC. addr (listener):: NC.SocketAddrV4
347+ address = ND. join_host_port (" 127.0.0.1" , Int (laddr. port))
348+ reset_processed = Channel {Nothing} (1 )
349+ finish = Channel {Nothing} (1 )
350+ server_task = errormonitor (Threads. @spawn begin
351+ accepted_conn = NC. accept (listener)
352+ reader = HT. _ConnReader (accepted_conn)
353+ server_encoder = HT. Encoder ()
354+ server_decoder = HT. Decoder ()
355+ try
356+ _ = _read_exact_h2_tcp! (accepted_conn, length (HT. _H2_PREFACE))
357+ _ = HT. read_frame! (reader)
358+ _write_frame_to_conn! (accepted_conn, HT. SettingsFrame (false , Pair{UInt16, UInt32}[]))
359+ _ = HT. read_frame! (reader)
360+ headers_frame = _read_next_headers_frame! (reader)
361+ hf = headers_frame:: HT.HeadersFrame
362+ _ = HT. decode_header_block (server_decoder, hf. header_block_fragment)
363+ encoded = HT. encode_header_block (server_encoder, HT. HeaderField[HT. HeaderField (" :status" , " 200" , false )])
364+ _write_frame_to_conn! (accepted_conn, HT. HeadersFrame (hf. stream_id, false , true , encoded))
365+ _write_frame_to_conn! (accepted_conn, HT. DataFrame (hf. stream_id, true , collect (codeunits (" complete" ))))
366+ _write_frame_to_conn! (accepted_conn, HT. RSTStreamFrame (hf. stream_id, UInt32 (0x8 )))
367+ ping_data = ntuple (UInt8, 8 )
368+ _write_frame_to_conn! (accepted_conn, HT. PingFrame (false , ping_data))
369+ while true
370+ frame = HT. read_frame! (reader)
371+ frame isa HT. PingFrame && (frame:: HT.PingFrame ). ack && break
372+ end
373+ put! (reset_processed, nothing )
374+ take! (finish)
375+ finally
376+ HTTP. @try_ignore NC. close (accepted_conn)
377+ end
378+ return nothing
379+ end )
380+ h2_conn = HT. connect_h2! (address; secure = false )
381+ try
382+ request = HT. Request (" GET" , " /complete-late-reset" ; host = address, body = HT. EmptyBody (), content_length = 0 )
383+ response = HT. h2_roundtrip! (h2_conn, request)
384+ reset_seen = timedwait (() -> isready (reset_processed), 5.0 ; pollint = 0.001 )
385+ @test reset_seen != :timed_out
386+ reset_seen == :timed_out && error (" client did not process the late reset" )
387+ take! (reset_processed)
388+ state = (response. body:: HT.H2Body ). state
389+ response_complete = timedwait (() -> begin
390+ lock (state. lock)
391+ try
392+ return state. stream_done
393+ finally
394+ unlock (state. lock)
395+ end
396+ end , 5.0 ; pollint = 0.001 )
397+ @test response_complete != :timed_out
398+ @test String (_read_all_h2_body (response. body)) == " complete"
399+ @test HT. body_closed (response. body)
400+ @test HT. _stream_state (h2_conn, (response. body:: HT.H2Body ). stream_id) === nothing
401+ @test isempty (HT. get_request_context (request). cancel_callbacks)
402+ @test HT. _h2_conn_reusable (h2_conn)
403+ finally
404+ put! (finish, nothing )
405+ _wait_task_h2! (server_task)
406+ close (h2_conn)
407+ HTTP. @try_ignore NC. close (listener)
408+ end
409+ end
410+
411+ @testset " HTTP/2 client keeps a complete response when another stream fails with the connection" begin
412+ listener = ND. listen (" tcp" , " 127.0.0.1:0" ; backlog = 8 )
413+ laddr = NC. addr (listener):: NC.SocketAddrV4
414+ address = ND. join_host_port (" 127.0.0.1" , Int (laddr. port))
415+ allow_close = Channel {Nothing} (1 )
416+ server_task = errormonitor (Threads. @spawn begin
417+ accepted_conn = NC. accept (listener)
418+ reader = HT. _ConnReader (accepted_conn)
419+ server_encoder = HT. Encoder ()
420+ server_decoder = HT. Decoder ()
421+ try
422+ _ = _read_exact_h2_tcp! (accepted_conn, length (HT. _H2_PREFACE))
423+ _ = HT. read_frame! (reader)
424+ _write_frame_to_conn! (accepted_conn, HT. SettingsFrame (false , Pair{UInt16, UInt32}[]))
425+ _ = HT. read_frame! (reader)
426+ first_headers_frame = _read_next_headers_frame! (reader)
427+ first_hf = first_headers_frame:: HT.HeadersFrame
428+ _ = HT. decode_header_block (server_decoder, first_hf. header_block_fragment)
429+ encoded = HT. encode_header_block (server_encoder, HT. HeaderField[HT. HeaderField (" :status" , " 200" , false )])
430+ _write_frame_to_conn! (accepted_conn, HT. HeadersFrame (first_hf. stream_id, false , true , encoded))
431+ _write_frame_to_conn! (accepted_conn, HT. DataFrame (first_hf. stream_id, true , collect (codeunits (" complete" ))))
432+ second_headers_frame = _read_next_headers_frame! (reader)
433+ second_hf = second_headers_frame:: HT.HeadersFrame
434+ _ = HT. decode_header_block (server_decoder, second_hf. header_block_fragment)
435+ _write_frame_to_conn! (accepted_conn, HT. HeadersFrame (second_hf. stream_id, false , true , encoded))
436+ take! (allow_close)
437+ finally
438+ HTTP. @try_ignore NC. close (accepted_conn)
439+ end
440+ return nothing
441+ end )
442+ h2_conn = HT. connect_h2! (address; secure = false )
443+ try
444+ complete_request = HT. Request (" GET" , " /complete" ; host = address, body = HT. EmptyBody (), content_length = 0 )
445+ complete_response = HT. h2_roundtrip! (h2_conn, complete_request)
446+ incomplete_request = HT. Request (" GET" , " /incomplete" ; host = address, body = HT. EmptyBody (), content_length = 0 )
447+ incomplete_response = HT. h2_roundtrip! (h2_conn, incomplete_request)
448+ put! (allow_close, nothing )
449+ _wait_task_h2! (server_task)
450+ complete_state = (complete_response. body:: HT.H2Body ). state
451+ incomplete_state = (incomplete_response. body:: HT.H2Body ). state
452+ failure_seen = timedwait (() -> begin
453+ complete_failed = lock (complete_state. lock) do
454+ complete_state. conn_errored
455+ end
456+ return lock (incomplete_state. lock) do
457+ complete_failed && incomplete_state. conn_errored
458+ end
459+ end , 5.0 ; pollint = 0.001 )
460+ @test failure_seen != :timed_out
461+ @test String (_read_all_h2_body (complete_response. body)) == " complete"
462+ @test_throws HT. ProtocolError HT. body_read! (incomplete_response. body, Vector {UInt8} (undef, 1 ))
463+ HT. body_close! (incomplete_response. body)
464+ @test HT. body_closed (complete_response. body)
465+ @test HT. _stream_state (h2_conn, (complete_response. body:: HT.H2Body ). stream_id) === nothing
466+ @test isempty (HT. get_request_context (complete_request). cancel_callbacks)
467+ @test ! HT. _h2_conn_reusable (h2_conn)
468+ finally
469+ isready (allow_close) || put! (allow_close, nothing )
470+ _wait_task_h2! (server_task)
471+ close (h2_conn)
472+ HTTP. @try_ignore NC. close (listener)
473+ end
474+ end
475+
272476@testset " HTTP/2 client requires initial SETTINGS before other frames" begin
273477 listener = ND. listen (" tcp" , " 127.0.0.1:0" ; backlog = 8 )
274478 laddr = NC. addr (listener):: NC.SocketAddrV4
@@ -1740,6 +1944,9 @@ end
17401944 @test String (_read_all_h2_body (response. body)) == " ok"
17411945 failure = only (failures):: TaskFailedException
17421946 @test failure. task. exception isa HT. H2GoAwayError
1947+ goaway_error = failure. task. exception:: HT.H2GoAwayError
1948+ @test goaway_error. last_stream_id == UInt32 (1 )
1949+ @test sprint (showerror, goaway_error) == " HTTP/2 stream rejected by GOAWAY"
17431950 _wait_task_h2! (server_task)
17441951 finally
17451952 close (h2_conn)
0 commit comments