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.
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)")")
}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
}()
}
}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!
-
When using
SwiftyCurl.followLocation, the returnedHTTPURLResponseshould return the last location curl ended up with. However,CURLINFO_EFFECTIVE_URLdoesn'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_easyhandles are thrown away after one use. These could instead get pooled and reused to improve efficiency. -
Lots of libcurl features aren't properly exposed.
-
When used with SPM, SwiftyCurl cannot find the
cacert.pemfile automatically, due to limitations of SPM. You will need to extract that from the binary dependencycurl.xcframeworkor download it here, add it to your app bundle manually and setSwiftyCurl.tlsCertsCombinedPemto that file.
If any of these bug you, I am awaiting your merge requests!
To run the example project, clone the repo, and run pod install from the Example directory first.
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!
SwiftyCurl is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'SwiftyCurl' …
dependencies: [
.package(url: "https://github.com/greatfire/SwiftyCurl", from: "0.4.5"),
],
…Benjamin Erhart, berhart@netzarchitekten.com
SwiftyCurl is available under the MIT license. See the LICENSE file for more info.