@@ -17,6 +17,7 @@ import NIOCore
17
17
import NIOEmbedded
18
18
import NIOHTTP1
19
19
import NIOHTTP2
20
+ import NIOHPACK
20
21
21
22
/// A `ChannelInboundHandler` that re-entrantly calls into a handler that just passed
22
23
/// it `channelRead`.
@@ -43,6 +44,22 @@ final class ReenterOnReadHandler: ChannelInboundHandler {
43
44
}
44
45
}
45
46
47
+ final class WindowUpdatedEventHandler : ChannelInboundHandler {
48
+ public typealias InboundIn = HTTP2Frame
49
+ public typealias InboundOut = HTTP2Frame
50
+ public typealias OutboundOut = HTTP2Frame
51
+
52
+ init ( ) {
53
+ }
54
+
55
+ func userInboundEventTriggered( context: ChannelHandlerContext , event: Any ) {
56
+ guard event is NIOHTTP2WindowUpdatedEvent else { return }
57
+
58
+ let frame = HTTP2Frame ( streamID: . rootStream, payload: . windowUpdate( windowSizeIncrement: 1 ) )
59
+ context. writeAndFlush ( self . wrapOutboundOut ( frame) , promise: nil )
60
+ }
61
+ }
62
+
46
63
// Tests that the HTTP2Parser is safely re-entrant.
47
64
//
48
65
// These tests ensure that we don't ever call into the HTTP/2 session more than once at a time.
@@ -164,4 +181,38 @@ final class ReentrancyTests: XCTestCase {
164
181
XCTAssertNoThrow ( try self . clientChannel. finish ( ) )
165
182
XCTAssertNoThrow ( try self . serverChannel. finish ( ) )
166
183
}
184
+
185
+ func testReenterAutomaticFrames( ) throws {
186
+ // Start by setting up the connection.
187
+ try self . basicHTTP2Connection ( )
188
+ let windowUpdateFrameHandler = WindowUpdatedEventHandler ( )
189
+ XCTAssertNoThrow ( try self . serverChannel. pipeline. addHandler ( windowUpdateFrameHandler) . wait ( ) )
190
+
191
+ // Write and flush the header from the client to open a stream
192
+ let headers = HPACKHeaders ( [ ( " :path " , " / " ) , ( " :method " , " POST " ) , ( " :scheme " , " https " ) , ( " :authority " , " localhost " ) ] )
193
+ let reqFramePayload = HTTP2Frame . FramePayload. headers ( . init( headers: headers) )
194
+ self . clientChannel. writeAndFlush ( HTTP2Frame ( streamID: 1 , payload: reqFramePayload) , promise: nil )
195
+ self . interactInMemory ( clientChannel, serverChannel)
196
+
197
+ // Write and flush the header from the server
198
+ let resHeaders = HPACKHeaders ( [ ( " :status " , " 200 " ) ] )
199
+ let resFramePayload = HTTP2Frame . FramePayload. headers ( . init( headers: resHeaders) )
200
+ self . serverChannel. writeAndFlush ( HTTP2Frame ( streamID: 1 , payload: resFramePayload) , promise: nil )
201
+
202
+ // Write lots of small data frames and flush them all at once
203
+ var requestBody = self . serverChannel. allocator. buffer ( capacity: 1 )
204
+ requestBody. writeBytes ( Array ( repeating: UInt8 ( 0x04 ) , count: 1 ) )
205
+ var reqBodyFramePayload = HTTP2Frame . FramePayload. data ( . init( data: . byteBuffer( requestBody) ) )
206
+ for _ in 0 ..< 10000 {
207
+ serverChannel. write ( HTTP2Frame ( streamID: 1 , payload: reqBodyFramePayload) , promise: nil )
208
+ }
209
+ reqBodyFramePayload = . data( . init( data: . byteBuffer( requestBody) , endStream: true ) )
210
+ serverChannel. writeAndFlush ( HTTP2Frame ( streamID: 1 , payload: reqBodyFramePayload) , promise: nil )
211
+
212
+ // Now we can deliver these bytes.
213
+ self . deliverAllBytes ( from: self . clientChannel, to: self . serverChannel)
214
+
215
+ XCTAssertNoThrow ( try self . clientChannel. finish ( ) )
216
+ XCTAssertNoThrow ( try self . serverChannel. finish ( ) )
217
+ }
167
218
}
0 commit comments