diff --git a/README.md b/README.md index c9c06e0de..6745d40fb 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 ```SwiftNIOHTTP``` 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 HTTPClient + +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 HTTPClient + +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) +} +```