@@ -18,12 +18,42 @@ import NIOConcurrencyHelpers
1818import NIOHTTP1
1919import NIOSSL
2020
21+ /// HTTPClient class provides API for request execution.
22+ ///
23+ /// Example:
24+ ///
25+ /// ```swift
26+ /// let client = HTTPClient(eventLoopGroupProvider = .createNew)
27+ /// client.get(url: "https://swift.org", deadline: .now() + .seconds(1)).whenComplete{result in
28+ /// switch result{
29+ /// case .failure(let error):
30+ /// // process error
31+ /// case .success(let response):
32+ /// if let response.status == .ok{
33+ /// // handle response
34+ /// } else{
35+ /// // handle remote error
36+ /// }
37+ /// }
38+ /// }
39+ /// ```
40+ ///
41+ /// It is important to close the client instance, for example in a defer statement, after use to cleanly shutdown the underlying NIO `EventLoopGroup`:
42+ ///
43+ /// ```swift
44+ /// try client.syncShutdown()
45+ /// ```
2146public class HTTPClient {
2247public let eventLoopGroup : EventLoopGroup
2348let eventLoopGroupProvider : EventLoopGroupProvider
2449let configuration : Configuration
2550let isShutdown = Atomic < Bool > ( value: false )
2651
52+ /// Create an `HTTPClient` with specified `EventLoopGroup` provider and configuration.
53+ ///
54+ /// - parameters:
55+ /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created.
56+ /// - configuration: Client configuration.
2757public init ( eventLoopGroupProvider: EventLoopGroupProvider , configuration: Configuration = Configuration ( ) ) {
2858self . eventLoopGroupProvider = eventLoopGroupProvider
2959switch self . eventLoopGroupProvider {
@@ -44,6 +74,7 @@ public class HTTPClient{
4474}
4575}
4676
77+ /// Shuts down the client and `EventLoopGroup` if it was created by the client.
4778public func syncShutdown( ) throws {
4879switch self . eventLoopGroupProvider {
4980case . shared:
@@ -58,6 +89,11 @@ public class HTTPClient{
5889}
5990}
6091
92+ /// Execute `GET` request using specified URL.
93+ ///
94+ /// - parameters:
95+ /// - url: Remote URL.
96+ /// - deadline: Point in time by which the request must complete.
6197public func get( url: String , deadline: NIODeadline ? = nil ) -> EventLoopFuture < Response > {
6298do {
6399let request = try Request ( url: url, method: . GET)
@@ -67,6 +103,12 @@ public class HTTPClient{
67103}
68104}
69105
106+ /// Execute `POST` request using specified URL.
107+ ///
108+ /// - parameters:
109+ /// - url: Remote URL.
110+ /// - body: Request body.
111+ /// - deadline: Point in time by which the request must complete.
70112public func post( url: String , body: Body ? = nil , deadline: NIODeadline ? = nil ) -> EventLoopFuture < Response > {
71113do {
72114let request = try HTTPClient . Request ( url: url, method: . POST, body: body)
@@ -76,6 +118,12 @@ public class HTTPClient{
76118}
77119}
78120
121+ /// Execute `PATCH` request using specified URL.
122+ ///
123+ /// - parameters:
124+ /// - url: Remote URL.
125+ /// - body: Request body.
126+ /// - deadline: Point in time by which the request must complete.
79127public func patch( url: String , body: Body ? = nil , deadline: NIODeadline ? = nil ) -> EventLoopFuture < Response > {
80128do {
81129let request = try HTTPClient . Request ( url: url, method: . PATCH, body: body)
@@ -85,6 +133,12 @@ public class HTTPClient{
85133}
86134}
87135
136+ /// Execute `PUT` request using specified URL.
137+ ///
138+ /// - parameters:
139+ /// - url: Remote URL.
140+ /// - body: Request body.
141+ /// - deadline: Point in time by which the request must complete.
88142public func put( url: String , body: Body ? = nil , deadline: NIODeadline ? = nil ) -> EventLoopFuture < Response > {
89143do {
90144let request = try HTTPClient . Request ( url: url, method: . PUT, body: body)
@@ -94,6 +148,11 @@ public class HTTPClient{
94148}
95149}
96150
151+ /// Execute `DELETE` request using specified URL.
152+ ///
153+ /// - parameters:
154+ /// - url: Remote URL.
155+ /// - deadline: The time when the request must have been completed by.
97156public func delete( url: String , deadline: NIODeadline ? = nil ) -> EventLoopFuture < Response > {
98157do {
99158let request = try Request ( url: url, method: . DELETE)
@@ -103,11 +162,22 @@ public class HTTPClient{
103162}
104163}
105164
165+ /// Execute arbitrary HTTP request using specified URL.
166+ ///
167+ /// - parameters:
168+ /// - request: HTTP request to execute.
169+ /// - deadline: Point in time by which the request must complete.
106170public func execute( request: Request , deadline: NIODeadline ? = nil ) -> EventLoopFuture < Response > {
107171let accumulator = ResponseAccumulator ( request: request)
108172return self . execute ( request: request, delegate: accumulator, deadline: deadline) . futureResult
109173}
110174
175+ /// Execute arbitrary HTTP request and handle response processing using provided delegate.
176+ ///
177+ /// - parameters:
178+ /// - request: HTTP request to execute.
179+ /// - delegate: Delegate to process response parts.
180+ /// - deadline: Point in time by which the request must complete.
111181public func execute< T: HTTPClientResponseDelegate > ( request: Request , delegate: T , deadline: NIODeadline ? = nil ) -> Task < T . Response > {
112182let eventLoop = self . eventLoopGroup. next ( )
113183
@@ -187,10 +257,24 @@ public class HTTPClient{
187257}
188258}
189259
260+ /// `HTTPClient` configuration.
190261public struct Configuration {
262+ /// TLS configuration, defaults to `TLSConfiguration.forClient()`.
191263public var tlsConfiguration : TLSConfiguration ?
264+ /// Enables following 3xx redirects automatically, defaults to `false`.
265+ ///
266+ /// Following redirects are supported:
267+ /// - `301: Moved Permanently`
268+ /// - `302: Found`
269+ /// - `303: See Other`
270+ /// - `304: Not Modified`
271+ /// - `305: Use Proxy`
272+ /// - `307: Temporary Redirect`
273+ /// - `308: Permanent Redirect`
192274public var followRedirects : Bool
275+ /// Default client timeout, defaults to no timeouts.
193276public var timeout : Timeout
277+ /// Upstream proxy, defaults to no proxy.
194278public var proxy : Proxy ?
195279
196280public init ( tlsConfiguration: TLSConfiguration ? = nil , followRedirects: Bool = false , timeout: Timeout = Timeout ( ) , proxy: Proxy ? = nil ) {
@@ -208,15 +292,26 @@ public class HTTPClient{
208292}
209293}
210294
295+ /// Specifies how `EventLoopGroup` will be created and establishes lifecycle ownership.
211296public enum EventLoopGroupProvider {
297+ /// `EventLoopGroup` will be provided by the user. Owner of this group is responsible for its lifecycle.
212298case shared( EventLoopGroup )
299+ /// `EventLoopGroup` will be created by the client. When `syncShutdown` is called, created `EventLoopGroup` will be shut down as well.
213300case createNew
214301}
215302
303+ /// Timeout configuration
216304public struct Timeout {
305+ /// Specifies connect timeout.
217306public var connect : TimeAmount ?
307+ /// Specifies read timeout.
218308public var read : TimeAmount ?
219309
310+ /// Create timeout.
311+ ///
312+ /// - parameters:
313+ /// - connect: `connect` timeout.
314+ /// - read: `read` timeout.
220315public init ( connect: TimeAmount ? = nil , read: TimeAmount ? = nil ) {
221316self . connect = connect
222317self . read = read
@@ -255,6 +350,7 @@ private extension ChannelPipeline{
255350}
256351}
257352
353+ /// Possible client errors.
258354public struct HTTPClientError : Error , Equatable , CustomStringConvertible {
259355private enum Code : Equatable {
260356case invalidURL
@@ -281,16 +377,28 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible{
281377return " HTTPClientError. \( String ( describing: self . code) ) "
282378}
283379
380+ /// URL provided is invalid.
284381public static let invalidURL = HTTPClientError ( code: . invalidURL)
382+ /// URL does not contain host.
285383public static let emptyHost = HTTPClientError ( code: . emptyHost)
384+ /// Client is shutdown and cannot be used for new requests.
286385public static let alreadyShutdown = HTTPClientError ( code: . alreadyShutdown)
386+ /// URL does not contain scheme.
287387public static let emptyScheme = HTTPClientError ( code: . emptyScheme)
388+ /// Provided URL scheme is not supported, supported schemes are: `http` and `https`
288389public static func unsupportedScheme( _ scheme: String ) -> HTTPClientError { return HTTPClientError ( code: . unsupportedScheme( scheme) ) }
390+ /// Request timed out.
289391public static let readTimeout = HTTPClientError ( code: . readTimeout)
392+ /// Remote connection was closed unexpectedly.
290393public static let remoteConnectionClosed = HTTPClientError ( code: . remoteConnectionClosed)
394+ /// Request was cancelled.
291395public static let cancelled = HTTPClientError ( code: . cancelled)
396+ /// Request contains invalid identity encoding.
292397public static let identityCodingIncorrectlyPresent = HTTPClientError ( code: . identityCodingIncorrectlyPresent)
398+ /// Request contains multiple chunks definitions.
293399public static let chunkedSpecifiedMultipleTimes = HTTPClientError ( code: . chunkedSpecifiedMultipleTimes)
400+ /// Proxy response was invalid.
294401public static let invalidProxyResponse = HTTPClientError ( code: . invalidProxyResponse)
402+ /// Request does not contain `Content-Length` header.
295403public static let contentLengthMissing = HTTPClientError ( code: . contentLengthMissing)
296404}
0 commit comments