diff --git a/Package.swift b/Package.swift index a9ea2ba7f..a60f012aa 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.4 +// swift-tools-version:5.6 //===----------------------------------------------------------------------===// // // This source file is part of the AsyncHTTPClient open source project @@ -28,6 +28,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"), .package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"), .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ .target(name: "CAsyncHTTPClient"), diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift new file mode 100644 index 000000000..a9ea2ba7f --- /dev/null +++ b/Package@swift-5.4.swift @@ -0,0 +1,74 @@ +// swift-tools-version:5.4 +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let package = Package( + name: "async-http-client", + products: [ + .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.38.0"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.1"), + .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"), + .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"), + .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + ], + targets: [ + .target(name: "CAsyncHTTPClient"), + .target( + name: "AsyncHTTPClient", + dependencies: [ + .target(name: "CAsyncHTTPClient"), + .product(name: "NIO", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOHTTP1", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "NIOHTTP2", package: "swift-nio-http2"), + .product(name: "NIOSSL", package: "swift-nio-ssl"), + .product(name: "NIOHTTPCompression", package: "swift-nio-extras"), + .product(name: "NIOSOCKS", package: "swift-nio-extras"), + .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Atomics", package: "swift-atomics"), + ] + ), + .testTarget( + name: "AsyncHTTPClientTests", + dependencies: [ + .target(name: "AsyncHTTPClient"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "NIOTestUtils", package: "swift-nio"), + .product(name: "NIOSSL", package: "swift-nio-ssl"), + .product(name: "NIOHTTP2", package: "swift-nio-http2"), + .product(name: "NIOSOCKS", package: "swift-nio-extras"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Atomics", package: "swift-atomics"), + ], + resources: [ + .copy("Resources/self_signed_cert.pem"), + .copy("Resources/self_signed_key.pem"), + ] + ), + ] +) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift new file mode 100644 index 000000000..a9ea2ba7f --- /dev/null +++ b/Package@swift-5.5.swift @@ -0,0 +1,74 @@ +// swift-tools-version:5.4 +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let package = Package( + name: "async-http-client", + products: [ + .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.38.0"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.1"), + .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"), + .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"), + .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), + ], + targets: [ + .target(name: "CAsyncHTTPClient"), + .target( + name: "AsyncHTTPClient", + dependencies: [ + .target(name: "CAsyncHTTPClient"), + .product(name: "NIO", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOHTTP1", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "NIOHTTP2", package: "swift-nio-http2"), + .product(name: "NIOSSL", package: "swift-nio-ssl"), + .product(name: "NIOHTTPCompression", package: "swift-nio-extras"), + .product(name: "NIOSOCKS", package: "swift-nio-extras"), + .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Atomics", package: "swift-atomics"), + ] + ), + .testTarget( + name: "AsyncHTTPClientTests", + dependencies: [ + .target(name: "AsyncHTTPClient"), + .product(name: "NIOCore", package: "swift-nio"), + .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "NIOTestUtils", package: "swift-nio"), + .product(name: "NIOSSL", package: "swift-nio-ssl"), + .product(name: "NIOHTTP2", package: "swift-nio-http2"), + .product(name: "NIOSOCKS", package: "swift-nio-extras"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Atomics", package: "swift-atomics"), + ], + resources: [ + .copy("Resources/self_signed_cert.pem"), + .copy("Resources/self_signed_key.pem"), + ] + ), + ] +) diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift index cfab828a0..a76b9239a 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift @@ -16,12 +16,21 @@ import NIOCore import NIOHTTP1 +/// A representation of an HTTP request for the Swift Concurrency HTTPClient API. +/// +/// This object is similar to ``HTTPClient/Request``, but used for the Swift Concurrency API. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public struct HTTPClientRequest { + /// The request URL, including scheme, hostname, and optionally port. public var url: String + + /// The request method. public var method: HTTPMethod + + /// The request headers. public var headers: HTTPHeaders + /// The request body, if any. public var body: Body? public init(url: String) { @@ -34,6 +43,10 @@ public struct HTTPClientRequest { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientRequest { + /// An HTTP request body. + /// + /// This object encapsulates the difference between streamed HTTP request bodies and those bodies that + /// are already entirely in memory. public struct Body { @usableFromInline internal enum Mode { @@ -54,10 +67,20 @@ extension HTTPClientRequest { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientRequest.Body { + /// Create an ``HTTPClientRequest/Body-swift.struct`` from a `ByteBuffer`. + /// + /// - parameter byteBuffer: The bytes of the body. public static func bytes(_ byteBuffer: ByteBuffer) -> Self { self.init(.byteBuffer(byteBuffer)) } + /// Create an ``HTTPClientRequest/Body-swift.struct`` from a `RandomAccessCollection` of bytes. + /// + /// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory + /// usage of this construction will be double the size of the original collection. The construction + /// of the `ByteBuffer` will be delayed until it's needed. + /// + /// - parameter bytes: The bytes of the request body. @inlinable public static func bytes( _ bytes: Bytes @@ -75,6 +98,23 @@ extension HTTPClientRequest.Body { }) } + /// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Sequence` of bytes. + /// + /// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory + /// usage of this construction will be double the size of the original collection. The construction + /// of the `ByteBuffer` will be delayed until it's needed. + /// + /// Unlike ``bytes(_:)-1uns7``, this construction does not assume that the body can be replayed. As a result, + /// if a redirect is encountered that would need us to replay the request body, the redirect will instead + /// not be followed. Prefer ``bytes(_:)-1uns7`` wherever possible. + /// + /// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths + /// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload + /// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`. + /// + /// - parameters: + /// - bytes: The bytes of the request body. + /// - length: The length of the request body. @inlinable public static func bytes( _ bytes: Bytes, @@ -93,6 +133,19 @@ extension HTTPClientRequest.Body { }) } + /// Create an ``HTTPClientRequest/Body-swift.struct`` from a `Collection` of bytes. + /// + /// This construction will flatten the bytes into a `ByteBuffer`. As a result, the peak memory + /// usage of this construction will be double the size of the original collection. The construction + /// of the `ByteBuffer` will be delayed until it's needed. + /// + /// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths + /// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload + /// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`. + /// + /// - parameters: + /// - bytes: The bytes of the request body. + /// - length: The length of the request body. @inlinable public static func bytes( _ bytes: Bytes, @@ -111,6 +164,17 @@ extension HTTPClientRequest.Body { }) } + /// Create an ``HTTPClientRequest/Body-swift.struct`` from an `AsyncSequence` of `ByteBuffer`s. + /// + /// This construction will stream the upload one `ByteBuffer` at a time. + /// + /// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths + /// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload + /// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`. + /// + /// - parameters: + /// - sequenceOfBytes: The bytes of the request body. + /// - length: The length of the request body. @inlinable public static func stream( _ sequenceOfBytes: SequenceOfBytes, @@ -123,6 +187,19 @@ extension HTTPClientRequest.Body { return body } + /// Create an ``HTTPClientRequest/Body-swift.struct`` from an `AsyncSequence` of bytes. + /// + /// This construction will consume 1kB chunks from the `Bytes` and send them at once. This optimizes for + /// `AsyncSequence`s where larger chunks are buffered up and available without actually suspending, such + /// as those provided by `FileHandle`. + /// + /// Caution should be taken with this method to ensure that the `length` is correct. Incorrect lengths + /// will cause unnecessary runtime failures. Setting `length` to ``Length/unknown`` will trigger the upload + /// to use `chunked` `Transfer-Encoding`, while using ``Length/known(_:)`` will use `Content-Length`. + /// + /// - parameters: + /// - bytes: The bytes of the request body. + /// - length: The length of the request body. @inlinable public static func stream( _ bytes: Bytes, @@ -157,10 +234,12 @@ extension Optional where Wrapped == HTTPClientRequest.Body { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension HTTPClientRequest.Body { + /// The length of a HTTP request body. public struct Length { - /// size of the request body is not known before starting the request + /// The size of the request body is not known before starting the request public static let unknown: Self = .init(storage: .unknown) - /// size of the request body is fixed and exactly `count` bytes + + /// The size of the request body is known and exactly `count` bytes public static func known(_ count: Int) -> Self { .init(storage: .known(count)) } diff --git a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift index 52f03089b..7ccd74530 100644 --- a/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift +++ b/Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift @@ -16,13 +16,28 @@ import NIOCore import NIOHTTP1 +/// A representation of an HTTP response for the Swift Concurrency HTTPClient API. +/// +/// This object is similar to ``HTTPClient/Response``, but used for the Swift Concurrency API. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public struct HTTPClientResponse { + /// The HTTP version on which the response was received. public var version: HTTPVersion + + /// The HTTP status for this response. public var status: HTTPResponseStatus + + /// The HTTP headers of this response. public var headers: HTTPHeaders + + /// The body of this HTTP response. public var body: Body + /// A representation of the response body for an HTTP response. + /// + /// The body is streamed as an `AsyncSequence` of `ByteBuffer`, where each `ByteBuffer` contains + /// an arbitrarily large chunk of data. The boundaries between `ByteBuffer` objects in the sequence + /// are entirely synthetic and have no semantic meaning. public struct Body { private let bag: Transaction private let reference: ResponseRef diff --git a/Sources/AsyncHTTPClient/Docs.docc/index.md b/Sources/AsyncHTTPClient/Docs.docc/index.md new file mode 100644 index 000000000..acb408684 --- /dev/null +++ b/Sources/AsyncHTTPClient/Docs.docc/index.md @@ -0,0 +1,349 @@ +# ``AsyncHTTPClient`` + +This package provides simple HTTP Client library built on top of SwiftNIO. + +This library provides the following: +- First class support for Swift Concurrency (since version 1.9.0) +- Asynchronous and non-blocking request methods +- Simple follow-redirects (cookie headers are dropped) +- Streaming body download +- TLS support +- Automatic HTTP/2 over HTTPS (since version 1.7.0) +- Cookie parsing (but not storage) + +--- + +**NOTE**: You will need [Xcode 13.2](https://apps.apple.com/gb/app/xcode/id497799835?mt=12) or [Swift 5.5.2](https://swift.org/download/#swift-552) to try out `AsyncHTTPClient`s new async/await APIs. + +--- + +## Getting Started + +#### Adding the dependency + +Add the following entry in your Package.swift to start using HTTPClient: + +```swift +.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0") +``` +and `AsyncHTTPClient` dependency to your target: +```swift +.target(name: "MyApp", dependencies: [.product(name: "AsyncHTTPClient", package: "async-http-client")]), +``` + +#### Request-Response API + +The code snippet below illustrates how to make a simple GET request to a remote server. + +Please note that the example will spawn a new `EventLoopGroup` which will _create fresh threads_ which is a very costly operation. In a real-world application that uses [SwiftNIO](https://github.com/apple/swift-nio) for other parts of your application (for example a web server), please prefer `eventLoopGroupProvider: .shared(myExistingEventLoopGroup)` to share the `EventLoopGroup` used by AsyncHTTPClient with other parts of your application. + +If your application does not use SwiftNIO yet, it is acceptable to use `eventLoopGroupProvider: .createNew` but please make sure to share the returned `HTTPClient` instance throughout your whole application. Do not create a large number of `HTTPClient` instances with `eventLoopGroupProvider: .createNew`, this is very wasteful and might exhaust the resources of your program. + +```swift +import AsyncHTTPClient + +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + +/// MARK: - Using Swift Concurrency +let request = HTTPClientRequest(url: "https://apple.com/") +let response = try await httpClient.execute(request, timeout: .seconds(30)) +print("HTTP head", response) +if response.status == .ok { + let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB + // handle body +} else { + // handle remote error +} + + +/// MARK: - Using SwiftNIO EventLoopFuture +httpClient.get(url: "https://apple.com/").whenComplete { result in + switch result { + case .failure(let error): + // process error + case .success(let response): + if response.status == .ok { + // handle response + } else { + // handle remote error + } + } +} +``` + +You should always shut down ``HTTPClient`` instances you created using ``HTTPClient/syncShutdown()``. Please note that you must not call ``HTTPClient/syncShutdown()`` before all requests of the HTTP client have finished, or else the in-flight requests will likely fail because their network connections are interrupted. + +### async/await examples + +Examples for the async/await API can be found in the [`Examples` folder](https://github.com/swift-server/async-http-client/tree/main/Examples) in the repository. + +## Usage guide + +The default HTTP Method is `GET`. In case you need to have more control over the method, or you want to add headers or body, use the ``HTTPClientRequest`` struct: + +#### Using Swift Concurrency + +```swift +import AsyncHTTPClient + +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +do { + var request = HTTPClientRequest(url: "https://apple.com/") + request.method = .POST + request.headers.add(name: "User-Agent", value: "Swift HTTPClient") + request.body = .bytes(ByteBuffer(string: "some data")) + + let response = try await httpClient.execute(request, timeout: .seconds(30)) + if response.status == .ok { + // handle response + } else { + // handle remote error + } +} catch { + // handle error +} +// it's important to shutdown the httpClient after all requests are done, even if one failed +try await httpClient.shutdown() +``` + +#### Using SwiftNIO EventLoopFuture + +```swift +import AsyncHTTPClient + +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +defer { + try? httpClient.syncShutdown() +} + +var request = try HTTPClient.Request(url: "https://apple.com/", 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 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: HTTPClient.Configuration(followRedirects: true)) +``` + +### Timeouts +Timeouts (connect and read) can also be set using the client configuration: +```swift +let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(1), read: .seconds(1)) +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + configuration: HTTPClient.Configuration(timeout: timeout)) +``` +or on a per-request basis: +```swift +httpClient.execute(request: request, deadline: .now() + .milliseconds(1)) +``` + +### Streaming +When dealing with larger amount of data, it's critical to stream the response body instead of aggregating in-memory. +The following example demonstrates how to count the number of bytes in a streaming response body: + +#### Using Swift Concurrency +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +do { + let request = HTTPClientRequest(url: "https://apple.com/") + let response = try await httpClient.execute(request, timeout: .seconds(30)) + print("HTTP head", response) + + // if defined, the content-length headers announces the size of the body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + + var receivedBytes = 0 + // asynchronously iterates over all body fragments + // this loop will automatically propagate backpressure correctly + for try await buffer in response.body { + // for this example, we are just interested in the size of the fragment + receivedBytes += buffer.readableBytes + + if let expectedBytes = expectedBytes { + // if the body size is known, we calculate a progress indicator + let progress = Double(receivedBytes) / Double(expectedBytes) + print("progress: \(Int(progress * 100))%") + } + } + print("did receive \(receivedBytes) bytes") +} catch { + print("request failed:", error) +} +// it is important to shutdown the httpClient after all requests are done, even if one failed +try await httpClient.shutdown() +``` + +#### Using HTTPClientResponseDelegate and SwiftNIO EventLoopFuture + +```swift +import NIOCore +import NIOHTTP1 + +class CountingDelegate: HTTPClientResponseDelegate { + typealias Response = Int + + var count = 0 + + func didSendRequestHead(task: HTTPClient.Task, _ head: HTTPRequestHead) { + // this is executed right after request head was sent, called once + } + + func didSendRequestPart(task: HTTPClient.Task, _ part: IOData) { + // this is executed when request body part is sent, could be called zero or more times + } + + func didSendRequest(task: HTTPClient.Task) { + // this is executed when request is fully sent, called once + } + + func didReceiveHead( + task: HTTPClient.Task, + _ head: HTTPResponseHead + ) -> EventLoopFuture { + // this is executed when we receive HTTP response head part of the request + // (it contains response code and headers), called once in case backpressure + // is needed, all reads will be paused until returned future is resolved + return task.eventLoop.makeSucceededFuture(()) + } + + func didReceiveBodyPart( + task: HTTPClient.Task, + _ buffer: ByteBuffer + ) -> EventLoopFuture { + // this is executed when we receive parts of the response body, could be called zero or more times + count += buffer.readableBytes + // in case backpressure is needed, all reads will be paused until returned future is resolved + return task.eventLoop.makeSucceededFuture(()) + } + + func didFinishRequest(task: HTTPClient.Task) throws -> Int { + // this is called when the 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(task: HTTPClient.Task, _ error: Error) { + // this is called when we receive any network-related error, called once + } +} + +let request = try HTTPClient.Request(url: "https://apple.com/") +let delegate = CountingDelegate() + +httpClient.execute(request: request, delegate: delegate).futureResult.whenSuccess { count in + print(count) +} +``` + +### File downloads + +Based on the `HTTPClientResponseDelegate` example above you can build more complex delegates, +the built-in `FileDownloadDelegate` is one of them. It allows streaming the downloaded data +asynchronously, while reporting the download progress at the same time, like in the following +example: + +```swift +let client = HTTPClient(eventLoopGroupProvider: .createNew) +let request = try HTTPClient.Request( + url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml" +) + +let delegate = try FileDownloadDelegate(path: "/tmp/latest-build.yml", reportProgress: { + if let totalBytes = $0.totalBytes { + print("Total bytes count: \(totalBytes)") + } + print("Downloaded \($0.receivedBytes) bytes so far") +}) + +client.execute(request: request, delegate: delegate).futureResult + .whenSuccess { progress in + if let totalBytes = progress.totalBytes { + print("Final total bytes count: \(totalBytes)") + } + print("Downloaded finished with \(progress.receivedBytes) bytes downloaded") + } +``` + +### Unix Domain Socket Paths +Connecting to servers bound to socket paths is easy: +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +httpClient.execute( + .GET, + socketPath: "/tmp/myServer.socket", + urlPath: "/path/to/resource" +).whenComplete (...) +``` + +Connecting over TLS to a unix domain socket path is possible as well: +```swift +let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +httpClient.execute( + .POST, + secureSocketPath: "/tmp/myServer.socket", + urlPath: "/path/to/resource", + body: .string("hello") +).whenComplete (...) +``` + +Direct URLs can easily be constructed to be executed in other scenarios: +```swift +let socketPathBasedURL = URL( + httpURLWithSocketPath: "/tmp/myServer.socket", + uri: "/path/to/resource" +) +let secureSocketPathBasedURL = URL( + httpsURLWithSocketPath: "/tmp/myServer.socket", + uri: "/path/to/resource" +) +``` + +### Disabling HTTP/2 +The exclusive use of HTTP/1 is possible by setting ``HTTPClient/Configuration/httpVersion-swift.property`` to ``HTTPClient/Configuration/HTTPVersion-swift.struct/http1Only`` on the ``HTTPClient/Configuration``: +```swift +var configuration = HTTPClient.Configuration() +configuration.httpVersion = .http1Only +let client = HTTPClient( + eventLoopGroupProvider: .createNew, + configuration: configuration +) +``` + +## Security + +AsyncHTTPClient's security process is documented on [GitHub](https://github.com/swift-server/async-http-client/blob/main/SECURITY.md). + +## Topics + +### HTTPClient + +- ``HTTPClient`` +- ``HTTPClientRequest`` +- ``HTTPClientResponse`` + +### HTTP Client Delegates + +- ``HTTPClientResponseDelegate`` +- ``ResponseAccumulator`` +- ``FileDownloadDelegate`` +- ``HTTPClientCopyingDelegate`` + +### Errors + +- ``HTTPClientError`` diff --git a/Sources/AsyncHTTPClient/FileDownloadDelegate.swift b/Sources/AsyncHTTPClient/FileDownloadDelegate.swift index 6f046dce9..75f16f52a 100644 --- a/Sources/AsyncHTTPClient/FileDownloadDelegate.swift +++ b/Sources/AsyncHTTPClient/FileDownloadDelegate.swift @@ -38,6 +38,7 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate { private var writeFuture: EventLoopFuture? /// Initializes a new file download delegate. + /// /// - parameters: /// - path: Path to a file you'd like to write the download to. /// - pool: A thread pool to use for asynchronous file I/O. diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 094a6d052..3fbdb1366 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -65,6 +65,9 @@ let globalRequestID = ManagedAtomic(0) /// try client.syncShutdown() /// ``` public class HTTPClient { + /// The `EventLoopGroup` in use by this ``HTTPClient``. + /// + /// All HTTP transactions will occur on loops owned by this group. public let eventLoopGroup: EventLoopGroup let eventLoopGroupProvider: EventLoopGroupProvider let configuration: Configuration @@ -74,7 +77,7 @@ public class HTTPClient { internal static let loggingDisabled = Logger(label: "AHC-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() }) - /// 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. @@ -86,7 +89,7 @@ public class HTTPClient { backgroundActivityLogger: HTTPClient.loggingDisabled) } - /// 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. @@ -180,7 +183,9 @@ public class HTTPClient { } } - /// Shuts down the client and event loop gracefully. This function is clearly an outlier in that it uses a completion + /// Shuts down the client and event loop gracefully. + /// + /// This function is clearly an outlier in that it uses a completion /// callback instead of an EventLoopFuture. The reason for that is that NIO's EventLoopFutures will call back on an event loop. /// The virtue of this function is to shut the event loop down. To work around that we call back on a DispatchQueue /// instead. @@ -623,11 +628,11 @@ public class HTTPClient { return task } - /// `HTTPClient` configuration. + /// ``HTTPClient`` configuration. public struct Configuration { /// TLS configuration, defaults to `TLSConfiguration.makeClientConfiguration()`. public var tlsConfiguration: Optional - /// Enables following 3xx redirects automatically, defaults to `RedirectConfiguration()`. + /// Enables following 3xx redirects automatically. /// /// Following redirects are supported: /// - `301: Moved Permanently` @@ -638,7 +643,8 @@ public class HTTPClient { /// - `307: Temporary Redirect` /// - `308: Permanent Redirect` public var redirectConfiguration: RedirectConfiguration - /// Default client timeout, defaults to no `read` timeout and 10 seconds `connect` timeout. + /// Default client timeout, defaults to no ``Timeout-swift.struct/read`` timeout + /// and 10 seconds ``Timeout-swift.struct/connect`` timeout. public var timeout: Timeout /// Connection pool configuration. public var connectionPool: ConnectionPool @@ -653,10 +659,12 @@ public class HTTPClient { set {} } - /// is set to `.automatic` by default which will use HTTP/2 if run over https and the server supports it, otherwise HTTP/1 + /// What HTTP versions to use. + /// + /// Set to ``HTTPVersion-swift.struct/automatic`` by default which will use HTTP/2 if run over https and the server supports it, otherwise HTTP/1 public var httpVersion: HTTPVersion - /// Whether `HTTPClient` will let Network.framework sit in the `.waiting` state awaiting new network changes, or fail immediately. Defaults to `true`, + /// Whether ``HTTPClient`` will let Network.framework sit in the `.waiting` state awaiting new network changes, or fail immediately. Defaults to `true`, /// which is the recommended setting. Only set this to `false` when attempting to trigger a particular error path. public var networkFrameworkWaitForConnectivity: Bool @@ -755,11 +763,11 @@ public class HTTPClient { 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. + /// `EventLoopGroup` will be created by the client. When ``HTTPClient/syncShutdown()`` is called, the created `EventLoopGroup` will be shut down as well. case createNew } - /// Specifies how the library will treat event loop passed by the user. + /// Specifies how the library will treat the event loop passed by the user. public struct EventLoopPreference { enum Preference { /// Event Loop will be selected by the library. @@ -830,8 +838,7 @@ extension HTTPClient.Configuration { /// Create timeout. /// /// - parameters: - /// - connect: `connect` timeout. Will default to 10 seconds, if no value is - /// provided. See `var connectionCreationTimeout` + /// - connect: `connect` timeout. Will default to 10 seconds, if no value is provided. /// - read: `read` timeout. public init(connect: TimeAmount? = nil, read: TimeAmount? = nil) { self.connect = connect @@ -897,7 +904,7 @@ extension HTTPClient.Configuration { case automatic } - /// we only use HTTP/1, even if the server would supports HTTP/2 + /// We will only use HTTP/1, even if the server would supports HTTP/2 public static let http1Only: Self = .init(configuration: .http1Only) /// HTTP/2 is used if we connect to a server with HTTPS and the server supports HTTP/2, otherwise we use HTTP/1 diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index c1ce39632..aeef71ba4 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -20,13 +20,15 @@ import NIOHTTP1 import NIOSSL extension HTTPClient { - /// Represent request body. + /// A request body. public struct Body { - /// Chunk provider. + /// A streaming uploader. + /// + /// ``StreamWriter`` abstracts public struct StreamWriter { let closure: (IOData) -> EventLoopFuture - /// Create new StreamWriter + /// Create new ``HTTPClient/Body/StreamWriter`` /// /// - parameters: /// - closure: function that will be called to write actual bytes to the channel. @@ -43,7 +45,7 @@ extension HTTPClient { } } - /// Body size. if nil,`Transfer-Encoding` will automatically be set to `chunked`. Otherwise a `Content-Length` + /// Body size. If nil,`Transfer-Encoding` will automatically be set to `chunked`. Otherwise a `Content-Length` /// header is set with the given `length`. public var length: Int? /// Body chunk provider. @@ -65,7 +67,7 @@ extension HTTPClient { } } - /// Create and stream body using `StreamWriter`. + /// Create and stream body using ``StreamWriter``. /// /// - parameters: /// - length: Body size. If nil, `Transfer-Encoding` will automatically be set to `chunked`. Otherwise a `Content-Length` @@ -97,7 +99,7 @@ extension HTTPClient { } } - /// Represent HTTP request. + /// Represents an HTTP request. public struct Request { /// Request HTTP method, defaults to `GET`. public let method: HTTPMethod @@ -226,7 +228,7 @@ extension HTTPClient { } } - /// Represent HTTP response. + /// Represents an HTTP response. public struct Response { /// Remote host of the request. public var host: String @@ -272,7 +274,7 @@ extension HTTPClient { } } - /// HTTP authentication + /// HTTP authentication. public struct Authorization: Hashable { private enum Scheme: Hashable { case Basic(String) @@ -285,18 +287,24 @@ extension HTTPClient { self.scheme = scheme } + /// HTTP basic auth. public static func basic(username: String, password: String) -> HTTPClient.Authorization { return .basic(credentials: Base64.encode(bytes: "\(username):\(password)".utf8)) } + /// HTTP basic auth. + /// + /// This version uses the raw string directly. public static func basic(credentials: String) -> HTTPClient.Authorization { return .init(scheme: .Basic(credentials)) } + /// HTTP bearer auth public static func bearer(tokens: String) -> HTTPClient.Authorization { return .init(scheme: .Bearer(tokens)) } + /// The header string for this auth field. public var headerValue: String { switch self.scheme { case .Basic(let credentials): @@ -308,6 +316,10 @@ extension HTTPClient { } } +/// The default ``HTTPClientResponseDelegate``. +/// +/// This ``HTTPClientResponseDelegate`` buffers a complete HTTP response in memory. It does not stream the response body in. +/// The resulting ``Response`` type is ``HTTPClient/Response``. public class ResponseAccumulator: HTTPClientResponseDelegate { public typealias Response = HTTPClient.Response @@ -385,32 +397,34 @@ public class ResponseAccumulator: HTTPClientResponseDelegate { } } -/// `HTTPClientResponseDelegate` allows an implementation to receive notifications about request processing and to control how response 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-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./ +/// class to the ``HTTPClient/execute(request:delegate:eventLoop:deadline:)`` method and this package will call each delegate method appropriately as the request takes place. /// /// ### Backpressure /// -/// A `HTTPClientResponseDelegate` can be used to exert backpressure on the server response. This is achieved by way of the futures returned from -/// `didReceiveHead` and `didReceiveBodyPart`. The following functions are part of the "backpressure system" in the delegate: +/// A ``HTTPClientResponseDelegate`` can be used to exert backpressure on the server response. This is achieved by way of the futures returned from +/// ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd`` and ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``. +/// The following functions are part of the "backpressure system" in the delegate: /// -/// - `didReceiveHead` -/// - `didReceiveBodyPart` -/// - `didFinishRequest` -/// - `didReceiveError` +/// - ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd`` +/// - ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v`` +/// - ``HTTPClientResponseDelegate/didFinishRequest(task:)`` +/// - ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg`` /// -/// The first three methods are strictly _exclusive_, with that exclusivity managed by the futures returned by `didReceiveHead` and -/// `didReceiveBodyPart`. What this means is that until the returned future is completed, none of these three methods will be called -/// again. This allows delegates to rate limit the server to a capacity it can manage. `didFinishRequest` does not return a future, +/// The first three methods are strictly _exclusive_, with that exclusivity managed by the futures returned by ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd`` and +/// ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``. What this means is that until the returned future is completed, none of these three methods will be called +/// again. This allows delegates to rate limit the server to a capacity it can manage. ``HTTPClientResponseDelegate/didFinishRequest(task:)`` does not return a future, /// as we are expecting no more data from the server at this time. /// -/// `didReceiveError` is somewhat special: it signals the end of this regime. `didRecieveError` is not exclusive: it may be called at -/// any time, even if a returned future is not yet completed. `didReceiveError` is terminal, meaning that once it has been called none -/// of these four methods will be called again. This can be used as a signal to abandon all outstanding work. +/// ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg`` is somewhat special: it signals the end of this regime. ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg`` +/// is not exclusive: it may be called at any time, even if a returned future is not yet completed. ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg`` is terminal, meaning +/// that once it has been called none of these four methods will be called again. This can be used as a signal to abandon all outstanding work. /// /// - note: This delegate is strongly held by the `HTTPTaskHandler` -/// for the duration of the `Request` processing and will be +/// for the duration of the ``HTTPClient/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. @@ -428,7 +442,7 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// /// - parameters: /// - task: Current request context. - /// - part: Request body `Part`. + /// - part: Request body part. func didSendRequestPart(task: HTTPClient.Task, _ part: IOData) /// Called when the request is fully sent. Will be called once. @@ -451,7 +465,7 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// 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(())`. /// - /// This function will not be called until the future returned by `didReceiveHead` has completed. + /// This function will not be called until the future returned by ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd`` has completed. /// /// This function will not be called for subsequent body parts until the previous future returned by a /// call to this function completes. @@ -464,19 +478,22 @@ public protocol HTTPClientResponseDelegate: AnyObject { /// Called when error was thrown during request execution. Will be called zero or one time only. Request processing will be stopped after that. /// - /// This function may be called at any time: it does not respect the backpressure exerted by `didReceiveHead` and `didReceiveBodyPart`. - /// All outstanding work may be cancelled when this is received. Once called, no further calls will be made to `didReceiveHead`, `didReceiveBodyPart`, - /// or `didFinishRequest`. + /// This function may be called at any time: it does not respect the backpressure exerted by ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd`` + /// and ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``. + /// All outstanding work may be cancelled when this is received. Once called, no further calls will be made to + /// ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd``, ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``, + /// or ``HTTPClientResponseDelegate/didFinishRequest(task:)``. /// /// - parameters: /// - task: Current request context. /// - error: Error that occured during response processing. func didReceiveError(task: HTTPClient.Task, _ error: Error) - /// 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. + /// 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. /// - /// This function will not be called until all futures returned by `didReceiveHead` and `didReceiveBodyPart` have completed. Once called, - /// no further calls will be made to `didReceiveHead`, `didReceiveBodyPart`, or `didReceiveError`. + /// This function will not be called until all futures returned by ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd`` and ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v`` + /// have completed. Once called, no further calls will be made to ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd``, ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``, + /// or ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg``. /// /// - parameters: /// - task: Current request context. @@ -485,20 +502,38 @@ public protocol HTTPClientResponseDelegate: AnyObject { } extension HTTPClientResponseDelegate { + /// Default implementation of ``HTTPClientResponseDelegate/didSendRequestHead(task:_:)-6khai``. + /// + /// By default, this does nothing. public func didSendRequestHead(task: HTTPClient.Task, _ head: HTTPRequestHead) {} + /// Default implementation of ``HTTPClientResponseDelegate/didSendRequestPart(task:_:)-4qxap``. + /// + /// By default, this does nothing. public func didSendRequestPart(task: HTTPClient.Task, _ part: IOData) {} + /// Default implementation of ``HTTPClientResponseDelegate/didSendRequest(task:)-3vqgm``. + /// + /// By default, this does nothing. public func didSendRequest(task: HTTPClient.Task) {} + /// Default implementation of ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd``. + /// + /// By default, this does nothing. public func didReceiveHead(task: HTTPClient.Task, _: HTTPResponseHead) -> EventLoopFuture { - return task.eventLoop.makeSucceededFuture(()) + return task.eventLoop.makeSucceededVoidFuture() } + /// Default implementation of ``HTTPClientResponseDelegate/didReceiveBodyPart(task:_:)-4fd4v``. + /// + /// By default, this does nothing. public func didReceiveBodyPart(task: HTTPClient.Task, _: ByteBuffer) -> EventLoopFuture { - return task.eventLoop.makeSucceededFuture(()) + return task.eventLoop.makeSucceededVoidFuture() } + /// Default implementation of ``HTTPClientResponseDelegate/didReceiveError(task:_:)-fhsg``. + /// + /// By default, this does nothing. public func didReceiveError(task: HTTPClient.Task, _: Error) {} } @@ -560,7 +595,9 @@ protocol HTTPClientTaskDelegate { } extension HTTPClient { - /// Response execution context. Will be created by the library and could be used for obtaining + /// 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 { /// The `EventLoop` the delegate will be executed on. diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index f732c37f4..9796bc2af 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -21,6 +21,7 @@ import NIOTransportServices extension HTTPClient { #if canImport(Network) + /// A wrapper for `POSIX` errors thrown by `Network.framework`. public struct NWPOSIXError: Error, CustomStringConvertible { /// POSIX error code (enum) public let errorCode: POSIXErrorCode @@ -40,8 +41,9 @@ extension HTTPClient { public var description: String { return self.reason } } + /// A wrapper for TLS errors thrown by `Network.framework`. public struct NWTLSError: Error, CustomStringConvertible { - /// TLS error status. List of TLS errors can be found in + /// TLS error status. List of TLS errors can be found in `` public let status: OSStatus /// actual reason, in human readable form diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index f4154df3d..ed2819fd0 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -14,6 +14,10 @@ import NIOCore +/// An ``HTTPClientResponseDelegate`` that wraps a callback. +/// +/// ``HTTPClientCopyingDelegate`` discards most parts of a HTTP response, but streams the body +/// to the `chunkHandler` provided on ``init(chunkHandler:)``. This is mostly useful for testing. public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { public typealias Response = Void diff --git a/docker/docker-compose.1804.54.yaml b/docker/docker-compose.1804.54.yaml index 660429851..dd869549c 100644 --- a/docker/docker-compose.1804.54.yaml +++ b/docker/docker-compose.1804.54.yaml @@ -11,6 +11,7 @@ services: test: image: async-http-client:18.04-5.4 + command: /bin/bash -xcl "swift test --parallel -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" environment: [] #- SANITIZER_ARG=--sanitize=thread diff --git a/docker/docker-compose.2004.55.yaml b/docker/docker-compose.2004.55.yaml index 4d0a12ee7..71b75c0fe 100644 --- a/docker/docker-compose.2004.55.yaml +++ b/docker/docker-compose.2004.55.yaml @@ -11,6 +11,7 @@ services: test: image: async-http-client:20.04-5.5 + command: /bin/bash -xcl "swift test --parallel -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" environment: [] #- SANITIZER_ARG=--sanitize=thread diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 6269e953b..75699c60b 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -28,7 +28,7 @@ services: test: <<: *common - command: /bin/bash -xcl "swift test --parallel -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" + command: /bin/bash -xcl "swift test --parallel -Xswiftc -warnings-as-errors --enable-test-discovery $${SANITIZER_ARG-}" # util diff --git a/scripts/soundness.sh b/scripts/soundness.sh index da9a91d24..5170ec4f1 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -72,7 +72,7 @@ for language in swift-or-c bash dtrace; do matching_files=( -name '*' ) case "$language" in swift-or-c) - exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h) + exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h -o -name 'Package@swift*.swift' ) matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" //===----------------------------------------------------------------------===//