Skip to content

Commit

Permalink
document public API (#62)
Browse files Browse the repository at this point in the history
Co-Authored-By: Joe Smith <yasumoto7@gmail.com>
  • Loading branch information
2 people authored and weissi committed Jul 17, 2019
1 parent 8fa6c6f commit 0c617a4
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 11 deletions.
39 changes: 33 additions & 6 deletions Sources/AsyncHTTPClient/HTTPClient+HTTPCookie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,33 @@ 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 string 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 in seconds.
public var maxAge: Int?
/// Whether the cookie should only be sent to HTTP servers.
public var httpOnly: Bool
/// Whether the cookie should only be sent over secure channels.
public var secure: Bool

public init?(from string: String, defaultDomain: String) {
let components = string.components(separatedBy: ";").map {
/// Create a Cookie by parsing a `Set-Cookie` header.
///
/// - parameters:
/// - 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?(header: String, defaultDomain: String) {
let components = header.components(separatedBy: ";").map {
$0.trimmingCharacters(in: .whitespaces)
}

Expand Down Expand Up @@ -90,6 +105,17 @@ extension HTTPClient {
}
}

/// Create HTTP cookie.
///
/// - parameters:
/// - name: The name of the cookie.
/// - 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.
/// - 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) {
self.name = name
self.value = value
Expand All @@ -112,12 +138,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] {
return self.cookieHeaders.compactMap { HTTPClient.Cookie(from: $0.value, defaultDomain: self.host) }
/// List of HTTP cookies returned by the server.
public var cookies: [HTTPClient.Cookie] {
return self.cookieHeaders.compactMap { HTTPClient.Cookie(header: $0.value, defaultDomain: self.host) }
}
}
108 changes: 108 additions & 0 deletions Sources/AsyncHTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bool>(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 {
Expand All @@ -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:
Expand All @@ -58,6 +89,11 @@ public class HTTPClient {
}
}

/// Execute `GET` request using specified URL.
///
/// - parameters:
/// - url: Remote URL.
/// - deadline: Point in time by which the request must complete.
public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
do {
let request = try Request(url: url, method: .GET)
Expand All @@ -67,6 +103,12 @@ public class HTTPClient {
}
}

/// Execute `POST` request using specified URL.
///
/// - parameters:
/// - url: Remote URL.
/// - body: Request body.
/// - deadline: Point in time by which the request must complete.
public func post(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
do {
let request = try HTTPClient.Request(url: url, method: .POST, body: body)
Expand All @@ -76,6 +118,12 @@ public class HTTPClient {
}
}

/// Execute `PATCH` request using specified URL.
///
/// - parameters:
/// - url: Remote URL.
/// - body: Request body.
/// - deadline: Point in time by which the request must complete.
public func patch(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
do {
let request = try HTTPClient.Request(url: url, method: .PATCH, body: body)
Expand All @@ -85,6 +133,12 @@ public class HTTPClient {
}
}

/// Execute `PUT` request using specified URL.
///
/// - parameters:
/// - url: Remote URL.
/// - body: Request body.
/// - deadline: Point in time by which the request must complete.
public func put(url: String, body: Body? = nil, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
do {
let request = try HTTPClient.Request(url: url, method: .PUT, body: body)
Expand All @@ -94,6 +148,11 @@ public class HTTPClient {
}
}

/// Execute `DELETE` request using specified URL.
///
/// - parameters:
/// - url: Remote URL.
/// - deadline: The time when the request must have been completed by.
public func delete(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
do {
let request = try Request(url: url, method: .DELETE)
Expand All @@ -103,11 +162,22 @@ public class HTTPClient {
}
}

/// Execute arbitrary HTTP request using specified URL.
///
/// - parameters:
/// - request: HTTP request to execute.
/// - deadline: Point in time by which the request must complete.
public func execute(request: Request, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
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: Point in time by which the request must complete.
public func execute<T: HTTPClientResponseDelegate>(request: Request, delegate: T, deadline: NIODeadline? = nil) -> Task<T.Response> {
let eventLoop = self.eventLoopGroup.next()

Expand Down Expand Up @@ -187,10 +257,24 @@ public class HTTPClient {
}
}

/// `HTTPClient` configuration.
public struct Configuration {
/// 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
/// Upstream proxy, defaults to no proxy.
public var proxy: Proxy?

public init(tlsConfiguration: TLSConfiguration? = nil, followRedirects: Bool = false, timeout: Timeout = Timeout(), proxy: Proxy? = nil) {
Expand All @@ -208,15 +292,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 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
}

/// 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
Expand Down Expand Up @@ -255,6 +350,7 @@ private extension ChannelPipeline {
}
}

/// Possible client errors.
public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
private enum Code: Equatable {
case invalidURL
Expand All @@ -281,16 +377,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)
}
Loading

0 comments on commit 0c617a4

Please sign in to comment.