From 7b99912a9c2ba4efb4bc995e0737493d75b2725a Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Sun, 7 Jul 2019 10:24:16 +0100 Subject: [PATCH 01/12] document public API --- .../HTTPClient+HTTPCookie.swift | 32 +++++- Sources/AsyncHTTPClient/HTTPClient.swift | 99 +++++++++++++++++ Sources/AsyncHTTPClient/HTTPHandler.swift | 104 +++++++++++++++++- 3 files changed, 229 insertions(+), 6 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift index 9a1d786f4..1aae829bf 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift @@ -16,16 +16,30 @@ import Foundation import NIOHTTP1 extension HTTPClient { + /// A representation of an HTTP cookie. public struct Cookie { + /// The name of the cookie. public var name: String + /// The cookie's sting value. public var value: String + /// The cookie's path. public var path: String + /// The domain of the cookie public var domain: String? + /// The cookie's expiration date. public var expires: Date? + /// The cookie's age. public var maxAge: Int? + /// A Boolean value that indicates whether the cookie should only be sent to HTTP servers. public var httpOnly: Bool + /// A Boolean value that indicates whether this cookie should only be sent over secure channels. public var secure: Bool + /// Parses HTTP cookie from `Set-Cookie` header. + /// + /// - parameters: + /// - string: String representation of the `Set-Cookie` response header. + /// - defaultDomain: Default domain to use if cookie was sent without one. public init?(from string: String, defaultDomain: String) { let components = string.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) @@ -90,6 +104,17 @@ extension HTTPClient { } } + /// Create HTTP cookie. + /// + /// - parameters: + /// - name: The name of the cookie. + /// - value: The cookie's sting value. + /// - path: The cookie's path. + /// - domain: The domain of the cookie, defaults to nil. + /// - expires: The cookie's expiration date, defaults to nil. + /// - maxAge: The cookie's age, defaults to nil. + /// - httpOnly: Whether this cookie should be used by HTTP servers only, defaults to false. + /// - secure: Whether this cookie should only be sent using secure channels, defaults to false. public init(name: String, value: String, path: String = "/", domain: String? = nil, expires: Date? = nil, maxAge: Int? = nil, httpOnly: Bool = false, secure: Bool = false) { self.name = name self.value = value @@ -112,12 +137,13 @@ extension HTTPClient { } } -public extension HTTPClient.Response { - internal var cookieHeaders: [HTTPHeaders.Element] { +extension HTTPClient.Response { + var cookieHeaders: [HTTPHeaders.Element] { return headers.filter { $0.name.lowercased() == "set-cookie" } } - var cookies: [HTTPClient.Cookie] { + /// List of HTTP cookies returned by the server. + public var cookies: [HTTPClient.Cookie] { return self.cookieHeaders.compactMap { HTTPClient.Cookie(from: $0.value, defaultDomain: self.host) } } } diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 8dfd55ab8..989b96b8b 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -18,12 +18,42 @@ import NIOConcurrencyHelpers import NIOHTTP1 import NIOSSL +/// HTTPClient class provides API for request execution. +/// +/// Example: +/// +/// ```swift +/// let client = HTTPClient(eventLoopGroupProvider = .createNew) +/// client.get(url: "https://swift.org", deadline: .now() + .seconds(1)).whenComplete { result in +/// switch result { +/// case .failure(let error): +/// // process error +/// case .success(let response): +/// if let response.status == .ok { +/// // handle response +/// } else { +/// // handle remote error +/// } +/// } +/// } +/// ``` +/// +/// It is important to close the client instance, for example in a defer statement, after use to cleanly shutdown the underlying NIO `EventLoopGroup`: +/// +/// ```swift +/// try client.syncShutdown() +/// ``` public class HTTPClient { public let eventLoopGroup: EventLoopGroup let eventLoopGroupProvider: EventLoopGroupProvider let configuration: Configuration let isShutdown = Atomic(value: false) + /// Create an HTTPClient with specified `EventLoopGroup` provider and configuration. + /// + /// - parameters: + /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. + /// - configuration: Client configuration. public init(eventLoopGroupProvider: EventLoopGroupProvider, configuration: Configuration = Configuration()) { self.eventLoopGroupProvider = eventLoopGroupProvider switch self.eventLoopGroupProvider { @@ -44,6 +74,7 @@ public class HTTPClient { } } + /// Shuts down the client and `EventLoopGroup` if it was created by the client. public func syncShutdown() throws { switch self.eventLoopGroupProvider { case .shared: @@ -58,6 +89,11 @@ public class HTTPClient { } } + /// Execute GET request using specified URL. + /// + /// - parameters: + /// - url: Remote URL. + /// - deadline: Maximum request duration. public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try Request(url: url, method: .GET) @@ -67,6 +103,12 @@ public class HTTPClient { } } + /// Execute POST request using specified URL. + /// + /// - parameters: + /// - url: Remote URL. + /// - body: Request body. + /// - deadline: Maximum request duration. public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .POST, body: body) @@ -76,6 +118,12 @@ public class HTTPClient { } } + /// Execute PATCH request using specified URL. + /// + /// - parameters: + /// - url: Remote URL. + /// - body: Request body. + /// - deadline: Maximum request duration. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .PATCH, body: body) @@ -85,6 +133,12 @@ public class HTTPClient { } } + /// Execute PUT request using specified URL. + /// + /// - parameters: + /// - url: Remote URL. + /// - body: Request body. + /// - deadline: Maximum request duration. public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .PUT, body: body) @@ -94,6 +148,11 @@ public class HTTPClient { } } + /// Execute DELETE request using specified URL. + /// + /// - parameters: + /// - url: Remote URL. + /// - deadline: Maximum request duration. public func delete(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try Request(url: url, method: .DELETE) @@ -103,11 +162,22 @@ public class HTTPClient { } } + /// Execute arbitrary HTTP request using specified URL. + /// + /// - parameters: + /// - request: HTTP request to execute. + /// - deadline: Maximum request duration. public func execute(request: Request, deadline: NIODeadline? = nil) -> EventLoopFuture { let accumulator = ResponseAccumulator(request: request) return self.execute(request: request, delegate: accumulator, deadline: deadline).futureResult } + /// Execute arbitrary HTTP request and handle response processing using provided delegate. + /// + /// - parameters: + /// - request: HTTP request to execute. + /// - delegate: Delegate to process response parts. + /// - deadline: Maximum request duration. public func execute(request: Request, delegate: T, deadline: NIODeadline? = nil) -> Task { let eventLoop = self.eventLoopGroup.next() @@ -187,10 +257,15 @@ public class HTTPClient { } } + /// HTTPClient configuration. public struct Configuration { + /// TLS configuration, defaults to `TLSConfiguration.forClient()`. public var tlsConfiguration: TLSConfiguration? + /// Enables following certain 3xx redirects automatically, defaults to `false`. public var followRedirects: Bool + /// Default client timeout, defaults to no timeouts. public var timeout: Timeout + /// Upstream proxy, defaults to no proxy. public var proxy: Proxy? public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil) { @@ -208,15 +283,26 @@ public class HTTPClient { } } + /// Specifies how `EventLoopGroup` will be created and establishes lifecycle ownership. public enum EventLoopGroupProvider { + /// `EventLoopGroup` will be provided by the user. Owner of this group is responsible for it's lifecycle. case shared(EventLoopGroup) + /// `EventLoopGroup` will be created by the client. When `syncShutdown` is called, created `EventLoopGroup` will be shut down as well. case createNew } + /// Timeout configuration public struct Timeout { + /// Specifies `connect` timeout. public var connect: TimeAmount? + /// Specifies `read` timeout. public var read: TimeAmount? + /// Create timeout. + /// + /// - parameters: + /// - connect: `connect` timeout. + /// - read: `read` timeout. public init(connect: TimeAmount? = nil, read: TimeAmount? = nil) { self.connect = connect self.read = read @@ -255,6 +341,7 @@ private extension ChannelPipeline { } } +/// Possible client errors. public struct HTTPClientError: Error, Equatable, CustomStringConvertible { private enum Code: Equatable { case invalidURL @@ -281,16 +368,28 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible { return "HTTPClientError.\(String(describing: self.code))" } + /// URL provided is invalid. public static let invalidURL = HTTPClientError(code: .invalidURL) + /// URL does not contain host. public static let emptyHost = HTTPClientError(code: .emptyHost) + /// Client is shutdown and cannot be used for new requests. public static let alreadyShutdown = HTTPClientError(code: .alreadyShutdown) + /// URL does not contain scheme. public static let emptyScheme = HTTPClientError(code: .emptyScheme) + /// Provided URL scheme is not supported, supported schemes are: `http` and `https` public static func unsupportedScheme(_ scheme: String) -> HTTPClientError { return HTTPClientError(code: .unsupportedScheme(scheme)) } + /// Request timed out. public static let readTimeout = HTTPClientError(code: .readTimeout) + /// Remote connection was closed unexpectedly. public static let remoteConnectionClosed = HTTPClientError(code: .remoteConnectionClosed) + /// Request was cancelled. public static let cancelled = HTTPClientError(code: .cancelled) + /// Request contains invalid identity encoding. public static let identityCodingIncorrectlyPresent = HTTPClientError(code: .identityCodingIncorrectlyPresent) + /// Request contains multiple chunks definitions. public static let chunkedSpecifiedMultipleTimes = HTTPClientError(code: .chunkedSpecifiedMultipleTimes) + /// Proxy response was invalid. public static let invalidProxyResponse = HTTPClientError(code: .invalidProxyResponse) + /// Request does not contain `Content-Length` header. public static let contentLengthMissing = HTTPClientError(code: .contentLengthMissing) } diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 862c7a371..9845bd7b3 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -20,28 +20,49 @@ import NIOSSL extension HTTPClient { + /// Represent request body. public struct Body { + /// Chunk provider. public struct StreamWriter { let closure: (IOData) -> EventLoopFuture + /// Write data to upstream connection. + /// + /// - parameters: + /// - data: Data to write. public func write(_ data: IOData) -> EventLoopFuture { return self.closure(data) } } + /// Body size. public var length: Int? + /// Body chunk provider. public var stream: (StreamWriter) -> EventLoopFuture + /// Create and stream body using `ByteBuffer`. + /// + /// - parameters: + /// - buffer: Body `ByteBuffer` representation. public static func byteBuffer(_ buffer: ByteBuffer) -> Body { return Body(length: buffer.readableBytes) { writer in writer.write(.byteBuffer(buffer)) } } + /// Create and stream body using `StreamWriter`. + /// + /// - parameters: + /// - length: Body size. + /// - stream: Body chunk provider. public static func stream(length: Int? = nil, _ stream: @escaping (StreamWriter) -> EventLoopFuture) -> Body { return Body(length: length, stream: stream) } + /// Create and stream body using `Data`. + /// + /// - parameters: + /// - data: Body `Data` representation. public static func data(_ data: Data) -> Body { return Body(length: data.count) { writer in var buffer = ByteBufferAllocator().buffer(capacity: data.count) @@ -50,6 +71,10 @@ extension HTTPClient { } } + /// Create and stream body using `String`. + /// + /// - parameters: + /// - string: Body `String` representation. public static func string(_ string: String) -> Body { return Body(length: string.utf8.count) { writer in var buffer = ByteBufferAllocator().buffer(capacity: string.utf8.count) @@ -59,13 +84,21 @@ extension HTTPClient { } } + /// Represent HTTP request. public struct Request { + /// Request HTTP version, defaults to `HTTP/1.1`. public var version: HTTPVersion + /// Request HTTP method, defaults to `GET`. public var method: HTTPMethod + /// Remote URL. public var url: URL + /// Remote HTTP scheme, resolved from `URL`. public var scheme: String + /// Remote host, resolved from `URL`. public var host: String + /// Request custom HTTP Headers, defaults to no headers. public var headers: HTTPHeaders + /// Request body, defaults to no body. 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 { @@ -76,6 +109,18 @@ extension HTTPClient { try self.init(url: url, version: version, method: method, headers: headers, body: body) } + /// Create HTTP request. + /// + /// - parameters: + /// - url: Remote `URL`. + /// - version: HTTP version. + /// - method: HTTP method. + /// - headers: Custom HTTP headers. + /// - body: Request body. + /// - throws: + /// - `emptyScheme` if URL does not contain HTTP scheme. + /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. + /// - `emptyHost` if URL does not contains a host. public init(url: URL, version: HTTPVersion = HTTPVersion(major: 1, minor: 1), method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { guard let scheme = url.scheme else { throw HTTPClientError.emptyScheme @@ -98,10 +143,12 @@ extension HTTPClient { self.body = body } + /// Whether request will be executed using secure socket. public var useTLS: Bool { return self.url.scheme == "https" } + /// Resolved port. public var port: Int { return self.url.port ?? (self.useTLS ? 443 : 80) } @@ -111,10 +158,15 @@ extension HTTPClient { } } + /// Represent HTTP response. public struct Response { + /// Remote host of the request. public var host: String + /// Response HTTP status. public var status: HTTPResponseStatus + /// Reponse HTTP headers. public var headers: HTTPHeaders + /// Response body. public var body: ByteBuffer? } } @@ -191,24 +243,62 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { } } -/// 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 +/// HTTPClientResponseDelegate allows to receive notification about request processing and to control how reponse parts are processed. +/// +/// - note: This delegate is strongly held by the `HTTPTaskHandler` +/// for the duration of the `Request` processing and will be +/// released together with the `HTTPTaskHandler` when channel is closed. public protocol HTTPClientResponseDelegate: AnyObject { associatedtype Response + /// Called when request `Head` is sent. Will be called once. + /// + /// - parameters: + /// - task: Current request context. + /// - head: Request `Head`. func didSendRequestHead(task: HTTPClient.Task, _ head: HTTPRequestHead) + /// Called when request part was sent. Could be called zero or more times. + /// + /// - parameters: + /// - task: Current request context. + /// - part: Request body `Part`. func didSendRequestPart(task: HTTPClient.Task, _ part: IOData) + /// Called when request is fully sent. Will be called once. + /// + /// - parameters: + /// - task: Current request context. func didSendRequest(task: HTTPClient.Task) + /// Called when response `Head` is received. Will be called once. Response processing will resume when returned `EventLoopFuture` is fullfilled. + /// + /// - parameters: + /// - task: Current request context. + /// - head: Received reposonse head. + /// - returns: `EventLoopFuture` that will be used for backpressure. func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) -> EventLoopFuture + /// Called when response body `Part` is received. Could be called zero or more times. Response processing will resume when returned `EventLoopFuture` is fullfilled. + /// + /// - parameters: + /// - task: Current request context. + /// - buffer: Received body `Part`. + /// - returns: `EventLoopFuture` that will be used for backpressure. func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) -> EventLoopFuture + /// Called when error was thrown during request execution. Request processing will be stopped. + /// + /// - parameters: + /// - task: Current request context. + /// - error: Error that occured during response processing. func didReceiveError(task: HTTPClient.Task, _ error: Error) + /// Called when response is fully processed. Client is requred to return result of the processing specified by `Response` associated type. + /// + /// - parameters: + /// - task: Current request context. + /// - returns: Result of processing. func didFinishRequest(task: HTTPClient.Task) throws -> Response } @@ -238,7 +328,9 @@ internal extension URL { } extension HTTPClient { + /// Response execution context. public final class Task { + /// `EventLoop` used to execute and process request. public let eventLoop: EventLoop let promise: EventLoopPromise @@ -253,14 +345,20 @@ extension HTTPClient { self.lock = Lock() } + /// `EventLoopFuture` of this request. public var futureResult: EventLoopFuture { return self.promise.futureResult } + /// Waits for execution of request. + /// + /// - returns: The value of the `EventLoopFuture` when it completes. + /// - throws: The error value of the `EventLoopFuture` if it errors. public func wait() throws -> Response { return try self.promise.futureResult.wait() } + /// Cancels the request execution. public func cancel() { self.lock.withLock { if !cancelled { From 9bfe6cea869bbe6c151eee88ad375a0af4075614 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Mon, 8 Jul 2019 12:31:21 +0100 Subject: [PATCH 02/12] review fixes --- .../AsyncHTTPClient/HTTPClient+HTTPCookie.swift | 11 ++++++----- Sources/AsyncHTTPClient/HTTPHandler.swift | 17 +++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift index 1aae829bf..9a5a98cb8 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift @@ -20,26 +20,27 @@ extension HTTPClient { public struct Cookie { /// The name of the cookie. public var name: String - /// The cookie's sting value. + /// The cookie's string value. public var value: String /// The cookie's path. public var path: String - /// The domain of the cookie + /// The domain of the cookie. public var domain: String? /// The cookie's expiration date. public var expires: Date? /// The cookie's age. public var maxAge: Int? - /// A Boolean value that indicates whether the cookie should only be sent to HTTP servers. + /// Whether the cookie should only be sent to HTTP servers. public var httpOnly: Bool - /// A Boolean value that indicates whether this cookie should only be sent over secure channels. + /// Whether the cookie should only be sent over secure channels. public var secure: Bool - /// Parses HTTP cookie from `Set-Cookie` header. + /// Create a Cookie by parsing a `Set-Cookie` header. /// /// - parameters: /// - string: String representation of the `Set-Cookie` response header. /// - defaultDomain: Default domain to use if cookie was sent without one. + /// - returns: nil if the header is invalid. public init?(from string: String, defaultDomain: String) { let components = string.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 9845bd7b3..d4cbbf67a 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -258,7 +258,7 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// - head: Request `Head`. func didSendRequestHead(task: HTTPClient.Task, _ head: HTTPRequestHead) - /// Called when request part was sent. Could be called zero or more times. + /// Called when a part of the request body is sent. Could be called zero or more times. /// /// - parameters: /// - task: Current request context. @@ -271,7 +271,9 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// - task: Current request context. func didSendRequest(task: HTTPClient.Task) - /// Called when response `Head` is received. Will be called once. Response processing will resume when returned `EventLoopFuture` is fullfilled. + /// Called when response `Head` is received. Will be called once. + /// You must return an `EventLoopFuture` that you complete when you have finished processing the body part. + /// You can create an already succeeded future by calling `task.eventLoop.makeSucceededFuture(())`. /// /// - parameters: /// - task: Current request context. @@ -279,7 +281,9 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// - returns: `EventLoopFuture` that will be used for backpressure. func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) -> EventLoopFuture - /// Called when response body `Part` is received. Could be called zero or more times. Response processing will resume when returned `EventLoopFuture` is fullfilled. + /// Called when part of a response body is received. Could be called zero or more times. + /// You must return an `EventLoopFuture` that you complete when you have finished processing the body part. + /// You can create an already succeeded future by calling `task.eventLoop.makeSucceededFuture(())`. /// /// - parameters: /// - task: Current request context. @@ -287,14 +291,14 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// - returns: `EventLoopFuture` that will be used for backpressure. func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) -> EventLoopFuture - /// Called when error was thrown during request execution. Request processing will be stopped. + /// Called when error was thrown during request execution. Will be called zero or one time only. Request processing will be stopped after that. /// /// - parameters: /// - task: Current request context. /// - error: Error that occured during response processing. func didReceiveError(task: HTTPClient.Task, _ error: Error) - /// Called when response is fully processed. Client is requred to return result of the processing specified by `Response` associated type. + /// Called when the complete HTTP request is finished. You must return an instance of your `Response` associated type. Will be called once, except if an error occurred. /// /// - parameters: /// - task: Current request context. @@ -328,7 +332,8 @@ internal extension URL { } extension HTTPClient { - /// Response execution context. + /// Response execution context. Will be created by the library and could be used for obtaining + /// `EventLoopFuture` of the execution or cancellation of the execution. public final class Task { /// `EventLoop` used to execute and process request. public let eventLoop: EventLoop From 8d2f7789332bc5f12b9b4fb7117b09fedba3f422 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Mon, 8 Jul 2019 12:32:10 +0100 Subject: [PATCH 03/12] review fixes --- Sources/AsyncHTTPClient/HTTPClient.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 989b96b8b..a3a626e73 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -93,7 +93,7 @@ public class HTTPClient { /// /// - parameters: /// - url: Remote URL. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by. public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try Request(url: url, method: .GET) @@ -108,7 +108,7 @@ public class HTTPClient { /// - parameters: /// - url: Remote URL. /// - body: Request body. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by. public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .POST, body: body) @@ -123,7 +123,7 @@ public class HTTPClient { /// - parameters: /// - url: Remote URL. /// - body: Request body. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .PATCH, body: body) @@ -138,7 +138,7 @@ public class HTTPClient { /// - parameters: /// - url: Remote URL. /// - body: Request body. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by. public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .PUT, body: body) @@ -152,7 +152,7 @@ public class HTTPClient { /// /// - parameters: /// - url: Remote URL. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by. public func delete(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try Request(url: url, method: .DELETE) @@ -166,7 +166,7 @@ public class HTTPClient { /// /// - parameters: /// - request: HTTP request to execute. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by public func execute(request: Request, deadline: NIODeadline? = nil) -> EventLoopFuture { let accumulator = ResponseAccumulator(request: request) return self.execute(request: request, delegate: accumulator, deadline: deadline).futureResult @@ -177,7 +177,7 @@ public class HTTPClient { /// - parameters: /// - request: HTTP request to execute. /// - delegate: Delegate to process response parts. - /// - deadline: Maximum request duration. + /// - deadline: The time when the request must have been completed by. public func execute(request: Request, delegate: T, deadline: NIODeadline? = nil) -> Task { let eventLoop = self.eventLoopGroup.next() From bc2ddc0131142958cb6cf74e9e3ff7991eb4bb32 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 14:39:30 +0100 Subject: [PATCH 04/12] review fixes --- .../HTTPClient+HTTPCookie.swift | 6 ++-- Sources/AsyncHTTPClient/HTTPClient.swift | 34 +++++++++---------- Sources/AsyncHTTPClient/HTTPHandler.swift | 28 +++++++++++---- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift index 9a5a98cb8..4fbafd2ee 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift @@ -38,10 +38,10 @@ extension HTTPClient { /// Create a Cookie by parsing a `Set-Cookie` header. /// /// - parameters: - /// - string: String representation of the `Set-Cookie` response header. + /// - header: String representation of the `Set-Cookie` response header. /// - defaultDomain: Default domain to use if cookie was sent without one. /// - returns: nil if the header is invalid. - public init?(from string: String, defaultDomain: String) { + public init?(header: String, defaultDomain: String) { let components = string.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) } @@ -109,7 +109,7 @@ extension HTTPClient { /// /// - parameters: /// - name: The name of the cookie. - /// - value: The cookie's sting value. + /// - value: The cookie's string value. /// - path: The cookie's path. /// - domain: The domain of the cookie, defaults to nil. /// - expires: The cookie's expiration date, defaults to nil. diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index a3a626e73..3b984511e 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -27,7 +27,7 @@ import NIOSSL /// client.get(url: "https://swift.org", deadline: .now() + .seconds(1)).whenComplete { result in /// switch result { /// case .failure(let error): -/// // process error +/// // process error /// case .success(let response): /// if let response.status == .ok { /// // handle response @@ -49,7 +49,7 @@ public class HTTPClient { let configuration: Configuration let isShutdown = Atomic(value: false) - /// Create an HTTPClient with specified `EventLoopGroup` provider and configuration. + /// Create an `HTTPClient` with specified `EventLoopGroup` provider and configuration. /// /// - parameters: /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. @@ -89,11 +89,11 @@ public class HTTPClient { } } - /// Execute GET request using specified URL. + /// Execute `GET` request using specified URL. /// /// - parameters: /// - url: Remote URL. - /// - deadline: The time when the request must have been completed by. + /// - deadline: Point in time by which the request must complete. public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try Request(url: url, method: .GET) @@ -103,12 +103,12 @@ public class HTTPClient { } } - /// Execute POST request using specified URL. + /// Execute `POST` request using specified URL. /// /// - parameters: /// - url: Remote URL. /// - body: Request body. - /// - deadline: The time when the request must have been completed by. + /// - deadline: Point in time by which the request must complete. public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .POST, body: body) @@ -118,12 +118,12 @@ public class HTTPClient { } } - /// Execute PATCH request using specified URL. + /// Execute `PATCH` request using specified URL. /// /// - parameters: /// - url: Remote URL. /// - body: Request body. - /// - deadline: The time when the request must have been completed by. + /// - deadline: Point in time by which the request must complete. public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .PATCH, body: body) @@ -133,12 +133,12 @@ public class HTTPClient { } } - /// Execute PUT request using specified URL. + /// Execute `PUT` request using specified URL. /// /// - parameters: /// - url: Remote URL. /// - body: Request body. - /// - deadline: The time when the request must have been completed by. + /// - deadline: Point in time by which the request must complete. public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture { do { let request = try HTTPClient.Request(url: url, method: .PUT, body: body) @@ -148,7 +148,7 @@ public class HTTPClient { } } - /// Execute DELETE request using specified URL. + /// Execute `DELETE` request using specified URL. /// /// - parameters: /// - url: Remote URL. @@ -166,7 +166,7 @@ public class HTTPClient { /// /// - parameters: /// - request: HTTP request to execute. - /// - deadline: The time when the request must have been completed by + /// - deadline: Point in time by which the request must complete. public func execute(request: Request, deadline: NIODeadline? = nil) -> EventLoopFuture { let accumulator = ResponseAccumulator(request: request) return self.execute(request: request, delegate: accumulator, deadline: deadline).futureResult @@ -177,7 +177,7 @@ public class HTTPClient { /// - parameters: /// - request: HTTP request to execute. /// - delegate: Delegate to process response parts. - /// - deadline: The time when the request must have been completed by. + /// - 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() @@ -257,11 +257,11 @@ public class HTTPClient { } } - /// HTTPClient configuration. + /// `HTTPClient` configuration. public struct Configuration { /// TLS configuration, defaults to `TLSConfiguration.forClient()`. public var tlsConfiguration: TLSConfiguration? - /// Enables following certain 3xx redirects automatically, defaults to `false`. + /// Enables following 3xx redirects automatically, defaults to `false`. public var followRedirects: Bool /// Default client timeout, defaults to no timeouts. public var timeout: Timeout @@ -293,9 +293,9 @@ public class HTTPClient { /// Timeout configuration public struct Timeout { - /// Specifies `connect` timeout. + /// Specifies connect timeout. public var connect: TimeAmount? - /// Specifies `read` timeout. + /// Specifies read timeout. public var read: TimeAmount? /// Create timeout. diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index d4cbbf67a..030b05956 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -29,7 +29,7 @@ extension HTTPClient { /// Write data to upstream connection. /// /// - parameters: - /// - data: Data to write. + /// - data: `IOData` to write. public func write(_ data: IOData) -> EventLoopFuture { return self.closure(data) } @@ -101,6 +101,19 @@ extension HTTPClient { /// Request body, defaults to no body. public var body: Body? + /// Create HTTP request. + /// + /// - parameters: + /// - url: Remote `URL`. + /// - version: HTTP version. + /// - method: HTTP method. + /// - headers: Custom HTTP headers. + /// - body: Request body. + /// - throws: + /// - `invalidURL` if URL cannot be parsed. + /// - `emptyScheme` if URL does not contain HTTP scheme. + /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. + /// - `emptyHost` if URL does not contains a host. 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 @@ -109,7 +122,7 @@ extension HTTPClient { try self.init(url: url, version: version, method: method, headers: headers, body: body) } - /// Create HTTP request. + /// Create an HTTP `Request`. /// /// - parameters: /// - url: Remote `URL`. @@ -243,7 +256,10 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { } } -/// HTTPClientResponseDelegate allows to receive notification about request processing and to control how reponse parts are processed. +/// `HTTPClientResponseDelegate` allows to receive notification about request processing and to control how reponse parts are processed. +/// You can implement this protocol if you need fine control over an HTTP request/response, for example if you want to inspect the response +/// headers before deciding whether to accept a response body, or if you want to stream your request body. Pass an instance of your conforming +/// class to the `HTTPClient.execute()` method and this package will call each delegate method appropriately as the request takes place. /// /// - note: This delegate is strongly held by the `HTTPTaskHandler` /// for the duration of the `Request` processing and will be @@ -251,11 +267,11 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { public protocol HTTPClientResponseDelegate: AnyObject { associatedtype Response - /// Called when request `Head` is sent. Will be called once. + /// Called when request head is sent. Will be called once. /// /// - parameters: /// - task: Current request context. - /// - head: Request `Head`. + /// - head: Request head. func didSendRequestHead(task: HTTPClient.Task, _ head: HTTPRequestHead) /// Called when a part of the request body is sent. Could be called zero or more times. @@ -271,7 +287,7 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// - task: Current request context. func didSendRequest(task: HTTPClient.Task) - /// Called when response `Head` is received. Will be called once. + /// Called when response head is received. Will be called once. /// You must return an `EventLoopFuture` that you complete when you have finished processing the body part. /// You can create an already succeeded future by calling `task.eventLoop.makeSucceededFuture(())`. /// From cb5e964c4c822459f36e0f4bb098b263d59441d6 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 14:45:09 +0100 Subject: [PATCH 05/12] fix compilation --- Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift | 4 ++-- Tests/AsyncHTTPClientTests/HTTPClientCookieTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift index 4fbafd2ee..2f8a243d9 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift @@ -42,7 +42,7 @@ extension HTTPClient { /// - defaultDomain: Default domain to use if cookie was sent without one. /// - returns: nil if the header is invalid. public init?(header: String, defaultDomain: String) { - let components = string.components(separatedBy: ";").map { + let components = header.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) } @@ -145,6 +145,6 @@ extension HTTPClient.Response { /// List of HTTP cookies returned by the server. public var cookies: [HTTPClient.Cookie] { - return self.cookieHeaders.compactMap { HTTPClient.Cookie(from: $0.value, defaultDomain: self.host) } + return self.cookieHeaders.compactMap { HTTPClient.Cookie(header: $0.value, defaultDomain: self.host) } } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientCookieTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientCookieTests.swift index fda30d8d7..787c64521 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientCookieTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientCookieTests.swift @@ -19,7 +19,7 @@ import XCTest class HTTPClientCookieTests: XCTestCase { func testCookie() { let v = "key=value; Path=/path; Domain=example.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Max-Age=42; Secure; HttpOnly" - let c = HTTPClient.Cookie(from: v, defaultDomain: "exampe.org")! + let c = HTTPClient.Cookie(header: v, defaultDomain: "exampe.org")! XCTAssertEqual("key", c.name) XCTAssertEqual("value", c.value) XCTAssertEqual("/path", c.path) @@ -32,7 +32,7 @@ class HTTPClientCookieTests: XCTestCase { func testCookieDefaults() { let v = "key=value" - let c = HTTPClient.Cookie(from: v, defaultDomain: "example.org")! + let c = HTTPClient.Cookie(header: v, defaultDomain: "example.org")! XCTAssertEqual("key", c.name) XCTAssertEqual("value", c.value) XCTAssertEqual("/", c.path) From a040b1b6cb91d8a52b599818144cdaf170df6e6d Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 14:51:48 +0100 Subject: [PATCH 06/12] review fixes --- Sources/AsyncHTTPClient/HTTPHandler.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 030b05956..1eed8f978 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -26,7 +26,7 @@ extension HTTPClient { public struct StreamWriter { let closure: (IOData) -> EventLoopFuture - /// Write data to upstream connection. + /// Write data to server. /// /// - parameters: /// - data: `IOData` to write. @@ -35,7 +35,8 @@ extension HTTPClient { } } - /// Body size. + /// Body size. Request validation will be failed with `HTTPClientErrors.contentLengthMissing` if nil, + /// unless `Trasfer-Encoding: chunked` header is set. public var length: Int? /// Body chunk provider. public var stream: (StreamWriter) -> EventLoopFuture @@ -53,7 +54,8 @@ extension HTTPClient { /// Create and stream body using `StreamWriter`. /// /// - parameters: - /// - length: Body size. + /// - length: Body size. Request validation will be failed with `HTTPClientErrors.contentLengthMissing` if nil, + /// unless `Trasfer-Encoding: chunked` header is set. /// - stream: Body chunk provider. public static func stream(length: Int? = nil, _ stream: @escaping (StreamWriter) -> EventLoopFuture) -> Body { return Body(length: length, stream: stream) From b20fff7927358b0522b88772591126426ea611d6 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 14:56:36 +0100 Subject: [PATCH 07/12] review fixes --- Sources/AsyncHTTPClient/HTTPClient.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 3b984511e..d75537661 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -262,6 +262,15 @@ public class HTTPClient { /// TLS configuration, defaults to `TLSConfiguration.forClient()`. public var tlsConfiguration: TLSConfiguration? /// Enables following 3xx redirects automatically, defaults to `false`. + /// + /// Following redirects are supported: + /// - `301: Moved Permanently` + /// - `302: Found` + /// - `303: See Other` + /// - `304: Not Modified` + /// - `305: Use Proxy` + /// - `307: Temporary Redirect` + /// - `308: Permanent Redirect` public var followRedirects: Bool /// Default client timeout, defaults to no timeouts. public var timeout: Timeout From ed8c77612affd3213e6f94aa81363730f928f915 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 14:59:35 +0100 Subject: [PATCH 08/12] add the --- Sources/AsyncHTTPClient/HTTPHandler.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 1eed8f978..234748c20 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -269,7 +269,7 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { public protocol HTTPClientResponseDelegate: AnyObject { associatedtype Response - /// Called when request head is sent. Will be called once. + /// Called when the request head is sent. Will be called once. /// /// - parameters: /// - task: Current request context. @@ -283,7 +283,7 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// - part: Request body `Part`. func didSendRequestPart(task: HTTPClient.Task, _ part: IOData) - /// Called when request is fully sent. Will be called once. + /// Called when the request is fully sent. Will be called once. /// /// - parameters: /// - task: Current request context. From 7d45787ba294a8f2202ee5ebc055792c1909a94c Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 15:31:18 +0100 Subject: [PATCH 09/12] review fixes --- Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift | 4 ++-- Sources/AsyncHTTPClient/HTTPHandler.swift | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift index 2f8a243d9..5993fb0e3 100644 --- a/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift +++ b/Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift @@ -28,7 +28,7 @@ extension HTTPClient { public var domain: String? /// The cookie's expiration date. public var expires: Date? - /// The cookie's age. + /// The cookie's age in seconds. public var maxAge: Int? /// Whether the cookie should only be sent to HTTP servers. public var httpOnly: Bool @@ -113,7 +113,7 @@ extension HTTPClient { /// - path: The cookie's path. /// - domain: The domain of the cookie, defaults to nil. /// - expires: The cookie's expiration date, defaults to nil. - /// - maxAge: The cookie's age, defaults to nil. + /// - maxAge: The cookie's age in seconds, defaults to nil. /// - httpOnly: Whether this cookie should be used by HTTP servers only, defaults to false. /// - secure: Whether this cookie should only be sent using secure channels, defaults to false. public init(name: String, value: String, path: String = "/", domain: String? = nil, expires: Date? = nil, maxAge: Int? = nil, httpOnly: Bool = false, secure: Bool = false) { diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 234748c20..5da18a7fa 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -266,6 +266,8 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { /// - note: This delegate is strongly held by the `HTTPTaskHandler` /// for the duration of the `Request` processing and will be /// released together with the `HTTPTaskHandler` when channel is closed. +/// Users of the library are not required to keep a reference to the +/// object that implements this protocol, but may do so if needed. public protocol HTTPClientResponseDelegate: AnyObject { associatedtype Response @@ -368,7 +370,7 @@ extension HTTPClient { self.lock = Lock() } - /// `EventLoopFuture` of this request. + /// `EventLoopFuture` for the response returned by this request. public var futureResult: EventLoopFuture { return self.promise.futureResult } From b1703ff3194dc3c3b2be8f34a47170783e0d3953 Mon Sep 17 00:00:00 2001 From: Artem Redkin Date: Tue, 9 Jul 2019 19:47:23 +0100 Subject: [PATCH 10/12] Update Sources/AsyncHTTPClient/HTTPClient.swift Co-Authored-By: Joe Smith --- Sources/AsyncHTTPClient/HTTPClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index d75537661..18114323b 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -294,7 +294,7 @@ public class HTTPClient { /// Specifies how `EventLoopGroup` will be created and establishes lifecycle ownership. public enum EventLoopGroupProvider { - /// `EventLoopGroup` will be provided by the user. Owner of this group is responsible for it's lifecycle. + /// `EventLoopGroup` will be provided by the user. Owner of this group is responsible for its lifecycle. case shared(EventLoopGroup) /// `EventLoopGroup` will be created by the client. When `syncShutdown` is called, created `EventLoopGroup` will be shut down as well. case createNew From 93aee658d915144bfb7b7f783900fccf5467639f Mon Sep 17 00:00:00 2001 From: Ian Partridge Date: Tue, 9 Jul 2019 21:30:24 +0100 Subject: [PATCH 11/12] Update Sources/AsyncHTTPClient/HTTPHandler.swift Co-Authored-By: Joe Smith --- Sources/AsyncHTTPClient/HTTPHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 5da18a7fa..e9cdb8a6f 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -258,7 +258,7 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { } } -/// `HTTPClientResponseDelegate` allows to receive notification about request processing and to control how reponse parts are processed. +/// `HTTPClientResponseDelegate` allows an implementation to receive notifications about request processing and to control how response parts are processed. /// You can implement this protocol if you need fine control over an HTTP request/response, for example if you want to inspect the response /// headers before deciding whether to accept a response body, or if you want to stream your request body. Pass an instance of your conforming /// class to the `HTTPClient.execute()` method and this package will call each delegate method appropriately as the request takes place. From a4eea07eac76d58766f6e01f01854e5f66ac9091 Mon Sep 17 00:00:00 2001 From: Ian Partridge Date: Wed, 10 Jul 2019 09:44:51 +0100 Subject: [PATCH 12/12] Apply suggestions from code review Co-Authored-By: Joe Smith --- Sources/AsyncHTTPClient/HTTPHandler.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index e9cdb8a6f..db25f2ca8 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -259,7 +259,7 @@ internal class ResponseAccumulator: HTTPClientResponseDelegate { } /// `HTTPClientResponseDelegate` allows an implementation to receive notifications about request processing and to control how response parts are processed. -/// You can implement this protocol if you need fine control over an HTTP request/response, for example if you want to inspect the response +/// You can implement this protocol if you need fine-grained control over an HTTP request/response, for example, if you want to inspect the response /// headers before deciding whether to accept a response body, or if you want to stream your request body. Pass an instance of your conforming /// class to the `HTTPClient.execute()` method and this package will call each delegate method appropriately as the request takes place. /// @@ -355,7 +355,7 @@ extension HTTPClient { /// Response execution context. Will be created by the library and could be used for obtaining /// `EventLoopFuture` of the execution or cancellation of the execution. public final class Task { - /// `EventLoop` used to execute and process request. + /// `EventLoop` used to execute and process this request. public let eventLoop: EventLoop let promise: EventLoopPromise @@ -375,7 +375,7 @@ extension HTTPClient { return self.promise.futureResult } - /// Waits for execution of request. + /// Waits for execution of this request to complete. /// /// - returns: The value of the `EventLoopFuture` when it completes. /// - throws: The error value of the `EventLoopFuture` if it errors.