Skip to content

Commit

Permalink
Tolerate sending data after close(mode: .output) (#2421)
Browse files Browse the repository at this point in the history
Motivation

We shouldn't crash on somewhat likely user error.

Modifications

Pass on writes after close(mode: .output) instead of crashing.

Result

User code is more robust to weird edge cases.
  • Loading branch information
Lukasa committed May 9, 2023
1 parent 7f4d10b commit 2d8e6ca
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Sources/NIOHTTP1/HTTPServerPipelineHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ public final class HTTPServerPipelineHandler: ChannelDuplexHandler, RemovableCha
// We got a response while still receiving a request, which we have to
// wait for.
self = .requestEndPending
case .requestEndPending, .idle, .sentCloseOutput, .sentCloseOutputRequestEndPending:
case .sentCloseOutput, .sentCloseOutputRequestEndPending:
// This is a user error: they have sent close(mode: .output), but are continuing to write.
// The write will fail, so we can allow it to pass.
()
case .requestEndPending, .idle:
preconditionFailure("Unexpectedly received a response in state \(self)")
}
}
Expand Down
16 changes: 16 additions & 0 deletions Tests/NIOHTTP1Tests/HTTPServerPipelineHandlerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1165,4 +1165,20 @@ class HTTPServerPipelineHandlerTest: XCTestCase {
[.channelRead(HTTPServerRequestPart.head(self.requestHead)),
.channelRead(HTTPServerRequestPart.end(nil))])
}

func testWritesAfterCloseOutputAreDropped() throws {
// Send in a request
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.head(self.requestHead)))
XCTAssertNoThrow(try self.channel.writeInbound(HTTPServerRequestPart.end(nil)))

// Server sends a head.
XCTAssertNoThrow(try self.channel.writeOutbound(HTTPServerResponsePart.head(self.responseHead)))

// Now the server sends close output
XCTAssertNoThrow(try channel.close(mode: .output).wait())

// Now, in error, the server sends .body and .end. Both pass unannounced.
XCTAssertNoThrow(try self.channel.writeOutbound(HTTPServerResponsePart.body(.byteBuffer(ByteBuffer()))))
XCTAssertNoThrow(try self.channel.writeOutbound(HTTPServerResponsePart.end(nil)))
}
}

0 comments on commit 2d8e6ca

Please sign in to comment.