Skip to content

greatfire/SwiftyCurl

Repository files navigation

SwiftyCurl

Version License Platform

SwiftyCurl is an easily usable Swift and Objective-C wrapper for libcurl.

It uses native Darwin multithreading in conjunction with libcurl's "easy" interface, together with standard Foundation classes URLRequest and HTTPURLResponse.

Usage

    let request = URLRequest(url: .init(string: "http://google.com")!)

    let curl = SwiftyCurl()
    curl.followLocation = true
    curl.queue = .global(qos: .background)

    let progress = Progress()
    let observation = progress.observe(\.fractionCompleted) { progress, _ in
        print("Progress: \(progress.completedUnitCount) of \(progress.totalUnitCount) = \(progress.fractionCompleted)")
    }

    curl.perform(with: request, progress: progress) { data, response, error in
        print(String(data: data ?? .init(), encoding: .ascii) ?? "(nil)")

        if let response = response as? HTTPURLResponse {
            print("Response: \(response.url?.absoluteString ?? "(nil)") \(response.statusCode)\nheaders: \(response.allHeaderFields)")
        }

        if let error = error {
            print("Error: \(error)")
        }

        observation.invalidate()
    }

    // or
    
    Task {
        let task = curl.task(with: request)
        let observation2 = task?.progress.observe(\.fractionCompleted) { progress, _ in
            print("Progress2: \(progress.completedUnitCount) of \(progress.totalUnitCount) = \(progress.fractionCompleted)")
        }

        print("Ticket: \(task?.taskIdentifier ?? UInt.max)")

        do {
            let result = try await task?.resume()
            print(String(data: result?.0 ?? .init(), encoding: .ascii) ?? "(nil)")

            if let response = result?.1 as? HTTPURLResponse {
                print("Response2: \(response.url?.absoluteString ?? "(nil)") \(response.statusCode)\nheaders: \(response.allHeaderFields)")
            }
        }
        catch {
            print("Error: \(error)")
        }

        observation2?.invalidate()
    }
    
    // or
    
    let task = curl.task(with: request)
    task.delegate = self
    task.resume()
    
    
    // MARK: - CurlTaskDelegate

    func task(_ task: CurlTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) -> Bool {
        print("\(task) willPerformHTTPRedirection: \(response), newRequest: \(request)")

        return true
    }

    func task(_ task: CurlTask, isHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest) {
        print("\(task) isHTTPRedirection: \(response), newRequest: \(request)")
    }

    func task(_ task: CurlTask, didReceive challenge: URLAuthenticationChallenge) -> Bool {
        print("\(task) didReceive: \(challenge)")

        return true
    }

    func task(_ task: CurlTask, didReceive response: URLResponse) -> Bool {
        print("\(task) didReceive: \(response)")

        return true
    }

    func task(_ task: CurlTask, didReceive data: Data) -> Bool {
        print("\(task) didReceive: \(data)")

        return true
    }

    func task(_ task: CurlTask, didCompleteWithError error: (any Error)?) {
        print("\(task) didCompleteWithError: \(error?.localizedDescription ?? "(nil)")")
    }

Singleton

When you use SwiftyCurl in a lot of places, it is recommended, that you create a shared singleton, instead of constantly creating and destroying SwiftyCurl objects.

The reason for this is, that SwiftyCurl calls curl_global_init on constructions and curl_global_cleanup on destruction.

These calls might interfere, if you repeatedly create and destroy SwiftyCurl objects.

Do it like this:

extension SwiftyCurl {

    static let shared = {
        let curl = SwiftyCurl()
        // Put your standard configuration here.

        return curl
    }()
}
}

Why

The main reason why this exists, is, that URLSession is somewhat limited for specific applications. Especially when it comes to sending any headers you wish, URLSession often is in the way. The notes about this in URLRequest explicitly don't apply here: E.g. you can send your own Host header with SwiftyCurl and it won't get changed by it!

Known Issues

  • When using SwiftyCurl.followLocation, the returned HTTPURLResponse should return the last location curl ended up with. However, CURLINFO_EFFECTIVE_URL doesn't seem to work as expected in experiments.

  • No support for input/output streams, yet. You'll have to move your huge files into RAM first.

  • Other protocols than HTTP aren't fully supported by SwiftyCurl, esp. when it comes to response processing.

  • curl_easy handles are thrown away after one use. These could instead get pooled and reused to improve efficiency.

  • Lots of libcurl features aren't properly exposed.

If any of these bug you, I am awaiting your merge requests!

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

SwiftyCurl relies on curl-apple, which is libcurl built for iOS and macOS as an easily ingestible xcframework.

It should be downloaded automaticaly on pod installation, but if this doesn't work, please just run download-curl.sh yourself and rerun pod install!

Installation

CocoaPods

SwiftyCurl is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'SwiftyCurl'

Swift Package Manager

    
    dependencies: [
        .package(url: "https://github.com/greatfire/SwiftyCurl", from: "0.4.1"),
    ],
    

Author

Benjamin Erhart, berhart@netzarchitekten.com

License

SwiftyCurl is available under the MIT license. See the LICENSE file for more info.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published