Skip to content

Commit 83a83e9

Browse files
authored
Merge branch 'master' into assume_chunked_on_zero_length_stream
2 parents d8fad2d + 8e60b94 commit 83a83e9

13 files changed

+237
-91
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
2222
],
2323
dependencies: [
24-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.16.0"),
24+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.18.0"),
2525
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.7.0"),
2626
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"),
2727
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.5.1"),

Sources/AsyncHTTPClient/ConnectionsState.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,10 @@ extension HTTP1ConnectionProvider {
229229
self.openedConnectionsCount -= 1
230230
return self.processNextWaiter()
231231
case .closed:
232-
assertionFailure("should not happen")
232+
// This can happen in the following scenario: user initiates a connection that will fail to connect,
233+
// user calls `syncShutdown` before we received an error from the bootstrap. In this scenario,
234+
// pool will be `.closed` but connection will be still in the process of being established/failed,
235+
// so then this process finishes, it will get to this point.
233236
return .none
234237
}
235238
}

Sources/AsyncHTTPClient/HTTPClient.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
925925
case uncleanShutdown
926926
case traceRequestWithBody
927927
case invalidHeaderFieldNames([String])
928+
case bodyLengthMismatch
928929
}
929930

930931
private var code: Code
@@ -969,10 +970,12 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
969970
public static let redirectLimitReached = HTTPClientError(code: .redirectLimitReached)
970971
/// Redirect Cycle detected.
971972
public static let redirectCycleDetected = HTTPClientError(code: .redirectCycleDetected)
972-
/// Unclean shutdown
973+
/// Unclean shutdown.
973974
public static let uncleanShutdown = HTTPClientError(code: .uncleanShutdown)
974-
/// A body was sent in a request with method TRACE
975+
/// A body was sent in a request with method TRACE.
975976
public static let traceRequestWithBody = HTTPClientError(code: .traceRequestWithBody)
976-
/// Header field names contain invalid characters
977+
/// Header field names contain invalid characters.
977978
public static func invalidHeaderFieldNames(_ names: [String]) -> HTTPClientError { return HTTPClientError(code: .invalidHeaderFieldNames(names)) }
979+
/// Body length is not equal to `Content-Length`.
980+
public static let bodyLengthMismatch = HTTPClientError(code: .bodyLengthMismatch)
978981
}

Sources/AsyncHTTPClient/HTTPHandler.swift

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ extension HTTPClient {
7777
/// - data: Body `Data` representation.
7878
public static func data(_ data: Data) -> Body {
7979
return Body(length: data.count) { writer in
80-
var buffer = ByteBufferAllocator().buffer(capacity: data.count)
81-
buffer.writeBytes(data)
82-
return writer.write(.byteBuffer(buffer))
80+
writer.write(.byteBuffer(ByteBuffer(bytes: data)))
8381
}
8482
}
8583

@@ -89,9 +87,7 @@ extension HTTPClient {
8987
/// - string: Body `String` representation.
9088
public static func string(_ string: String) -> Body {
9189
return Body(length: string.utf8.count) { writer in
92-
var buffer = ByteBufferAllocator().buffer(capacity: string.utf8.count)
93-
buffer.writeString(string)
94-
return writer.write(.byteBuffer(buffer))
90+
writer.write(.byteBuffer(ByteBuffer(string: string)))
9591
}
9692
}
9793
}
@@ -641,7 +637,7 @@ internal class TaskHandler<Delegate: HTTPClientResponseDelegate>: RemovableChann
641637
case head
642638
case redirected(HTTPResponseHead, URL)
643639
case body
644-
case end
640+
case endOrError
645641
}
646642

647643
let task: HTTPClient.Task<Delegate.Response>
@@ -651,6 +647,8 @@ internal class TaskHandler<Delegate: HTTPClientResponseDelegate>: RemovableChann
651647
let logger: Logger // We are okay to store the logger here because a TaskHandler is just for one request.
652648

653649
var state: State = .idle
650+
var expectedBodyLength: Int?
651+
var actualBodyLength: Int = 0
654652
var pendingRead = false
655653
var mayRead = true
656654
var closing = false {
@@ -771,16 +769,21 @@ extension TaskHandler: ChannelDuplexHandler {
771769
uri: request.uri)
772770
var headers = request.headers
773771

774-
if !request.headers.contains(name: "Host") {
775-
headers.add(name: "Host", value: request.host)
772+
if !request.headers.contains(name: "host") {
773+
let port = request.port
774+
var host = request.host
775+
if !(port == 80 && request.scheme == "http"), !(port == 443 && request.scheme == "https") {
776+
host += ":\(port)"
777+
}
778+
headers.add(name: "host", value: host)
776779
}
777780

778781
do {
779782
try headers.validate(method: request.method, body: request.body)
780783
} catch {
781784
promise?.fail(error)
782785
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
783-
self.state = .end
786+
self.state = .endOrError
784787
return
785788
}
786789

@@ -794,12 +797,23 @@ extension TaskHandler: ChannelDuplexHandler {
794797
assert(head.version == HTTPVersion(major: 1, minor: 1),
795798
"Sending a request in HTTP version \(head.version) which is unsupported by the above `if`")
796799

800+
let contentLengths = head.headers[canonicalForm: "content-length"]
801+
assert(contentLengths.count <= 1)
802+
803+
self.expectedBodyLength = contentLengths.first.flatMap { Int($0) }
804+
797805
context.write(wrapOutboundOut(.head(head))).map {
798806
self.callOutToDelegateFireAndForget(value: head, self.delegate.didSendRequestHead)
799807
}.flatMap {
800808
self.writeBody(request: request, context: context)
801809
}.flatMap {
802810
context.eventLoop.assertInEventLoop()
811+
if let expectedBodyLength = self.expectedBodyLength, expectedBodyLength != self.actualBodyLength {
812+
self.state = .endOrError
813+
let error = HTTPClientError.bodyLengthMismatch
814+
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
815+
return context.eventLoop.makeFailedFuture(error)
816+
}
803817
return context.writeAndFlush(self.wrapOutboundOut(.end(nil)))
804818
}.map {
805819
context.eventLoop.assertInEventLoop()
@@ -808,10 +822,10 @@ extension TaskHandler: ChannelDuplexHandler {
808822
}.flatMapErrorThrowing { error in
809823
context.eventLoop.assertInEventLoop()
810824
switch self.state {
811-
case .end:
825+
case .endOrError:
812826
break
813827
default:
814-
self.state = .end
828+
self.state = .endOrError
815829
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
816830
}
817831
throw error
@@ -828,9 +842,11 @@ extension TaskHandler: ChannelDuplexHandler {
828842
let promise = self.task.eventLoop.makePromise(of: Void.self)
829843
// All writes have to be switched to the channel EL if channel and task ELs differ
830844
if context.eventLoop.inEventLoop {
845+
self.actualBodyLength += part.readableBytes
831846
context.writeAndFlush(self.wrapOutboundOut(.body(part)), promise: promise)
832847
} else {
833848
context.eventLoop.execute {
849+
self.actualBodyLength += part.readableBytes
834850
context.writeAndFlush(self.wrapOutboundOut(.body(part)), promise: promise)
835851
}
836852
}
@@ -893,12 +909,12 @@ extension TaskHandler: ChannelDuplexHandler {
893909
case .end:
894910
switch self.state {
895911
case .redirected(let head, let redirectURL):
896-
self.state = .end
912+
self.state = .endOrError
897913
self.task.releaseAssociatedConnection(delegateType: Delegate.self, closing: self.closing).whenSuccess {
898914
self.redirectHandler?.redirect(status: head.status, to: redirectURL, promise: self.task.promise)
899915
}
900916
default:
901-
self.state = .end
917+
self.state = .endOrError
902918
self.callOutToDelegate(promise: self.task.promise, self.delegate.didFinishRequest)
903919
}
904920
}
@@ -913,14 +929,14 @@ extension TaskHandler: ChannelDuplexHandler {
913929
context.read()
914930
}
915931
case .failure(let error):
916-
self.state = .end
932+
self.state = .endOrError
917933
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
918934
}
919935
}
920936

921937
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
922938
if (event as? IdleStateHandler.IdleStateEvent) == .read {
923-
self.state = .end
939+
self.state = .endOrError
924940
let error = HTTPClientError.readTimeout
925941
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
926942
} else {
@@ -930,7 +946,7 @@ extension TaskHandler: ChannelDuplexHandler {
930946

931947
func triggerUserOutboundEvent(context: ChannelHandlerContext, event: Any, promise: EventLoopPromise<Void>?) {
932948
if (event as? TaskCancelEvent) != nil {
933-
self.state = .end
949+
self.state = .endOrError
934950
let error = HTTPClientError.cancelled
935951
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
936952
promise?.succeed(())
@@ -941,10 +957,10 @@ extension TaskHandler: ChannelDuplexHandler {
941957

942958
func channelInactive(context: ChannelHandlerContext) {
943959
switch self.state {
944-
case .end:
960+
case .endOrError:
945961
break
946962
case .body, .head, .idle, .redirected, .sent:
947-
self.state = .end
963+
self.state = .endOrError
948964
let error = HTTPClientError.remoteConnectionClosed
949965
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
950966
}
@@ -955,7 +971,7 @@ extension TaskHandler: ChannelDuplexHandler {
955971
switch error {
956972
case NIOSSLError.uncleanShutdown:
957973
switch self.state {
958-
case .end:
974+
case .endOrError:
959975
/// Some HTTP Servers can 'forget' to respond with CloseNotify when client is closing connection,
960976
/// this could lead to incomplete SSL shutdown. But since request is already processed, we can ignore this error.
961977
break
@@ -964,11 +980,11 @@ extension TaskHandler: ChannelDuplexHandler {
964980
/// We can also ignore this error like `.end`.
965981
break
966982
default:
967-
self.state = .end
983+
self.state = .endOrError
968984
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
969985
}
970986
default:
971-
self.state = .end
987+
self.state = .endOrError
972988
self.failTaskAndNotifyDelegate(error: error, self.delegate.didReceiveError)
973989
}
974990
}

Tests/AsyncHTTPClientTests/ConnectionPoolTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extension ConnectionPoolTests {
3434
("testAcquireReplace", testAcquireReplace),
3535
("testAcquireWhenUnavailableSpecificEL", testAcquireWhenUnavailableSpecificEL),
3636
("testAcquireWhenClosed", testAcquireWhenClosed),
37+
("testConnectFailedWhenClosed", testConnectFailedWhenClosed),
3738
("testReleaseAliveConnectionEmptyQueue", testReleaseAliveConnectionEmptyQueue),
3839
("testReleaseAliveButClosingConnectionEmptyQueue", testReleaseAliveButClosingConnectionEmptyQueue),
3940
("testReleaseInactiveConnectionEmptyQueue", testReleaseInactiveConnectionEmptyQueue),

Tests/AsyncHTTPClientTests/ConnectionPoolTests.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,7 @@ class ConnectionPoolTests: XCTestCase {
304304

305305
func testAcquireWhenClosed() {
306306
var state = HTTP1ConnectionProvider.ConnectionsState(eventLoop: self.eventLoop)
307-
var snapshot = state.testsOnly_getInternalState()
308-
snapshot.state = .closed
309-
state.testsOnly_setInternalState(snapshot)
307+
_ = state.close()
310308

311309
XCTAssertFalse(state.enqueue())
312310

@@ -320,6 +318,19 @@ class ConnectionPoolTests: XCTestCase {
320318
}
321319
}
322320

321+
func testConnectFailedWhenClosed() {
322+
var state = HTTP1ConnectionProvider.ConnectionsState(eventLoop: self.eventLoop)
323+
_ = state.close()
324+
325+
let action = state.connectFailed()
326+
switch action {
327+
case .none:
328+
break
329+
default:
330+
XCTFail("Unexpected action: \(action)")
331+
}
332+
}
333+
323334
// MARK: - Release Tests
324335

325336
func testReleaseAliveConnectionEmptyQueue() throws {

Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extension HTTPClientInternalTests {
2727
return [
2828
("testHTTPPartsHandler", testHTTPPartsHandler),
2929
("testBadHTTPRequest", testBadHTTPRequest),
30+
("testHostPort", testHostPort),
3031
("testHTTPPartsHandlerMultiBody", testHTTPPartsHandlerMultiBody),
3132
("testProxyStreaming", testProxyStreaming),
3233
("testProxyStreamingFailure", testProxyStreamingFailure),

0 commit comments

Comments
 (0)