diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 44f237a61..949b09e70 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -172,6 +172,17 @@ public class HTTPClient { return self.execute(request: request, delegate: accumulator, deadline: deadline).futureResult } + /// Execute arbitrary HTTP request using specified URL. + /// + /// - parameters: + /// - request: HTTP request to execute. + /// - eventLoop: NIO Event Loop preference. + /// - deadline: Point in time by which the request must complete. + public func execute(request: Request, eventLoop: EventLoopPreference, deadline: NIODeadline? = nil) -> EventLoopFuture { + let accumulator = ResponseAccumulator(request: request) + return self.execute(request: request, delegate: accumulator, eventLoop: eventLoop, deadline: deadline).futureResult + } + /// Execute arbitrary HTTP request and handle response processing using provided delegate. /// /// - parameters: @@ -180,7 +191,27 @@ public class HTTPClient { /// - deadline: Point in time by which the request must complete. public func execute(request: Request, delegate: T, deadline: NIODeadline? = nil) -> Task { let eventLoop = self.eventLoopGroup.next() + return self.execute(request: request, delegate: delegate, eventLoop: eventLoop, deadline: deadline) + } + + /// Execute arbitrary HTTP request and handle response processing using provided delegate. + /// + /// - parameters: + /// - request: HTTP request to execute. + /// - delegate: Delegate to process response parts. + /// - eventLoop: NIO Event Loop preference. + /// - deadline: Point in time by which the request must complete. + public func execute(request: Request, delegate: T, eventLoop: EventLoopPreference, deadline: NIODeadline? = nil) -> Task { + switch eventLoop.preference { + case .indifferent: + return self.execute(request: request, delegate: delegate, eventLoop: self.eventLoopGroup.next(), deadline: deadline) + case .prefers(let preferred): + precondition(self.eventLoopGroup.makeIterator().contains { $0 === preferred }, "Provided EventLoop must be part of clients EventLoopGroup.") + return self.execute(request: request, delegate: delegate, eventLoop: preferred, deadline: deadline) + } + } + private func execute(request: Request, delegate: T, eventLoop: EventLoop, deadline: NIODeadline? = nil) -> Task { let redirectHandler: RedirectHandler? if self.configuration.followRedirects { redirectHandler = RedirectHandler(request: request) { newRequest in @@ -312,6 +343,29 @@ public class HTTPClient { case createNew } + /// Specifies how the library will treat event loop passed by the user. + public struct EventLoopPreference { + enum Preference { + /// Event Loop will be selected by the library. + case indifferent + /// Library will try to use provided event loop if possible. + case prefers(EventLoop) + } + + var preference: Preference + + init(_ preference: Preference) { + self.preference = preference + } + + /// Event Loop will be selected by the library. + public static let indifferent = EventLoopPreference(.indifferent) + /// Library will try to use provided event loop if possible. + public static func prefers(_ eventLoop: EventLoop) -> EventLoopPreference { + return EventLoopPreference(.prefers(eventLoop)) + } + } + /// Timeout configuration public struct Timeout { /// Specifies connect timeout. diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index cb33ba8ed..93f66905b 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -53,6 +53,7 @@ extension HTTPClientTests { ("testNoResponseWithIgnoreErrorForSSLUncleanShutdown", testNoResponseWithIgnoreErrorForSSLUncleanShutdown), ("testWrongContentLengthForSSLUncleanShutdown", testWrongContentLengthForSSLUncleanShutdown), ("testWrongContentLengthWithIgnoreErrorForSSLUncleanShutdown", testWrongContentLengthWithIgnoreErrorForSSLUncleanShutdown), + ("testEventLoopArgument", testEventLoopArgument), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index ecd6c6209..c9b85e957 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -484,4 +484,41 @@ class HTTPClientTests: XCTestCase { } } } + + func testEventLoopArgument() throws { + let httpBin = HttpBin() + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + defer { + try! eventLoopGroup.syncShutdownGracefully() + httpBin.shutdown() + } + + class EventLoopValidatingDelegate: HTTPClientResponseDelegate { + typealias Response = Bool + + let eventLoop: EventLoop + var result = false + + init(eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + + func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) -> EventLoopFuture { + self.result = task.eventLoop === self.eventLoop + return task.eventLoop.makeSucceededFuture(()) + } + + func didFinishRequest(task: HTTPClient.Task) throws -> Bool { + return self.result + } + } + + let eventLoop = eventLoopGroup.next() + let delegate = EventLoopValidatingDelegate(eventLoop: eventLoop) + let request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/get") + let response = try httpClient.execute(request: request, delegate: delegate, eventLoop: .prefers(eventLoop)).wait() + + XCTAssertEqual(true, response) + } }