Skip to content

Commit 4424dec

Browse files
committed
Failing tests
1 parent b0180d1 commit 4424dec

File tree

4 files changed

+85
-15
lines changed

4 files changed

+85
-15
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine+Demand.swift

+40-11
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,28 @@ extension HTTPRequestStateMachine {
5151
buffer.append(body)
5252
self.state = .waitingForBytes(buffer)
5353

54-
case .waitingForRead,
55-
.waitingForDemand,
56-
.waitingForReadOrDemand:
57-
preconditionFailure("How can we receive a body part, after a channelReadComplete, but no read has been forwarded yet. Invalid state: \(self.state)")
54+
// For all the following cases, please note:
55+
// Normally these code paths should never be hit. However there is one way to trigger
56+
// this:
57+
//
58+
// If the server decides to close a connection, NIO will forward all outstanding
59+
// `channelRead`s without waiting for a next `context.read` call. For this reason we
60+
// might receive further bytes, when we don't expect them here.
61+
62+
case .waitingForRead(var buffer):
63+
self.state = .modifying
64+
buffer.append(body)
65+
self.state = .waitingForRead(buffer)
66+
67+
case .waitingForDemand(var buffer):
68+
self.state = .modifying
69+
buffer.append(body)
70+
self.state = .waitingForDemand(buffer)
71+
72+
case .waitingForReadOrDemand(var buffer):
73+
self.state = .modifying
74+
buffer.append(body)
75+
self.state = .waitingForReadOrDemand(buffer)
5876

5977
case .modifying:
6078
preconditionFailure("Invalid state: \(self.state)")
@@ -134,15 +152,26 @@ extension HTTPRequestStateMachine {
134152
}
135153
}
136154

137-
mutating func end() -> CircularBuffer<ByteBuffer> {
155+
enum ConnectionAction {
156+
case none
157+
case close
158+
}
159+
160+
mutating func end() -> (CircularBuffer<ByteBuffer>, ConnectionAction) {
138161
switch self.state {
139162
case .waitingForBytes(let buffer):
140-
return buffer
141-
142-
case .waitingForReadOrDemand,
143-
.waitingForRead,
144-
.waitingForDemand:
145-
preconditionFailure("How can we receive a body end, after a channelReadComplete, but no read has been forwarded yet. Invalid state: \(self.state)")
163+
return (buffer, .none)
164+
165+
case .waitingForReadOrDemand(let buffer),
166+
.waitingForRead(let buffer),
167+
.waitingForDemand(let buffer):
168+
// Normally this code path should never be hit. However there is one way to trigger
169+
// this:
170+
//
171+
// If the server decides to close a connection, NIO will forward all outstanding
172+
// `channelRead`s without waiting for a next `context.read` call. For this reason we
173+
// might receive a call to `end()`, when we don't expect it here.
174+
return (buffer, .close)
146175

147176
case .modifying:
148177
preconditionFailure("Invalid state: \(self.state)")

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTPRequestStateMachine.swift

+9-4
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ struct HTTPRequestStateMachine {
523523
where head.status.code < 300:
524524

525525
return self.avoidingStateMachineCoW { state -> Action in
526-
let remainingBuffer = responseStreamState.end()
526+
let (remainingBuffer, _) = responseStreamState.end()
527527
state = .running(
528528
.streaming(expectedBodyLength: expectedBodyLength, sentBodyBytes: sentBodyBytes, producer: producerState),
529529
.endReceived
@@ -536,16 +536,21 @@ struct HTTPRequestStateMachine {
536536
assert(producerState == .paused, "Expected to have paused the request body stream, when the head was received. Invalid state: \(self.state)")
537537

538538
return self.avoidingStateMachineCoW { state -> Action in
539-
let remainingBuffer = responseStreamState.end()
539+
let (remainingBuffer, _) = responseStreamState.end()
540540
state = .finished
541541
return .succeedRequest(.close, remainingBuffer)
542542
}
543543

544544
case .running(.endSent, .receivingBody(_, var responseStreamState)):
545545
return self.avoidingStateMachineCoW { state -> Action in
546-
let remainingBuffer = responseStreamState.end()
546+
let (remainingBuffer, action) = responseStreamState.end()
547547
state = .finished
548-
return .succeedRequest(.none, remainingBuffer)
548+
switch action {
549+
case .none:
550+
return .succeedRequest(.none, remainingBuffer)
551+
case .close:
552+
return .succeedRequest(.close, remainingBuffer)
553+
}
549554
}
550555

551556
case .running(_, .endReceived), .finished:

Diff for: Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests+XCTest.swift

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ extension HTTPRequestStateMachineTests {
5050
("testReadTimeoutThatFiresToLateIsIgnored", testReadTimeoutThatFiresToLateIsIgnored),
5151
("testCancellationThatIsInvokedToLateIsIgnored", testCancellationThatIsInvokedToLateIsIgnored),
5252
("testErrorWhileRunningARequestClosesTheStream", testErrorWhileRunningARequestClosesTheStream),
53+
("testCanReadHTTP1_0ResponseWithoutBody", testCanReadHTTP1_0ResponseWithoutBody),
54+
("testCanReadHTTP1_0ResponseWithBody", testCanReadHTTP1_0ResponseWithBody),
5355
]
5456
}
5557
}

Diff for: Tests/AsyncHTTPClientTests/HTTPRequestStateMachineTests.swift

+34
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,40 @@ class HTTPRequestStateMachineTests: XCTestCase {
451451
XCTAssertEqual(state.errorHappened(NIOSSLError.uncleanShutdown), .failRequest(NIOSSLError.uncleanShutdown, .close))
452452
XCTAssertEqual(state.requestCancelled(), .wait, "A cancellation that happens to late is ignored")
453453
}
454+
455+
func testCanReadHTTP1_0ResponseWithoutBody() {
456+
var state = HTTPRequestStateMachine(isChannelWritable: true)
457+
let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/")
458+
let metadata = RequestFramingMetadata(connectionClose: false, body: .none)
459+
XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false))
460+
461+
let responseHead = HTTPResponseHead(version: .http1_0, status: .internalServerError)
462+
XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false))
463+
XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait)
464+
XCTAssertEqual(state.channelReadComplete(), .wait)
465+
XCTAssertEqual(state.read(), .read)
466+
XCTAssertEqual(state.channelReadComplete(), .wait)
467+
XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, []))
468+
XCTAssertEqual(state.channelInactive(), .wait)
469+
}
470+
471+
func testCanReadHTTP1_0ResponseWithBody() {
472+
var state = HTTPRequestStateMachine(isChannelWritable: true)
473+
let requestHead = HTTPRequestHead(version: .http1_1, method: .GET, uri: "/")
474+
let metadata = RequestFramingMetadata(connectionClose: false, body: .none)
475+
XCTAssertEqual(state.startRequest(head: requestHead, metadata: metadata), .sendRequestHead(requestHead, startBody: false))
476+
477+
let responseHead = HTTPResponseHead(version: .http1_0, status: .internalServerError)
478+
let body = ByteBuffer(string: "foo bar")
479+
XCTAssertEqual(state.channelRead(.head(responseHead)), .forwardResponseHead(responseHead, pauseRequestBodyStream: false))
480+
XCTAssertEqual(state.demandMoreResponseBodyParts(), .wait)
481+
XCTAssertEqual(state.channelReadComplete(), .wait)
482+
XCTAssertEqual(state.read(), .read)
483+
XCTAssertEqual(state.channelReadComplete(), .wait)
484+
XCTAssertEqual(state.channelRead(.body(body)), .wait)
485+
XCTAssertEqual(state.channelRead(.end(nil)), .succeedRequest(.close, [body]))
486+
XCTAssertEqual(state.channelInactive(), .wait)
487+
}
454488
}
455489

456490
extension HTTPRequestStateMachine.Action: Equatable {

0 commit comments

Comments
 (0)