QuickDataTask.swift (8517B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import Foundation 9 //import "Foundation/NSURLError.h" 10 import os.log 11 12 import FTalerWalletcore 13 14 // will be called from wallet-core for networking 15 func request_create(userdata: Optional<UnsafeMutableRawPointer>, 16 requestInfo: Optional<UnsafeMutablePointer<JSHttpRequestInfo>>) -> Int32 { 17 let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() 18 19 if let requestInfo { 20 if let url = URL(string: String(cString: requestInfo.pointee.url)) { 21 let responseCb = requestInfo.pointee.response_cb 22 let responseCbCls = requestInfo.pointee.response_cb_cls 23 let method = String(cString: requestInfo.pointee.method) 24 let requestHeaders = requestInfo.pointee.request_headers 25 let redirect = requestInfo.pointee.redirect // TODO: redirect 26 let timeoutMs = requestInfo.pointee.timeout_ms 27 let debug = requestInfo.pointee.debug // TODO: debug 28 let reqBody = requestInfo.pointee.req_body 29 let bodyLen = requestInfo.pointee.req_body_len 30 31 var request = URLRequest(url: url) 32 request.httpMethod = method 33 request.timeoutInterval = TimeInterval(timeoutMs) 34 if let reqBody { // caller will deallocate the req_body after dataTask finish or cancel 35 let body = Data(bytesNoCopy: reqBody, count: Int(bodyLen), deallocator: .none) 36 request.httpBody = body 37 } 38 if var ptr = requestHeaders { 39 while let cString = ptr.pointee { 40 let string = String(cString: cString) 41 if let index = string.firstIndex(of: ":") { 42 let headerField = string.prefix(upTo: index) 43 let nextIndex = string.index(index, offsetBy: 1) // skip the ":" 44 let value = string.suffix(from: nextIndex) 45 request.addValue(String(value), forHTTPHeaderField: String(headerField)) 46 } 47 ptr += 1 48 } 49 } 50 51 return quickjs.reqCreate(request, responseCb, responseCbCls) 52 } 53 } 54 return 0 55 } 56 57 func request_cancel(userdata: Optional<UnsafeMutableRawPointer>, 58 requestID: Int32) -> Int32 { 59 let quickjs = Unmanaged<Quickjs>.fromOpaque(userdata!).takeUnretainedValue() 60 return quickjs.reqCancel(requestID) 61 } 62 // MARK: - 63 extension Error { 64 var errorCode:Int? { 65 return (self as NSError).code 66 } 67 } 68 // MARK: - 69 class QuickDataTask: NSObject { 70 let urlSession: URLSession 71 let request: URLRequest 72 let requestID: Int32 73 var requests: [Int32: QuickDataTask] 74 let responseCb: JSHttpResponseCb? 75 let responseCbCls: Optional<UnsafeMutableRawPointer> 76 77 var dataTask: URLSessionDataTask? = nil 78 var logger: Logger 79 80 init(urlSession: URLSession, 81 request: URLRequest, 82 requestID: Int32, 83 requests: inout [Int32: QuickDataTask], 84 responseCb: JSHttpResponseCb?, 85 responseCbCls: Optional<UnsafeMutableRawPointer> 86 ) { 87 self.logger = Logger(subsystem: "net.taler.gnu", category: "Networking") 88 self.urlSession = urlSession 89 self.request = request 90 self.requestID = requestID 91 self.requests = requests 92 self.responseCb = responseCb 93 self.responseCbCls = responseCbCls 94 } 95 96 func run() { 97 if let responseCb, let responseCbCls { 98 let method = self.request.httpMethod ?? "Unknown" 99 let url = self.request.url?.absoluteString ?? EMPTYSTRING 100 logger.trace("❓\(self.requestID, privacy: .public) \(method, privacy: .public) \(url, privacy: .public)") 101 dataTask = urlSession.dataTask(with: request) { [self] data, response, error in 102 let err: Error 103 if let response = response as? HTTPURLResponse { 104 var headerArray: [String] = [] 105 var numHeaders: Int32 = 0 106 var status = Int32(response.statusCode) 107 var errmsg = HTTPURLResponse.localizedString(forStatusCode: Int(status)) 108 var errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8)) 109 // Initialization of 'UnsafeMutablePointer<CChar>' (aka 'UnsafeMutablePointer<Int8>') results in a dangling pointer 110 let headers = response.allHeaderFields 111 for (key,value) in headers { 112 headerArray.append("\(key): \(value)") 113 numHeaders += 1 114 } 115 let cHeaders = CStringArray(headerArray) 116 117 if let data { 118 var ndata:NSData = data as NSData 119 var bodyPtr = UnsafeMutableRawPointer(mutating: ndata.bytes) 120 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 121 status: status, 122 errmsg: errmsg_p0, 123 response_headers: cHeaders.pointer, 124 num_response_headers: numHeaders, 125 body: bodyPtr, 126 body_len: UInt32(data.count)) 127 let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) 128 // Initialization of 'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer 129 logger.trace("❗️ \(self.requestID, privacy: .public) \(url, privacy: .public)") 130 responseCb(responseCbCls, responseInfoPtr) 131 return 132 } else { // data == nil 133 logger.error("‼️\(self.requestID, privacy: .public) \(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, privacy: .public)") 134 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 135 status: status, 136 errmsg: errmsg_p0, 137 response_headers: cHeaders.pointer, 138 num_response_headers: numHeaders, 139 body: nil, 140 body_len: 0) 141 let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) 142 responseCb(responseCbCls, responseInfoPtr) 143 } 144 } else { // pass error to walletCore 145 Task.detached { 146 self.logger.error("⁉️ \(self.requestID, privacy: .public) \(method, privacy: .public) \(error, privacy: .public)") 147 Controller.shared.checkInternetConnection() 148 let errmsg = error?.localizedDescription 149 let errmsg_p0 = if let errmsg { UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8)) } 150 else { UnsafeMutablePointer<CChar>(nil) } 151 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 152 status: 0, 153 errmsg: errmsg_p0, 154 response_headers: nil, 155 num_response_headers: 0, 156 body: nil, 157 body_len: 0) 158 let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) 159 responseCb(responseCbCls, responseInfoPtr) 160 } 161 } 162 requests[requestID] = nil 163 } 164 dataTask?.resume() 165 } 166 } 167 }