@@ -22,6 +22,39 @@ import NIO
22
22
import NIOHTTP1
23
23
import XCTest
24
24
25
+ /// A trivial channel handler that invokes a callback once, the first time it sees
26
+ /// channelRead.
27
+ final class OnFirstReadHandler : ChannelInboundHandler {
28
+ typealias InboundIn = Any
29
+ typealias InboundOut = Any
30
+
31
+ private var callback : ( ( ) -> Void ) ?
32
+
33
+ init ( callback: @escaping ( ) -> Void ) {
34
+ self . callback = callback
35
+ }
36
+
37
+ func channelRead( context: ChannelHandlerContext , data: NIOAny ) {
38
+ context. fireChannelRead ( data)
39
+
40
+ if let callback = self . callback {
41
+ self . callback = nil
42
+ callback ( )
43
+ }
44
+ }
45
+ }
46
+
47
+ final class ErrorRecordingHandler : ChannelInboundHandler {
48
+ typealias InboundIn = Any
49
+
50
+ var errors : [ Error ] = [ ]
51
+
52
+ func errorCaught( context: ChannelHandlerContext , error: Error ) {
53
+ self . errors. append ( error)
54
+ context. fireErrorCaught ( error)
55
+ }
56
+ }
57
+
25
58
class HTTP1ToGRPCServerCodecTests : GRPCTestCase {
26
59
var channel : EmbeddedChannel !
27
60
@@ -127,4 +160,113 @@ class HTTP1ToGRPCServerCodecTests: GRPCTestCase {
127
160
}
128
161
}
129
162
}
163
+
164
+ func testReentrantMessageDelivery( ) throws {
165
+ XCTAssertNoThrow (
166
+ try self . channel
167
+ . writeInbound ( HTTPServerRequestPart . head ( self . makeRequestHead ( ) ) )
168
+ )
169
+ let requestPart = try self . channel. readInbound ( as: _RawGRPCServerRequestPart. self)
170
+
171
+ switch requestPart {
172
+ case . some( . head) :
173
+ ( )
174
+ default :
175
+ XCTFail ( " Unexpected request part: \( String ( describing: requestPart) ) " )
176
+ }
177
+
178
+ // Write three messages into a single body.
179
+ var buffer = self . channel. allocator. buffer ( capacity: 0 )
180
+ let serializedMessages : [ Data ] = try [ " foo " , " bar " , " baz " ] . map { text in
181
+ Echo_EchoRequest . with { $0. text = text }
182
+ } . map { request in
183
+ try request. serializedData ( )
184
+ }
185
+
186
+ for data in serializedMessages {
187
+ buffer. writeInteger ( UInt8 ( 0 ) )
188
+ buffer. writeInteger ( UInt32 ( data. count) )
189
+ buffer. writeBytes ( data)
190
+ }
191
+
192
+ // Create an OnFirstReadHandler that will _also_ send the data when it sees the first read.
193
+ // This is try! because it cannot throw.
194
+ let onFirstRead = OnFirstReadHandler {
195
+ try ! self . channel. writeInbound ( HTTPServerRequestPart . body ( buffer) )
196
+ }
197
+ XCTAssertNoThrow ( try self . channel. pipeline. addHandler ( onFirstRead) . wait ( ) )
198
+
199
+ // Now write.
200
+ XCTAssertNoThrow ( try self . channel. writeInbound ( HTTPServerRequestPart . body ( buffer) ) )
201
+
202
+ // This must not re-order messages.
203
+ for message in [ serializedMessages, serializedMessages] . flatMap ( { $0 } ) {
204
+ let requestPart = try self . channel. readInbound ( as: _RawGRPCServerRequestPart. self)
205
+ switch requestPart {
206
+ case var . some( . message( buffer) ) :
207
+ XCTAssertEqual ( message, buffer. readData ( length: buffer. readableBytes) !)
208
+ default :
209
+ XCTFail ( " Unexpected request part: \( String ( describing: requestPart) ) " )
210
+ }
211
+ }
212
+ }
213
+
214
+ func testErrorsOnlyHappenOnce( ) throws {
215
+ XCTAssertNoThrow (
216
+ try self . channel
217
+ . writeInbound ( HTTPServerRequestPart . head ( self . makeRequestHead ( ) ) )
218
+ )
219
+ let requestPart = try self . channel. readInbound ( as: _RawGRPCServerRequestPart. self)
220
+
221
+ switch requestPart {
222
+ case . some( . head) :
223
+ ( )
224
+ default :
225
+ XCTFail ( " Unexpected request part: \( String ( describing: requestPart) ) " )
226
+ }
227
+
228
+ // Write three messages into a single body.
229
+ var buffer = self . channel. allocator. buffer ( capacity: 0 )
230
+ let serializedMessages : [ Data ] = try [ " foo " , " bar " , " baz " ] . map { text in
231
+ Echo_EchoRequest . with { $0. text = text }
232
+ } . map { request in
233
+ try request. serializedData ( )
234
+ }
235
+
236
+ for data in serializedMessages {
237
+ buffer. writeInteger ( UInt8 ( 0 ) )
238
+ buffer. writeInteger ( UInt32 ( data. count) )
239
+ buffer. writeBytes ( data)
240
+ }
241
+
242
+ // Create an OnFirstReadHandler that will _also_ send the data when it sees the first read.
243
+ // This is try! because it cannot throw.
244
+ let onFirstRead = OnFirstReadHandler {
245
+ // Let's create a bad message: we'll turn on compression. We use two bytes here to deal with the fact that
246
+ // in hitting the error we'll actually consume the first byte (whoops).
247
+ var badBuffer = self . channel. allocator. buffer ( capacity: 0 )
248
+ badBuffer. writeInteger ( UInt8 ( 1 ) )
249
+ badBuffer. writeInteger ( UInt8 ( 1 ) )
250
+ _ = try ? self . channel. writeInbound ( HTTPServerRequestPart . body ( badBuffer) )
251
+ }
252
+ let errorHandler = ErrorRecordingHandler ( )
253
+ XCTAssertNoThrow ( try self . channel. pipeline. addHandlers ( [ onFirstRead, errorHandler] ) . wait ( ) )
254
+
255
+ // Now write.
256
+ XCTAssertNoThrow ( try self . channel. writeInbound ( HTTPServerRequestPart . body ( buffer) ) )
257
+
258
+ // We should have seen the original three messages
259
+ for message in serializedMessages {
260
+ let requestPart = try self . channel. readInbound ( as: _RawGRPCServerRequestPart. self)
261
+ switch requestPart {
262
+ case var . some( . message( buffer) ) :
263
+ XCTAssertEqual ( message, buffer. readData ( length: buffer. readableBytes) !)
264
+ default :
265
+ XCTFail ( " Unexpected request part: \( String ( describing: requestPart) ) " )
266
+ }
267
+ }
268
+
269
+ // We should have recorded only one error.
270
+ XCTAssertEqual ( errorHandler. errors. count, 1 )
271
+ }
130
272
}
0 commit comments