Skip to content

Commit fc817a8

Browse files
authored
Merge branch 'main' into add-request-response-delegate-method
2 parents 7057838 + ad262cc commit fc817a8

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

Sources/AsyncHTTPClient/HTTPHandler.swift

+8-2
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,7 @@ internal struct RedirectHandler<ResponseType> {
970970
status: HTTPResponseStatus,
971971
to redirectURL: URL,
972972
promise: EventLoopPromise<ResponseType>
973-
) {
973+
) -> HTTPClient.Task<ResponseType>? {
974974
do {
975975
var redirectState = self.redirectState
976976
try redirectState.redirect(to: redirectURL.absoluteString)
@@ -990,13 +990,19 @@ internal struct RedirectHandler<ResponseType> {
990990
headers: headers,
991991
body: body
992992
)
993-
self.execute(newRequest, redirectState).futureResult.whenComplete { result in
993+
994+
let newTask = self.execute(newRequest, redirectState)
995+
996+
newTask.futureResult.whenComplete { result in
994997
promise.futureResult.eventLoop.execute {
995998
promise.completeWith(result)
996999
}
9971000
}
1001+
1002+
return newTask
9981003
} catch {
9991004
promise.fail(error)
1005+
return nil
10001006
}
10011007
}
10021008
}

Sources/AsyncHTTPClient/RequestBag.swift

+8-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
4343
// the consume body part stack depth is synchronized on the task event loop.
4444
private var consumeBodyPartStackDepth: Int
4545

46+
// if a redirect occurs, we store the task for it so we can propagate cancellation
47+
private var redirectTask: HTTPClient.Task<Delegate.Response>? = nil
48+
4649
// MARK: HTTPClientTask properties
4750

4851
var logger: Logger {
@@ -236,7 +239,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
236239
executor.demandResponseBodyStream(self)
237240

238241
case .redirect(let executor, let handler, let head, let newURL):
239-
handler.redirect(status: head.status, to: newURL, promise: self.task.promise)
242+
self.redirectTask = handler.redirect(status: head.status, to: newURL, promise: self.task.promise)
240243
executor.cancelRequest(self)
241244

242245
case .forwardResponseHead(let head):
@@ -260,7 +263,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
260263
executor.demandResponseBodyStream(self)
261264

262265
case .redirect(let executor, let handler, let head, let newURL):
263-
handler.redirect(status: head.status, to: newURL, promise: self.task.promise)
266+
self.redirectTask = handler.redirect(status: head.status, to: newURL, promise: self.task.promise)
264267
executor.cancelRequest(self)
265268

266269
case .forwardResponsePart(let part):
@@ -296,7 +299,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
296299
}
297300

298301
case .redirect(let handler, let head, let newURL):
299-
handler.redirect(status: head.status, to: newURL, promise: self.task.promise)
302+
self.redirectTask = handler.redirect(status: head.status, to: newURL, promise: self.task.promise)
300303
}
301304
}
302305

@@ -360,6 +363,8 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
360363
let action = self.state.fail(error)
361364

362365
self.executeFailAction0(action)
366+
367+
self.redirectTask?.fail(reason: error)
363368
}
364369

365370
private func executeFailAction0(_ action: RequestBag<Delegate>.StateMachine.FailAction) {

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+64
Original file line numberDiff line numberDiff line change
@@ -4132,6 +4132,70 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
41324132
XCTAssertNoThrow(try client.execute(request: request).wait())
41334133
}
41344134

4135+
func testCancelingRequestAfterRedirect() throws {
4136+
let request = try Request(
4137+
url: self.defaultHTTPBinURLPrefix + "redirect/target",
4138+
method: .GET,
4139+
headers: ["X-Target-Redirect-URL": self.defaultHTTPBinURLPrefix + "wait"],
4140+
body: nil
4141+
)
4142+
4143+
class CancelAfterRedirect: HTTPClientResponseDelegate {
4144+
init() {}
4145+
func didFinishRequest(task: AsyncHTTPClient.HTTPClient.Task<Void>) throws {}
4146+
}
4147+
4148+
let task = defaultClient.execute(
4149+
request: request,
4150+
delegate: CancelAfterRedirect(),
4151+
deadline: .now() + .seconds(1)
4152+
)
4153+
4154+
// there is currently no HTTPClientResponseDelegate method to ensure the redirect occurs before we cancel, so we just sleep for 500ms
4155+
Thread.sleep(forTimeInterval: 0.5)
4156+
4157+
task.cancel()
4158+
4159+
XCTAssertThrowsError(try task.wait()) { error in
4160+
guard case let error = error as? HTTPClientError, error == .cancelled else {
4161+
return XCTFail("Should fail with cancelled")
4162+
}
4163+
}
4164+
}
4165+
4166+
func testFailingRequestAfterRedirect() throws {
4167+
let request = try Request(
4168+
url: self.defaultHTTPBinURLPrefix + "redirect/target",
4169+
method: .GET,
4170+
headers: ["X-Target-Redirect-URL": self.defaultHTTPBinURLPrefix + "wait"],
4171+
body: nil
4172+
)
4173+
4174+
class FailAfterRedirect: HTTPClientResponseDelegate {
4175+
init() {}
4176+
func didFinishRequest(task: AsyncHTTPClient.HTTPClient.Task<Void>) throws {}
4177+
}
4178+
4179+
let task = defaultClient.execute(
4180+
request: request,
4181+
delegate: FailAfterRedirect(),
4182+
deadline: .now() + .seconds(1)
4183+
)
4184+
4185+
// there is currently no HTTPClientResponseDelegate method to ensure the redirect occurs before we fail, so we just sleep for 500ms
4186+
Thread.sleep(forTimeInterval: 0.5)
4187+
4188+
struct TestError: Error {}
4189+
4190+
task.fail(reason: TestError())
4191+
4192+
XCTAssertThrowsError(try task.wait()) { error in
4193+
guard error is TestError else {
4194+
return XCTFail("Should fail with TestError")
4195+
}
4196+
}
4197+
}
4198+
41354199
func testCancelingHTTP1RequestAfterHeaderSend() throws {
41364200
var request = try HTTPClient.Request(url: self.defaultHTTPBin.baseURL + "/wait", method: .POST)
41374201
// non-empty body is important

0 commit comments

Comments
 (0)