diff --git a/Package.swift b/Package.swift index 3ff924e44..25fd3b6b7 100644 --- a/Package.swift +++ b/Package.swift @@ -16,9 +16,9 @@ import PackageDescription let package = Package( - name: "SwiftNIOHTTP", + name: "swift-nio-http-client", products: [ - .library(name: "SwiftNIOHTTP", targets: ["SwiftNIOHTTP"]), + .library(name: "NIOHTTPClient", targets: ["NIOHTTPClient"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"), @@ -26,10 +26,10 @@ let package = Package( ], targets: [ .target( - name: "SwiftNIOHTTP", + name: "NIOHTTPClient", dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers"]), .testTarget( - name: "SwiftNIOHTTPTests", - dependencies: ["SwiftNIOHTTP"]), + name: "NIOHTTPClientTests", + dependencies: ["NIOHTTPClient"]), ] ) diff --git a/README.md b/README.md index c9c06e0de..b8ee345ad 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,148 @@ -# swift-nio-http-client +# SwiftNIOHTTPClient +This package provides simple HTTP Client library built on top of SwiftNIO. -Swift HTTP Client library built on top of SwiftNIO +This library provides the following: +1. Asynchronous and non-blocking request methods +2. Simple follow-redirects (cookie headers are dropped) +3. Streaming body download +4. TLS support +5. Cookie parsing (but not storage) + +--- + +**NOTE**: You will need [Xcode 10.2](https://itunes.apple.com/us/app/xcode/id497799835) or [Swift 5.0](https://swift.org/download/#swift-50) to try out `SwiftNIOHTTPClient`. + +--- + +## Getting Started + +#### Adding the dependency +Add the following entry in your Package.swift to start using HTTPClient: + +```swift +// it's early days here so we haven't tagged a version yet, but will soon +.package(url: "https://github.com/swift-server/swift-nio-http-client.git", .branch("master")) +``` +and ```NIOHTTPClient``` dependency to your target: +```swift +.target(name: "MyApp", dependencies: ["NIOHTTPClient"]), +``` + +#### Request-Response API +The code snippet below illustrates how to make a simple GET request to a remote server: + +```swift +import NIOHTTPClient + +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +httpClient.get(url: "https://swift.org").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 client instance after use to cleanly shutdown underlying NIO ```EventLoopGroup```: +``` +try? httpClient.syncShutdown() +``` +Alternatively, you can provide shared ```EventLoopGroup```: +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .shared(userProvidedGroup)) +``` +In this case shutdown of the client is not neccecary. + +## Usage guide + +Most common HTTP methods are supported out of the box. In case you need to have more control over the method, or you want to add headers or body, use ```HTTPRequest``` struct: +```swift +import NIOHTTPClient + +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +defer { + try? httpClient.syncShutdown() +} + +var request = try HTTPRequest(url: "https://swift.org", method: .POST) +request.headers.add(name: "User-Agent", value: "Swift HTTPClient") +request.body = .string("some-body") + +httpClient.execute(request: request).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 + } + } +} +``` + +### Redirects following +Enable follow-redirects behavior using the client configuration: +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + configuration: HTTPClientConfiguration(followRedirects: true)) +``` + +### Timeouts +Timeouts (connect and read) can also be set using the client configuration: +```swift +let timeout = Timeout(connectTimeout: .seconds(1), readTimeout: .seconds(1)) +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + configuration: HTTPClientConfiguration(timeout: timeout)) +``` +or on per-request basis: +```swift +let timeout = Timeout(connectTimeout: .seconds(1), readTimeout: .seconds(1)) +httpClient.execute(request: request, timeout: timeout) +``` + +### Streaming +When dealing with larger amount of data, it's critical to stream the response body instead of aggregating in-memory. Handling a response stream is done using a delegate protocol. The following example demonstrates how to count the number of bytes in a streaming response body: +```swift +class CountingDelegate: HTTPResponseDelegate { + typealias Response = Int + + var count = 0 + + func didTransmitRequestBody() { + // this is executed when request is sent, called once + } + + func didReceiveHead(_ head: HTTPResponseHead) { + // this is executed when we receive HTTP Reponse head part of the request (it contains response code and headers), called once + } + + func didReceivePart(_ buffer: ByteBuffer) { + // this is executed when we receive parts of the response body, could be called zero or more times + count += buffer.readableBytes + } + + func didFinishRequest() throws -> Int { + // this is called when request is fully read, called once, this is where you return a result or throw any errors you require to propagate to the client + return count + } + + func didReceiveError(_ error: Error) { + // this is called when we receive any network-related error, called once + } +} + +let request = try HTTPRequest(url: "https://swift.org") +let delegate = CountingDelegate() + +try httpClient.execute(request: request, delegate: delegate).future.whenSuccess { count in + print(count) +} +``` diff --git a/Sources/SwiftNIOHTTP/HTTPCookie.swift b/Sources/NIOHTTPClient/HTTPCookie.swift similarity index 100% rename from Sources/SwiftNIOHTTP/HTTPCookie.swift rename to Sources/NIOHTTPClient/HTTPCookie.swift diff --git a/Sources/SwiftNIOHTTP/HTTPHandler.swift b/Sources/NIOHTTPClient/HTTPHandler.swift similarity index 100% rename from Sources/SwiftNIOHTTP/HTTPHandler.swift rename to Sources/NIOHTTPClient/HTTPHandler.swift diff --git a/Sources/SwiftNIOHTTP/RequestValidation.swift b/Sources/NIOHTTPClient/RequestValidation.swift similarity index 100% rename from Sources/SwiftNIOHTTP/RequestValidation.swift rename to Sources/NIOHTTPClient/RequestValidation.swift diff --git a/Sources/SwiftNIOHTTP/SwiftNIOHTTP.swift b/Sources/NIOHTTPClient/SwiftNIOHTTP.swift similarity index 100% rename from Sources/SwiftNIOHTTP/SwiftNIOHTTP.swift rename to Sources/NIOHTTPClient/SwiftNIOHTTP.swift diff --git a/Sources/SwiftNIOHTTP/Utils.swift b/Sources/NIOHTTPClient/Utils.swift similarity index 100% rename from Sources/SwiftNIOHTTP/Utils.swift rename to Sources/NIOHTTPClient/Utils.swift diff --git a/Tests/SwiftNIOHTTPTests/HTTPClientTestUtils.swift b/Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift similarity index 99% rename from Tests/SwiftNIOHTTPTests/HTTPClientTestUtils.swift rename to Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift index 41e016500..5a20679ac 100644 --- a/Tests/SwiftNIOHTTPTests/HTTPClientTestUtils.swift +++ b/Tests/NIOHTTPClientTests/HTTPClientTestUtils.swift @@ -16,7 +16,7 @@ import Foundation import NIO import NIOHTTP1 import NIOSSL -@testable import SwiftNIOHTTP +@testable import NIOHTTPClient class TestHTTPDelegate : HTTPResponseDelegate { typealias Response = Void diff --git a/Tests/SwiftNIOHTTPTests/HTTPCookieTests.swift b/Tests/NIOHTTPClientTests/HTTPCookieTests.swift similarity index 98% rename from Tests/SwiftNIOHTTPTests/HTTPCookieTests.swift rename to Tests/NIOHTTPClientTests/HTTPCookieTests.swift index 092d15339..b314939b3 100644 --- a/Tests/SwiftNIOHTTPTests/HTTPCookieTests.swift +++ b/Tests/NIOHTTPClientTests/HTTPCookieTests.swift @@ -14,7 +14,7 @@ import Foundation import XCTest -@testable import SwiftNIOHTTP +@testable import NIOHTTPClient class HTTPCookieTests: XCTestCase { diff --git a/Tests/SwiftNIOHTTPTests/SwiftNIOHTTPTests.swift b/Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift similarity index 99% rename from Tests/SwiftNIOHTTPTests/SwiftNIOHTTPTests.swift rename to Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift index ef41b9b2b..4b4124d68 100644 --- a/Tests/SwiftNIOHTTPTests/SwiftNIOHTTPTests.swift +++ b/Tests/NIOHTTPClientTests/SwiftNIOHTTPTests.swift @@ -17,7 +17,7 @@ import NIO import NIOSSL import XCTest @testable import NIOHTTP1 -@testable import SwiftNIOHTTP +@testable import NIOHTTPClient class SwiftHTTPTests: XCTestCase { diff --git a/Tests/SwiftNIOHTTPTests/XCTestManifests.swift b/Tests/NIOHTTPClientTests/XCTestManifests.swift similarity index 100% rename from Tests/SwiftNIOHTTPTests/XCTestManifests.swift rename to Tests/NIOHTTPClientTests/XCTestManifests.swift