diff options
author | Marc Stibane <marc@taler.net> | 2024-02-29 14:31:16 +0100 |
---|---|---|
committer | Marc Stibane <marc@taler.net> | 2024-02-29 14:40:26 +0100 |
commit | f39b4cae710ff997ac1415343e6c0fb765e8d4a9 (patch) | |
tree | eccb7bd139463a687b52a06c36fc97769d073b8b | |
parent | d6e2f8425dd0f904c5af1302e862fcfa9703f676 (diff) | |
download | taler-ios-f39b4cae710ff997ac1415343e6c0fb765e8d4a9.tar.gz taler-ios-f39b4cae710ff997ac1415343e6c0fb765e8d4a9.tar.bz2 taler-ios-f39b4cae710ff997ac1415343e6c0fb765e8d4a9.zip |
Native Networking via URLSession.dataTask
-rw-r--r-- | TalerWallet.xcodeproj/project.pbxproj | 6 | ||||
-rw-r--r-- | TalerWallet1/Quickjs/QuickDataTask.swift | 156 | ||||
-rw-r--r-- | TalerWallet1/Quickjs/quickjs.swift | 74 |
3 files changed, 211 insertions, 25 deletions
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj index e5ce46c..410f541 100644 --- a/TalerWallet.xcodeproj/project.pbxproj +++ b/TalerWallet.xcodeproj/project.pbxproj @@ -244,6 +244,8 @@ 4ED80E892B8F5FB8008BD576 /* CStringArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E872B8F5FB8008BD576 /* CStringArray.swift */; }; 4ED80E8B2B8F60E7008BD576 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */; }; 4ED80E8C2B8F60E7008BD576 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */; }; + 4ED80E8E2B8F6212008BD576 /* QuickDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */; }; + 4ED80E8F2B8F6212008BD576 /* QuickDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */; }; 4EDBDCD92AB787CB00925C02 /* CallStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD82AB787CB00925C02 /* CallStack.swift */; }; 4EDBDCDA2AB787CB00925C02 /* CallStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDBDCD82AB787CB00925C02 /* CallStack.swift */; }; 4EE171882B49635800BF9FF5 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4EE171872B49635800BF9FF5 /* MarkdownUI */; }; @@ -431,6 +433,7 @@ 4ED2F94A2A278F5100453B40 /* ThreeAmountsV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeAmountsV.swift; sourceTree = "<group>"; }; 4ED80E872B8F5FB8008BD576 /* CStringArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CStringArray.swift; sourceTree = "<group>"; }; 4ED80E8A2B8F60E7008BD576 /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; }; + 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickDataTask.swift; sourceTree = "<group>"; }; 4EDBDCD82AB787CB00925C02 /* CallStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallStack.swift; sourceTree = "<group>"; }; 4EEC118C2B83DE4700146CFF /* AmountInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountInputV.swift; sourceTree = "<group>"; }; 4EEC11922B83FB7A00146CFF /* SubjectInputV.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectInputV.swift; sourceTree = "<group>"; }; @@ -634,6 +637,7 @@ isa = PBXGroup; children = ( 4EB0950D2989CB9A0043A8A1 /* quickjs.swift */, + 4ED80E8D2B8F6212008BD576 /* QuickDataTask.swift */, ); path = Quickjs; sourceTree = "<group>"; @@ -1099,6 +1103,7 @@ 4E3EAE2B2A990778009F1BE8 /* LoadingView.swift in Sources */, 4E3EAE8C2AA0933C009F1BE8 /* Font+Taler.swift in Sources */, 4E3327BA2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */, + 4ED80E8E2B8F6212008BD576 /* QuickDataTask.swift in Sources */, 4E3EAE2C2A990778009F1BE8 /* ManualWithdraw.swift in Sources */, 4E3EAE2D2A990778009F1BE8 /* Model+Exchange.swift in Sources */, 4EBC0F012B7B3CD600C0CB19 /* DepositIbanV.swift in Sources */, @@ -1214,6 +1219,7 @@ 4EB0956D2989CBFE0043A8A1 /* LoadingView.swift in Sources */, 4E3EAE8D2AA0933C009F1BE8 /* Font+Taler.swift in Sources */, 4E3327BB2AD1635100BF5AD6 /* AsyncSemaphore.swift in Sources */, + 4ED80E8F2B8F6212008BD576 /* QuickDataTask.swift in Sources */, 4E50B3502A1BEE8000F9F01C /* ManualWithdraw.swift in Sources */, 4E3B4BC92A42BC4800CC88B8 /* Model+Exchange.swift in Sources */, 4EBC0F022B7B3CD600C0CB19 /* DepositIbanV.swift in Sources */, diff --git a/TalerWallet1/Quickjs/QuickDataTask.swift b/TalerWallet1/Quickjs/QuickDataTask.swift new file mode 100644 index 0000000..e4afe88 --- /dev/null +++ b/TalerWallet1/Quickjs/QuickDataTask.swift @@ -0,0 +1,156 @@ +/* + * This file is part of GNU Taler, ©2022-24 Taler Systems S.A. + * See LICENSE.md + */ +/** + * @author Marc Stibane + */ +import Foundation +//import "Foundation/NSURLError.h" +import os.log + +import FTalerWalletcore + +// will be called from wallet-core for networking +func request_create(userdata: Optional<UnsafeMutableRawPointer>, + requestInfo: Optional<UnsafeMutablePointer<JSHttpRequestInfo>>) -> Int32 { + let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() + + if let requestInfo { + if let url = URL(string: String(cString: requestInfo.pointee.url)) { + let responseCb = requestInfo.pointee.response_cb + let responseCbCls = requestInfo.pointee.response_cb_cls + let method = String(cString: requestInfo.pointee.method) + let requestHeaders = requestInfo.pointee.request_headers + let redirect = requestInfo.pointee.redirect // TODO: redirect + let timeoutMs = requestInfo.pointee.timeout_ms + let debug = requestInfo.pointee.debug // TODO: debug + let reqBody = requestInfo.pointee.req_body + let bodyLen = requestInfo.pointee.req_body_len + + var request = URLRequest(url: url) + request.httpMethod = method + request.timeoutInterval = TimeInterval(timeoutMs) + if let reqBody { // caller will deallocate the req_body after dataTask finish or cancel + let body = Data(bytesNoCopy: reqBody, count: Int(bodyLen), deallocator: .none) + request.httpBody = body + } + if var ptr = requestHeaders { + while let cString = ptr.pointee { + let string = String(cString: cString) + if let index = string.firstIndex(of: ":") { + let headerField = string.prefix(upTo: index) + let nextIndex = string.index(index, offsetBy: 1) // skip the ":" + let value = string.suffix(from: nextIndex) + request.addValue(String(value), forHTTPHeaderField: String(headerField)) + } + ptr += 1 + } + } + + return quickjs.reqCreate(request, responseCb, responseCbCls) + } + } + return 0 +} + +func request_cancel(userdata: Optional<UnsafeMutableRawPointer>, + requestID: Int32) -> Int32 { + let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() + return quickjs.reqCancel(requestID) +} +// MARK: - +class QuickDataTask: NSObject { + let urlSession: URLSession + let request: URLRequest + let requestID: Int32 + var requests: [Int32: QuickDataTask] + let responseCb: JSHttpResponseCb? + let responseCbCls: Optional<UnsafeMutableRawPointer> + + var dataTask: URLSessionDataTask? = nil + var logger: Logger + + init(urlSession: URLSession, + request: URLRequest, + requestID: Int32, + requests: inout [Int32: QuickDataTask], + responseCb: JSHttpResponseCb?, + responseCbCls: Optional<UnsafeMutableRawPointer> + ) { + self.logger = Logger(subsystem: "net.taler.gnu", category: "Networking") + self.urlSession = urlSession + self.request = request + self.requestID = requestID + self.requests = requests + self.responseCb = responseCb + self.responseCbCls = responseCbCls + } + func run() { + if let responseCb, let responseCbCls { + let method = self.request.httpMethod ?? "Unknown" + let url = self.request.url?.absoluteString ?? EMPTYSTRING + logger.trace("❓\(self.requestID, privacy: .public) \(method, privacy: .public) \(url, privacy: .public)") + dataTask = urlSession.dataTask(with: request) { [self] data, response, error in + let err: Error + if let response = response as? HTTPURLResponse { + var headerArray: [String] = [] + var numHeaders: Int32 = 0 + var status = Int32(response.statusCode) + var errmsg = HTTPURLResponse.localizedString(forStatusCode: Int(status)) + var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8)) + // Initialization of 'UnsafeMutablePointer<CChar>' (aka 'UnsafeMutablePointer<Int8>') results in a dangling pointer + let headers = response.allHeaderFields + for (key,value) in headers { + headerArray.append("\(key): \(value)") + numHeaders += 1 + } + let cHeaders = CStringArray(headerArray) + + if let data { + var ndata:NSData = data as NSData + var bodyPtr = UnsafeMutableRawPointer(mutating: ndata.bytes) + var responseInfo = JSHttpResponseInfo(request_id: self.requestID, + status: status, + errmsg: errmsg_p0, + response_headers: cHeaders.pointer, + num_response_headers: numHeaders, + body: bodyPtr, + body_len: data.count) + let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) + // Initialization of 'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer + logger.trace("❗️ \(self.requestID, privacy: .public) \(url, privacy: .public)") + responseCb(responseCbCls, responseInfoPtr) + return + } else { // data == nil + logger.error("‼️\(self.requestID, privacy: .public) \(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, privacy: .public)") + var responseInfo = JSHttpResponseInfo(request_id: self.requestID, + status: status, + errmsg: errmsg_p0, + response_headers: cHeaders.pointer, + num_response_headers: numHeaders, + body: nil, + body_len: 0) + let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) + responseCb(responseCbCls, responseInfoPtr) + } + } else { + var errmsg = "No http response from \(url)" + logger.error("⁉️\(self.requestID, privacy: .public) \(method, privacy: .public) \(errmsg, privacy: .public)") + var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8)) + var responseInfo = JSHttpResponseInfo(request_id: self.requestID, + status: 0, + errmsg: errmsg_p0, + response_headers: nil, + num_response_headers: 0, + body: nil, + body_len: 0) + let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) + responseCb(responseCbCls, responseInfoPtr) + } + requests[requestID] = nil + } + dataTask?.resume() + } + } +} diff --git a/TalerWallet1/Quickjs/quickjs.swift b/TalerWallet1/Quickjs/quickjs.swift index 06878af..dbe796d 100644 --- a/TalerWallet1/Quickjs/quickjs.swift +++ b/TalerWallet1/Quickjs/quickjs.swift @@ -16,17 +16,17 @@ public protocol QuickjsMessageHandler: AnyObject { // MARK: - func notification_callback(userdata: Optional<UnsafeMutableRawPointer>, payload: Optional<UnsafePointer<Int8>>) { - let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() + let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() let string = String(cString: payload!) - native.internalOnNotify(payload: string) + quickjs.internalOnNotify(payload: string) } func logging_callback(userdata: Optional<UnsafeMutableRawPointer>, level: TALER_WALLET_LogLevel, tag: Optional<UnsafePointer<Int8>>, message: Optional<UnsafePointer<Int8>>) { - let native = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() - let logger = native.logger + let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() + let logger = quickjs.logger let swiftTag = String(cString: tag!) let swiftMessage = String(cString: message!) @@ -43,14 +43,21 @@ func logging_callback(userdata: Optional<UnsafeMutableRawPointer>, logger.trace("\(swiftTag, privacy: .public) \(swiftMessage, privacy: .public)") default: break } -// native.internalOnLog(message: swiftMessage) } // MARK: - -public class Quickjs { +public class Quickjs { // acts as singleton, since only one instance ever exists var talerWalletInstance: OpaquePointer! public weak var messageHandler: QuickjsMessageHandler? var logger: Logger + @Atomic(default: 0) + private var lastRequestID: Int32 + private var requests: [Int32: QuickDataTask] = [:] + + private lazy var urlSession: URLSession = { + return URLSession(configuration: .default) + }() + public init() { self.logger = Logger(subsystem: "net.taler.gnu", category: "QuickJS") self.talerWalletInstance = TALER_WALLET_create() @@ -60,18 +67,48 @@ public class Quickjs { TALER_WALLET_set_log_handler(talerWalletInstance, logging_callback, Unmanaged.passUnretained(self).toOpaque()) - let http_curl = js_curl_http_client_create() - TALER_set_http_client_implementation(talerWalletInstance, http_curl) +#if USE_HTTP_CLIENT_CURL + let http_impl = js_curl_http_client_create() +#else + let http_impl = TALER_pack_http_client_implementation(request_create, request_cancel, + Unmanaged.passUnretained(self).toOpaque()) +#endif + // http_impl got malloc'd, and could possibly be free'd when the app terminates + TALER_set_http_client_implementation(talerWalletInstance, http_impl) + // - but we never free anything on termination, thus we don't save http_impl here TALER_WALLET_run(talerWalletInstance) } - deinit { - // FIXME: TALER_WALLET_destroy -// TALER_WALLET_destroy(talerWalletInstance) + func reqCreate(_ request: URLRequest, + _ responseCb: JSHttpResponseCb?, + _ responseCbCls: Optional<UnsafeMutableRawPointer>) -> Int32 { + let requestID = $lastRequestID.atomicIncrement() + let quickDataTask = QuickDataTask(urlSession: urlSession, + request: request, + requestID: requestID, + requests: &requests, + responseCb: responseCb, + responseCbCls: responseCbCls) + quickDataTask.run() + requests[requestID] = quickDataTask + return requestID } + func reqCancel(_ requestID: Int32) -> Int32 { + if let quickDataTask = requests[requestID] { + if let dataTask = quickDataTask.dataTask { + dataTask.cancel() + } + } + requests[requestID] = nil + return 0 + } - public func internalOnNotify(payload: String) { + deinit { + // No need to call TALER_WALLET_destroy - memory gets purged anyway + } + + func internalOnNotify(payload: String) { if let handler = messageHandler { handler.handleMessage(message: payload) } @@ -90,17 +127,4 @@ public class Quickjs { public func sendMessage(message: String) { TALER_WALLET_send_request(talerWalletInstance, message) } - - /// Note: This *must* be called before releasing the object, or else the thread will keep going. -// public func waitStopped() { -// scheduleNodeThreadSync { -// self.stopped = true -// } -// thread.cancel() -// } - -// public func putModuleCode(modName: String, code: String) { -// __putModuleCodeNative(self.instance, modName.cString(using: .utf8), -// code.cString(using: .utf8)) -// } } |