HTTPEncoder: fix 0 length chunks (#1524)
Motivation: Previously, if the user sent us a 0 length chunk, we would encode it as `0\r\n\r\n` which means the _end_ of the body in chunked encoding. But the end in NIO is send by sending `.end(...)`. Modifications: Don't write anything for 0 length writes. Result: More correct behaviour with 0 length body chunks.
This commit is contained in:
parent
b174051b81
commit
c1d86289ed
|
@ -143,13 +143,18 @@ public final class HTTPRequestEncoder: ChannelOutboundHandler, RemovableChannelH
|
|||
public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
|
||||
switch self.unwrapOutboundIn(data) {
|
||||
case .head(var request):
|
||||
|
||||
self.isChunked = sanitizeTransportHeaders(hasBody: request.method.hasRequestBody, headers: &request.headers, version: request.version) == .chunked
|
||||
|
||||
writeHead(wrapOutboundOut: self.wrapOutboundOut, writeStartLine: { buffer in
|
||||
buffer.write(request: request)
|
||||
}, context: context, headers: request.headers, promise: promise)
|
||||
case .body(let bodyPart):
|
||||
guard bodyPart.readableBytes > 0 else {
|
||||
// Empty writes shouldn't send any bytes in chunked or identity encoding.
|
||||
context.write(self.wrapOutboundOut(bodyPart), promise: promise)
|
||||
return
|
||||
}
|
||||
|
||||
writeChunk(wrapOutboundOut: self.wrapOutboundOut, context: context, isChunked: self.isChunked, chunk: bodyPart, promise: promise)
|
||||
case .end(let trailers):
|
||||
writeTrailers(wrapOutboundOut: self.wrapOutboundOut, context: context, isChunked: self.isChunked, trailers: trailers, promise: promise)
|
||||
|
|
|
@ -37,6 +37,10 @@ extension HTTPRequestEncoderTests {
|
|||
("testNoChunkedEncodingForHTTP10", testNoChunkedEncodingForHTTP10),
|
||||
("testBody", testBody),
|
||||
("testCONNECT", testCONNECT),
|
||||
("testChunkedEncodingIsTheDefault", testChunkedEncodingIsTheDefault),
|
||||
("testChunkedEncodingCanBetEnabled", testChunkedEncodingCanBetEnabled),
|
||||
("testChunkedEncodingDealsWithZeroLengthChunks", testChunkedEncodingDealsWithZeroLengthChunks),
|
||||
("testChunkedEncodingWorksIfNoPromisesAreAttachedToTheWrites", testChunkedEncodingWorksIfNoPromisesAreAttachedToTheWrites),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,6 +140,130 @@ class HTTPRequestEncoderTests: XCTestCase {
|
|||
assertOutboundContainsOnly(channel, "")
|
||||
}
|
||||
|
||||
func testChunkedEncodingIsTheDefault() {
|
||||
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
||||
var buffer = channel.allocator.buffer(capacity: 16)
|
||||
var expected = channel.allocator.buffer(capacity: 32)
|
||||
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/"))))
|
||||
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
buffer.writeString("foo")
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buffer))))
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("3\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
expected.clear()
|
||||
expected.writeString("foo")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
expected.clear()
|
||||
expected.writeString("\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("0\r\n\r\n")
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(nil)))
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
||||
}
|
||||
|
||||
func testChunkedEncodingCanBetEnabled() {
|
||||
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
||||
var buffer = channel.allocator.buffer(capacity: 16)
|
||||
var expected = channel.allocator.buffer(capacity: 32)
|
||||
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/",
|
||||
headers: ["TrAnSfEr-encoding": "chuNKED"]))))
|
||||
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
buffer.writeString("foo")
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buffer))))
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("3\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
expected.clear()
|
||||
expected.writeString("foo")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
expected.clear()
|
||||
expected.writeString("\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("0\r\n\r\n")
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(nil)))
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
||||
}
|
||||
|
||||
func testChunkedEncodingDealsWithZeroLengthChunks() {
|
||||
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
||||
var buffer = channel.allocator.buffer(capacity: 16)
|
||||
var expected = channel.allocator.buffer(capacity: 32)
|
||||
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/"))))
|
||||
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
buffer.clear()
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.body(.byteBuffer(buffer))))
|
||||
XCTAssertNoThrow(XCTAssertEqual(0, try channel.readOutbound(as: ByteBuffer.self)?.readableBytes))
|
||||
|
||||
XCTAssertNoThrow(try channel.writeOutbound(HTTPClientRequestPart.end(["foo": "bar"])))
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("0\r\nfoo: bar\r\n\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
||||
}
|
||||
|
||||
func testChunkedEncodingWorksIfNoPromisesAreAttachedToTheWrites() {
|
||||
let channel = EmbeddedChannel(handler: HTTPRequestEncoder())
|
||||
var buffer = channel.allocator.buffer(capacity: 16)
|
||||
var expected = channel.allocator.buffer(capacity: 32)
|
||||
|
||||
channel.write(HTTPClientRequestPart.head(.init(version: .init(major: 1, minor: 1),
|
||||
method: .POST,
|
||||
uri: "/")), promise: nil)
|
||||
channel.flush()
|
||||
expected.writeString("POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
buffer.writeString("foo")
|
||||
channel.write(HTTPClientRequestPart.body(.byteBuffer(buffer)), promise: nil)
|
||||
channel.flush()
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("3\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
expected.clear()
|
||||
expected.writeString("foo")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
expected.clear()
|
||||
expected.writeString("\r\n")
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
expected.clear()
|
||||
expected.writeString("0\r\n\r\n")
|
||||
channel.write(HTTPClientRequestPart.end(nil), promise: nil)
|
||||
channel.flush()
|
||||
XCTAssertNoThrow(XCTAssertEqual(expected, try channel.readOutbound(as: ByteBuffer.self)))
|
||||
|
||||
XCTAssertNoThrow(XCTAssertTrue(try channel.finish().isClean))
|
||||
}
|
||||
|
||||
private func assertOutboundContainsOnly(_ channel: EmbeddedChannel, _ expected: String) {
|
||||
XCTAssertNoThrow(XCTAssertNotNil(try channel.readOutbound(as: ByteBuffer.self).map { buffer in
|
||||
buffer.assertContainsOnly(expected)
|
||||
|
|
Loading…
Reference in New Issue