diff --git a/Sources/NIOHTTPClient/HTTPCookie.swift b/Sources/NIOHTTPClient/HTTPCookie.swift index 327c7f3b3..0af84d4fa 100644 --- a/Sources/NIOHTTPClient/HTTPCookie.swift +++ b/Sources/NIOHTTPClient/HTTPCookie.swift @@ -110,7 +110,7 @@ public struct HTTPCookie { } } -public extension HTTPResponse { +public extension HTTPClient.Response { internal var cookieHeaders: [HTTPHeaders.Element] { return headers.filter { $0.name.lowercased() == "set-cookie" } } diff --git a/Sources/NIOHTTPClient/HTTPHandler.swift b/Sources/NIOHTTPClient/HTTPHandler.swift index 9afc6eaf3..4f1acbe22 100644 --- a/Sources/NIOHTTPClient/HTTPHandler.swift +++ b/Sources/NIOHTTPClient/HTTPHandler.swift @@ -18,133 +18,86 @@ import NIOConcurrencyHelpers import NIOHTTP1 import NIOSSL -protocol HTTPClientError: Error {} - -public struct HTTPClientErrors { - public struct InvalidURLError: HTTPClientError {} - - public struct EmptyHostError: HTTPClientError {} - - public struct AlreadyShutdown: HTTPClientError {} - - public struct EmptySchemeError: HTTPClientError {} - - public struct UnsupportedSchemeError: HTTPClientError { - var scheme: String +public extension HTTPClient { + enum Body: Equatable { + case byteBuffer(ByteBuffer) + case data(Data) + case string(String) + + var length: Int { + switch self { + case .byteBuffer(let buffer): + return buffer.readableBytes + case .data(let data): + return data.count + case .string(let string): + return string.utf8.count + } + } } - public struct ReadTimeoutError: HTTPClientError {} - - public struct RemoteConnectionClosedError: HTTPClientError {} - - public struct CancelledError: HTTPClientError {} -} + struct Request: Equatable { + public var version: HTTPVersion + public var method: HTTPMethod + public var url: URL + public var scheme: String + public var host: String + public var headers: HTTPHeaders + public var body: Body? + + public init(url: String, version: HTTPVersion = HTTPVersion(major: 1, minor: 1), method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + guard let url = URL(string: url) else { + throw HTTPClientError.invalidURL + } -public enum HTTPBody: Equatable { - case byteBuffer(ByteBuffer) - case data(Data) - case string(String) - - var length: Int { - switch self { - case .byteBuffer(let buffer): - return buffer.readableBytes - case .data(let data): - return data.count - case .string(let string): - return string.utf8.count + try self.init(url: url, version: version, method: method, headers: headers, body: body) } - } -} -public struct HTTPRequest: Equatable { - public var version: HTTPVersion - public var method: HTTPMethod - public var url: URL - public var scheme: String - public var host: String - public var headers: HTTPHeaders - public var body: HTTPBody? - - public init(url: String, version: HTTPVersion = HTTPVersion(major: 1, minor: 1), method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: HTTPBody? = nil) throws { - guard let url = URL(string: url) else { - throw HTTPClientErrors.InvalidURLError() - } + public init(url: URL, version: HTTPVersion, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + guard let scheme = url.scheme else { + throw HTTPClientError.emptyScheme + } - try self.init(url: url, version: version, method: method, headers: headers, body: body) - } + guard Request.isSchemeSupported(scheme: scheme) else { + throw HTTPClientError.unsupportedScheme(scheme) + } - public init(url: URL, version: HTTPVersion, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: HTTPBody? = nil) throws { - guard let scheme = url.scheme else { - throw HTTPClientErrors.EmptySchemeError() - } + guard let host = url.host else { + throw HTTPClientError.emptyHost + } - guard HTTPRequest.isSchemeSupported(scheme: scheme) else { - throw HTTPClientErrors.UnsupportedSchemeError(scheme: scheme) + self.version = version + self.method = method + self.url = url + self.scheme = scheme + self.host = host + self.headers = headers + self.body = body } - guard let host = url.host else { - throw HTTPClientErrors.EmptyHostError() + public var useTLS: Bool { + return self.url.scheme == "https" } - self.version = version - self.method = method - self.url = url - self.scheme = scheme - self.host = host - self.headers = headers - self.body = body - } - - public var useTLS: Bool { - return self.url.scheme == "https" - } + public var port: Int { + return self.url.port ?? (self.useTLS ? 443 : 80) + } - public var port: Int { - return self.url.port ?? (self.useTLS ? 443 : 80) + static func isSchemeSupported(scheme: String?) -> Bool { + return scheme == "http" || scheme == "https" + } } - static func isSchemeSupported(scheme: String?) -> Bool { - return scheme == "http" || scheme == "https" + struct Response: Equatable { + public var host: String + public var status: HTTPResponseStatus + public var headers: HTTPHeaders + public var body: ByteBuffer? } } -public struct HTTPResponse: Equatable { - public var host: String - public var status: HTTPResponseStatus - public var headers: HTTPHeaders - public var body: ByteBuffer? -} - -/// This delegate is strongly held by the HTTPTaskHandler -/// for the duration of the HTTPRequest processing and will be -/// released together with the HTTPTaskHandler when channel is closed -public protocol HTTPResponseDelegate: class { - associatedtype Response - - func didTransmitRequestBody(task: HTTPTask) - - func didReceiveHead(task: HTTPTask, _ head: HTTPResponseHead) - - func didReceivePart(task: HTTPTask, _ buffer: ByteBuffer) - - func didReceiveError(task: HTTPTask, _ error: Error) - - func didFinishRequest(task: HTTPTask) throws -> Response -} - -extension HTTPResponseDelegate { - func didTransmitRequestBody(task: HTTPTask) {} - - func didReceiveHead(task: HTTPTask, _: HTTPResponseHead) {} - - func didReceivePart(task: HTTPTask, _: ByteBuffer) {} - - func didReceiveError(task: HTTPTask, _: Error) {} -} - -class HTTPResponseAccumulator: HTTPResponseDelegate { - typealias Response = HTTPResponse +internal class ResponseAccumulator: HTTPClientResponseDelegate { + public typealias Response = HTTPClient.Response enum State { case idle @@ -155,15 +108,15 @@ class HTTPResponseAccumulator: HTTPResponseDelegate { } var state = State.idle - let request: HTTPRequest + let request: HTTPClient.Request - init(request: HTTPRequest) { + init(request: HTTPClient.Request) { self.request = request } - func didTransmitRequestBody(task: HTTPTask) {} + func didTransmitRequestBody(task: HTTPClient.Task) {} - func didReceiveHead(task: HTTPTask, _ head: HTTPResponseHead) { + func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) { switch self.state { case .idle: self.state = .head(head) @@ -178,7 +131,7 @@ class HTTPResponseAccumulator: HTTPResponseDelegate { } } - func didReceivePart(task: HTTPTask, _ part: ByteBuffer) { + func didReceivePart(task: HTTPClient.Task, _ part: ByteBuffer) { switch self.state { case .idle: preconditionFailure("no head received before body") @@ -195,18 +148,18 @@ class HTTPResponseAccumulator: HTTPResponseDelegate { } } - func didReceiveError(task: HTTPTask, _ error: Error) { + func didReceiveError(task: HTTPClient.Task, _ error: Error) { self.state = .error(error) } - func didFinishRequest(task: HTTPTask) throws -> HTTPResponse { + func didFinishRequest(task: HTTPClient.Task) throws -> Response { switch self.state { case .idle: preconditionFailure("no head received before end") case .head(let head): - return HTTPResponse(host: self.request.host, status: head.status, headers: head.headers, body: nil) + return Response(host: self.request.host, status: head.status, headers: head.headers, body: nil) case .body(let head, let body): - return HTTPResponse(host: self.request.host, status: head.status, headers: head.headers, body: body) + return Response(host: self.request.host, status: head.status, headers: head.headers, body: body) case .end: preconditionFailure("request already processed") case .error(let error): @@ -215,6 +168,33 @@ class HTTPResponseAccumulator: HTTPResponseDelegate { } } +/// This delegate is strongly held by the HTTPTaskHandler +/// for the duration of the HTTPRequest processing and will be +/// released together with the HTTPTaskHandler when channel is closed +public protocol HTTPClientResponseDelegate: class { + associatedtype Response + + func didTransmitRequestBody(task: HTTPClient.Task) + + func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) + + func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) + + func didReceiveError(task: HTTPClient.Task, _ error: Error) + + func didFinishRequest(task: HTTPClient.Task) throws -> Response +} + +extension HTTPClientResponseDelegate { + func didTransmitRequestBody(task: HTTPClient.Task) {} + + func didReceiveHead(task: HTTPClient.Task, _: HTTPResponseHead) {} + + func didReceivePart(task: HTTPClient.Task, _: ByteBuffer) {} + + func didReceiveError(task: HTTPClient.Task, _: Error) {} +} + internal extension URL { var uri: String { return path.isEmpty ? "/" : path + (query.map { "?" + $0 } ?? "") @@ -225,48 +205,50 @@ internal extension URL { } } -struct CancelEvent {} +public extension HTTPClient { + final class Task { + let future: EventLoopFuture -public final class HTTPTask { - let future: EventLoopFuture + private var channel: Channel? + private var cancelled: Bool + private let lock: Lock - private var channel: Channel? - private var cancelled: Bool - private let lock: Lock - - init(future: EventLoopFuture) { - self.future = future - self.cancelled = false - self.lock = Lock() - } + init(future: EventLoopFuture) { + self.future = future + self.cancelled = false + self.lock = Lock() + } - func setChannel(_ channel: Channel) -> Channel { - return self.lock.withLock { - self.channel = channel - return channel + func setChannel(_ channel: Channel) -> Channel { + return self.lock.withLock { + self.channel = channel + return channel + } } - } - public func wait() throws -> Response { - return try self.future.wait() - } + public func wait() throws -> Response { + return try self.future.wait() + } - public func cancel() { - self.lock.withLock { - if !cancelled { - cancelled = true - channel?.pipeline.fireUserInboundEventTriggered(CancelEvent()) + public func cancel() { + self.lock.withLock { + if !cancelled { + cancelled = true + channel?.pipeline.fireUserInboundEventTriggered(TaskCancelEvent()) + } } } - } - public func cascade(promise: EventLoopPromise) { - self.future.cascade(to: promise) + public func cascade(promise: EventLoopPromise) { + self.future.cascade(to: promise) + } } } -class HTTPTaskHandler: ChannelInboundHandler, ChannelOutboundHandler { - typealias OutboundIn = HTTPRequest +internal struct TaskCancelEvent {} + +internal class TaskHandler: ChannelInboundHandler, ChannelOutboundHandler { + typealias OutboundIn = HTTPClient.Request typealias InboundIn = HTTPClientResponsePart typealias OutboundOut = HTTPClientRequestPart @@ -279,14 +261,14 @@ class HTTPTaskHandler: ChannelInboundHandler, ChannelOu case end } - let task: HTTPTask + let task: HTTPClient.Task let delegate: T let promise: EventLoopPromise let redirectHandler: RedirectHandler? var state: State = .idle - init(task: HTTPTask, delegate: T, promise: EventLoopPromise, redirectHandler: RedirectHandler?) { + init(task: HTTPClient.Task, delegate: T, promise: EventLoopPromise, redirectHandler: RedirectHandler?) { self.task = task self.delegate = delegate self.promise = promise @@ -378,12 +360,12 @@ class HTTPTaskHandler: ChannelInboundHandler, ChannelOu func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { if (event as? IdleStateHandler.IdleStateEvent) == .read { self.state = .end - let error = HTTPClientErrors.ReadTimeoutError() + let error = HTTPClientError.readTimeout delegate.didReceiveError(task: self.task, error) promise.fail(error) - } else if (event as? CancelEvent) != nil { + } else if (event as? TaskCancelEvent) != nil { self.state = .end - let error = HTTPClientErrors.CancelledError() + let error = HTTPClientError.cancelled delegate.didReceiveError(task: self.task, error) promise.fail(error) } else { @@ -397,7 +379,7 @@ class HTTPTaskHandler: ChannelInboundHandler, ChannelOu break default: self.state = .end - let error = HTTPClientErrors.RemoteConnectionClosedError() + let error = HTTPClientError.remoteConnectionClosed delegate.didReceiveError(task: self.task, error) promise.fail(error) } @@ -424,9 +406,9 @@ class HTTPTaskHandler: ChannelInboundHandler, ChannelOu } } -struct RedirectHandler { - let request: HTTPRequest - let execute: ((HTTPRequest) -> HTTPTask) +internal struct RedirectHandler { + let request: HTTPClient.Request + let execute: ((HTTPClient.Request) -> HTTPClient.Task) func redirectTarget(status: HTTPResponseStatus, headers: HTTPHeaders) -> URL? { switch status { @@ -444,7 +426,7 @@ struct RedirectHandler { return nil } - guard HTTPRequest.isSchemeSupported(scheme: url.scheme) else { + guard HTTPClient.Request.isSchemeSupported(scheme: url.scheme) else { return nil } diff --git a/Sources/NIOHTTPClient/RequestValidation.swift b/Sources/NIOHTTPClient/RequestValidation.swift index 8dc65a36e..e4b921494 100644 --- a/Sources/NIOHTTPClient/RequestValidation.swift +++ b/Sources/NIOHTTPClient/RequestValidation.swift @@ -15,21 +15,15 @@ import NIO import NIOHTTP1 -extension HTTPClientErrors { - public struct IdentityCodingIncorrectlyPresentError: HTTPClientError {} - - public struct ChunkedSpecifiedMultipleTimesError: HTTPClientError {} -} - extension HTTPHeaders { - mutating func validate(body: HTTPBody?) throws { + mutating func validate(body: HTTPClient.Body?) throws { // validate transfer encoding and content length (https://tools.ietf.org/html/rfc7230#section-3.3.1) var transferEncoding: String? var contentLength: Int? let encodings = self[canonicalForm: "Transfer-Encoding"].map { $0.lowercased() } guard !encodings.contains("identity") else { - throw HTTPClientErrors.IdentityCodingIncorrectlyPresentError() + throw HTTPClientError.identityCodingIncorrectlyPresent } self.remove(name: "Transfer-Encoding") @@ -37,7 +31,7 @@ extension HTTPHeaders { if let body = body { guard (encodings.filter { $0 == "chunked" }.count <= 1) else { - throw HTTPClientErrors.ChunkedSpecifiedMultipleTimesError() + throw HTTPClientError.chunkedSpecifiedMultipleTimes } if encodings.isEmpty { diff --git a/Sources/NIOHTTPClient/SwiftNIOHTTP.swift b/Sources/NIOHTTPClient/SwiftNIOHTTP.swift index 07d8640d2..188c8b15d 100644 --- a/Sources/NIOHTTPClient/SwiftNIOHTTP.swift +++ b/Sources/NIOHTTPClient/SwiftNIOHTTP.swift @@ -23,42 +23,14 @@ public enum EventLoopGroupProvider { case createNew } -public struct Timeout { - public var connect: TimeAmount? - public var read: TimeAmount? - - public init(connectTimeout: TimeAmount? = nil, readTimeout: TimeAmount? = nil) { - self.connect = connectTimeout - self.read = readTimeout - } -} - -public struct HTTPClientConfiguration { - public var tlsConfiguration: TLSConfiguration? - public var followRedirects: Bool - public var timeout: Timeout - - public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout()) { - self.tlsConfiguration = tlsConfiguration - self.followRedirects = followRedirects - self.timeout = timeout - } - - public init(certificateVerification: CertificateVerification, followRedirects: Bool = false, timeout: Timeout = Timeout()) { - self.tlsConfiguration = TLSConfiguration.forClient(certificateVerification: certificateVerification) - self.followRedirects = followRedirects - self.timeout = timeout - } -} - public class HTTPClient { let eventLoopGroupProvider: EventLoopGroupProvider let group: EventLoopGroup - let configuration: HTTPClientConfiguration + let configuration: Configuration let isShutdown = Atomic(value: false) - public init(eventLoopGroupProvider: EventLoopGroupProvider, configuration: HTTPClientConfiguration = HTTPClientConfiguration()) { + public init(eventLoopGroupProvider: EventLoopGroupProvider, configuration: Configuration = Configuration()) { self.eventLoopGroupProvider = eventLoopGroupProvider switch self.eventLoopGroupProvider { case .shared(let group): @@ -87,53 +59,53 @@ public class HTTPClient { if self.isShutdown.compareAndExchange(expected: false, desired: true) { try self.group.syncShutdownGracefully() } else { - throw HTTPClientErrors.AlreadyShutdown() + throw HTTPClientError.alreadyShutdown } } } - public func get(url: String, timeout: Timeout? = nil) -> EventLoopFuture { + public func get(url: String, timeout: Timeout? = nil) -> EventLoopFuture { do { - let request = try HTTPRequest(url: url, method: .GET) + let request = try Request(url: url, method: .GET) return self.execute(request: request) } catch { return self.group.next().makeFailedFuture(error) } } - public func post(url: String, body: HTTPBody? = nil, timeout: Timeout? = nil) -> EventLoopFuture { + public func post(url: String, body: Body? = nil, timeout: Timeout? = nil) -> EventLoopFuture { do { - let request = try HTTPRequest(url: url, method: .POST, body: body) + let request = try HTTPClient.Request(url: url, method: .POST, body: body) return self.execute(request: request) } catch { return self.group.next().makeFailedFuture(error) } } - public func put(url: String, body: HTTPBody? = nil, timeout: Timeout? = nil) -> EventLoopFuture { + public func put(url: String, body: Body? = nil, timeout: Timeout? = nil) -> EventLoopFuture { do { - let request = try HTTPRequest(url: url, method: .PUT, body: body) + let request = try HTTPClient.Request(url: url, method: .PUT, body: body) return self.execute(request: request) } catch { return self.group.next().makeFailedFuture(error) } } - public func delete(url: String, timeout: Timeout? = nil) -> EventLoopFuture { + public func delete(url: String, timeout: Timeout? = nil) -> EventLoopFuture { do { - let request = try HTTPRequest(url: url, method: .DELETE) + let request = try Request(url: url, method: .DELETE) return self.execute(request: request) } catch { return self.group.next().makeFailedFuture(error) } } - public func execute(request: HTTPRequest, timeout: Timeout? = nil) -> EventLoopFuture { - let accumulator = HTTPResponseAccumulator(request: request) + public func execute(request: Request, timeout: Timeout? = nil) -> EventLoopFuture { + let accumulator = ResponseAccumulator(request: request) return self.execute(request: request, delegate: accumulator, timeout: timeout).future } - public func execute(request: HTTPRequest, delegate: T, timeout: Timeout? = nil) -> HTTPTask { + public func execute(request: Request, delegate: T, timeout: Timeout? = nil) -> Task { let timeout = timeout ?? configuration.timeout let promise: EventLoopPromise = group.next().makePromise() @@ -147,7 +119,7 @@ public class HTTPClient { redirectHandler = nil } - let task = HTTPTask(future: promise.futureResult) + let task = Task(future: promise.futureResult) var bootstrap = ClientBootstrap(group: group) .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) @@ -161,7 +133,7 @@ public class HTTPClient { return channel.eventLoop.makeSucceededFuture(()) } }.flatMap { - channel.pipeline.addHandler(HTTPTaskHandler(task: task, delegate: delegate, promise: promise, redirectHandler: redirectHandler)) + channel.pipeline.addHandler(TaskHandler(task: task, delegate: delegate, promise: promise, redirectHandler: redirectHandler)) } } @@ -197,4 +169,68 @@ public class HTTPClient { return channel.eventLoop.makeSucceededFuture(()) } } + + public struct Configuration { + public var tlsConfiguration: TLSConfiguration? + public var followRedirects: Bool + public var timeout: Timeout + + public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout()) { + self.tlsConfiguration = tlsConfiguration + self.followRedirects = followRedirects + self.timeout = timeout + } + + public init(certificateVerification: CertificateVerification, followRedirects: Bool = false, timeout: Timeout = Timeout()) { + self.tlsConfiguration = TLSConfiguration.forClient(certificateVerification: certificateVerification) + self.followRedirects = followRedirects + self.timeout = timeout + } + } + + public struct Timeout { + public var connect: TimeAmount? + public var read: TimeAmount? + + public init(connect: TimeAmount? = nil, read: TimeAmount? = nil) { + self.connect = connect + self.read = read + } + } +} + +public struct HTTPClientError: Error, Equatable, CustomStringConvertible { + private enum Code: Equatable { + case invalidURL + case emptyHost + case alreadyShutdown + case emptyScheme + case unsupportedScheme(String) + case readTimeout + case remoteConnectionClosed + case cancelled + case identityCodingIncorrectlyPresent + case chunkedSpecifiedMultipleTimes + } + + private var code: Code + + private init(code: Code) { + self.code = code + } + + public var description: String { + return "HTTPClientError.\(String(describing: self.code))" + } + + public static let invalidURL = HTTPClientError(code: .invalidURL) + public static let emptyHost = HTTPClientError(code: .emptyHost) + public static let alreadyShutdown = HTTPClientError(code: .alreadyShutdown) + public static let emptyScheme = HTTPClientError(code: .emptyScheme) + public static func unsupportedScheme(_ scheme: String) -> HTTPClientError { return HTTPClientError(code: .unsupportedScheme(scheme)) } + public static let readTimeout = HTTPClientError(code: .readTimeout) + public static let remoteConnectionClosed = HTTPClientError(code: .remoteConnectionClosed) + public static let cancelled = HTTPClientError(code: .cancelled) + public static let identityCodingIncorrectlyPresent = HTTPClientError(code: .identityCodingIncorrectlyPresent) + public static let chunkedSpecifiedMultipleTimes = HTTPClientError(code: .chunkedSpecifiedMultipleTimes) } diff --git a/Sources/NIOHTTPClient/Utils.swift b/Sources/NIOHTTPClient/Utils.swift index fd670d5bd..94c515679 100644 --- a/Sources/NIOHTTPClient/Utils.swift +++ b/Sources/NIOHTTPClient/Utils.swift @@ -15,7 +15,7 @@ import NIO import NIOHTTP1 -public class HandlingHTTPResponseDelegate: HTTPResponseDelegate { +public class HandlingHTTPResponseDelegate: HTTPClientResponseDelegate { struct EmptyEndHandlerError: Error {} public typealias Result = T @@ -25,27 +25,27 @@ public class HandlingHTTPResponseDelegate: HTTPResponseDelegate { var handleError: ((Error) -> Void)? var handleEnd: (() throws -> T)? - public func didTransmitRequestBody(task: HTTPTask) {} + public func didTransmitRequestBody(task: HTTPClient.Task) {} - public func didReceiveHead(task: HTTPTask, _ head: HTTPResponseHead) { + public func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) { if let handler = handleHead { handler(head) } } - public func didReceivePart(task: HTTPTask, _ buffer: ByteBuffer) { + public func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) { if let handler = handleBody { handler(buffer) } } - public func didReceiveError(task: HTTPTask, _ error: Error) { + public func didReceiveError(task: HTTPClient.Task, _ error: Error) { if let handler = handleError { handler(error) } } - public func didFinishRequest(task: HTTPTask) throws -> T { + public func didFinishRequest(task: HTTPClient.Task) throws -> T { if let handler = handleEnd { return try handler() } diff --git a/Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift b/Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift index aa537fd30..d93fb6a14 100644 --- a/Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift @@ -18,16 +18,16 @@ import NIOHTTP1 @testable import NIOHTTPClient import NIOSSL -class TestHTTPDelegate: HTTPResponseDelegate { +class TestHTTPDelegate: HTTPClientResponseDelegate { typealias Response = Void - var state = HTTPResponseAccumulator.State.idle + var state = ResponseAccumulator.State.idle - func didReceiveHead(task: HTTPTask, _ head: HTTPResponseHead) { + func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) { self.state = .head(head) } - func didReceivePart(task: HTTPTask, _ buffer: ByteBuffer) { + func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) { switch self.state { case .head(let head): self.state = .body(head, buffer) @@ -40,15 +40,15 @@ class TestHTTPDelegate: HTTPResponseDelegate { } } - func didFinishRequest(task: HTTPTask) throws {} + func didFinishRequest(task: HTTPClient.Task) throws {} } -class CountingDelegate: HTTPResponseDelegate { +class CountingDelegate: HTTPClientResponseDelegate { typealias Response = Int var count = 0 - func didReceivePart(task: HTTPTask, _ buffer: ByteBuffer) { + func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) { var buffer = buffer let str = buffer.readString(length: buffer.readableBytes) if str?.starts(with: "id:") ?? false { @@ -56,7 +56,7 @@ class CountingDelegate: HTTPResponseDelegate { } } - func didFinishRequest(task: HTTPTask) throws -> Int { + func didFinishRequest(task: HTTPClient.Task) throws -> Int { return self.count } } diff --git a/Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift b/Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift index 4bf9f7d2c..d2d6fd520 100644 --- a/Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift +++ b/Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift @@ -20,14 +20,18 @@ import NIOSSL import XCTest class SwiftHTTPTests: XCTestCase { + typealias Request = HTTPClient.Request + typealias Response = HTTPClient.Response + typealias Task = HTTPClient.Task + func testRequestURI() throws { - let request1 = try HTTPRequest(url: "https://someserver.com:8888/some/path?foo=bar") + let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar") XCTAssertEqual(request1.host, "someserver.com") XCTAssertEqual(request1.url.uri, "/some/path?foo=bar") XCTAssertEqual(request1.port, 8888) XCTAssertTrue(request1.useTLS) - let request2 = try HTTPRequest(url: "https://someserver.com") + let request2 = try Request(url: "https://someserver.com") XCTAssertEqual(request2.url.uri, "/") } @@ -35,12 +39,12 @@ class SwiftHTTPTests: XCTestCase { let channel = EmbeddedChannel() let recorder = RecordingHandler() let promise: EventLoopPromise = channel.eventLoop.makePromise() - let task = HTTPTask(future: promise.futureResult) + let task = Task(future: promise.futureResult) try channel.pipeline.addHandler(recorder).wait() - try channel.pipeline.addHandler(HTTPTaskHandler(task: task, delegate: TestHTTPDelegate(), promise: promise, redirectHandler: nil)).wait() + try channel.pipeline.addHandler(TaskHandler(task: task, delegate: TestHTTPDelegate(), promise: promise, redirectHandler: nil)).wait() - var request = try HTTPRequest(url: "http://localhost/get") + var request = try Request(url: "http://localhost/get") request.headers.add(name: "X-Test-Header", value: "X-Test-Value") request.body = .string("1234") @@ -63,8 +67,8 @@ class SwiftHTTPTests: XCTestCase { let channel = EmbeddedChannel() let delegate = TestHTTPDelegate() let promise: EventLoopPromise = channel.eventLoop.makePromise() - let task = HTTPTask(future: promise.futureResult) - let handler = HTTPTaskHandler(task: task, delegate: delegate, promise: promise, redirectHandler: nil) + let task = Task(future: promise.futureResult) + let handler = TaskHandler(task: task, delegate: delegate, promise: promise, redirectHandler: nil) try channel.pipeline.addHandler(handler).wait() @@ -117,7 +121,7 @@ class SwiftHTTPTests: XCTestCase { func testGetHttps() throws { let httpBin = HttpBin(ssl: true) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, - configuration: HTTPClientConfiguration(certificateVerification: .none)) + configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { try! httpClient.syncShutdown() httpBin.shutdown() @@ -130,13 +134,13 @@ class SwiftHTTPTests: XCTestCase { func testPostHttps() throws { let httpBin = HttpBin(ssl: true) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, - configuration: HTTPClientConfiguration(certificateVerification: .none)) + configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { try! httpClient.syncShutdown() httpBin.shutdown() } - let request = try HTTPRequest(url: "https://localhost:\(httpBin.port)/post", method: .POST, body: .string("1234")) + let request = try Request(url: "https://localhost:\(httpBin.port)/post", method: .POST, body: .string("1234")) let response = try httpClient.execute(request: request).wait() let bytes = response.body!.withUnsafeReadableBytes { @@ -152,7 +156,7 @@ class SwiftHTTPTests: XCTestCase { let httpBin = HttpBin(ssl: false) let httpsBin = HttpBin(ssl: true) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, - configuration: HTTPClientConfiguration(certificateVerification: .none, followRedirects: true)) + configuration: HTTPClient.Configuration(certificateVerification: .none, followRedirects: true)) defer { try! httpClient.syncShutdown() @@ -183,7 +187,7 @@ class SwiftHTTPTests: XCTestCase { var headers = HTTPHeaders() headers.add(name: "Content-Length", value: "12") - let request = try HTTPRequest(url: "http://localhost:\(httpBin.port)/post", method: .POST, headers: headers, body: .byteBuffer(body)) + let request = try Request(url: "http://localhost:\(httpBin.port)/post", method: .POST, headers: headers, body: .byteBuffer(body)) let response = try httpClient.execute(request: request).wait() // if the library adds another content length header we'll get a bad request error. XCTAssertEqual(.ok, response.status) @@ -197,7 +201,7 @@ class SwiftHTTPTests: XCTestCase { httpBin.shutdown() } - var request = try HTTPRequest(url: "http://localhost:\(httpBin.port)/events/10/1") + var request = try Request(url: "http://localhost:\(httpBin.port)/events/10/1") request.headers.add(name: "Accept", value: "text/event-stream") let delegate = CountingDelegate() @@ -215,32 +219,26 @@ class SwiftHTTPTests: XCTestCase { httpBin.shutdown() } - do { - _ = try httpClient.get(url: "http://localhost:\(httpBin.port)/close").wait() - XCTFail("Should fail with RemoteConnectionClosedError") - } catch _ as HTTPClientErrors.RemoteConnectionClosedError { - // ok - } catch { - XCTFail("Unexpected error: \(error)") + XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(httpBin.port)/close").wait(), "Should fail") { error in + guard case let error = error as? HTTPClientError, error == .remoteConnectionClosed else { + return XCTFail("Should fail with remoteConnectionClosed") + } } } func testReadTimeout() throws { let httpBin = HttpBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: HTTPClientConfiguration(timeout: Timeout(readTimeout: .milliseconds(150)))) + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: HTTPClient.Configuration(timeout: HTTPClient.Timeout(read: .milliseconds(150)))) defer { try! httpClient.syncShutdown() httpBin.shutdown() } - do { - _ = try httpClient.get(url: "http://localhost:\(httpBin.port)/wait").wait() - XCTFail("Should fail with: ReadTimeoutError") - } catch _ as HTTPClientErrors.ReadTimeoutError { - // ok - } catch { - XCTFail("Unexpected error: \(error)") + XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(httpBin.port)/wait").wait(), "Should fail") { error in + guard case let error = error as? HTTPClientError, error == .readTimeout else { + return XCTFail("Should fail with readTimeout") + } } } @@ -254,20 +252,17 @@ class SwiftHTTPTests: XCTestCase { } let queue = DispatchQueue(label: "nio-test") - let request = try HTTPRequest(url: "http://localhost:\(httpBin.port)/wait") + let request = try Request(url: "http://localhost:\(httpBin.port)/wait") let task = httpClient.execute(request: request, delegate: TestHTTPDelegate()) queue.asyncAfter(deadline: .now() + .milliseconds(100)) { task.cancel() } - do { - _ = try task.wait() - XCTFail("Should fail with: CancelledError") - } catch _ as HTTPClientErrors.CancelledError { - // ok - } catch { - XCTFail("Unexpected error: \(error)") + XCTAssertThrowsError(try task.wait(), "Should fail") { error in + guard case let error = error as? HTTPClientError, error == .cancelled else { + return XCTFail("Should fail with cancelled") + } } } }