QuickDataTask.swift (9461B)
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 if let response = response as? HTTPURLResponse { 103 var headerArray: [String] = [] 104 var numHeaders: Int32 = 0 105 let status = Int32(response.statusCode) 106 let errmsg = HTTPURLResponse.localizedString(forStatusCode: Int(status)) 107 let errmsg_p0 = UnsafeMutablePointer<CChar>(mutating: errmsg.cString(using: .utf8)) 108 // Initialization of 'UnsafeMutablePointer<CChar>' (aka 'UnsafeMutablePointer<Int8>') results in a dangling pointer 109 let headers = response.allHeaderFields 110 for (key,value) in headers { 111 headerArray.append("\(key): \(value)") 112 numHeaders += 1 113 } 114 let cHeaders = CStringArray(headerArray) 115 116 if let data { 117 let ndata:NSData = data as NSData 118 let bodyPtr = UnsafeMutableRawPointer(mutating: ndata.bytes) 119 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 120 status: status, 121 errmsg: errmsg_p0, 122 response_headers: cHeaders.pointer, 123 num_response_headers: numHeaders, 124 body: bodyPtr, 125 body_len: UInt32(data.count)) 126 let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) 127 // Initialization of 'UnsafeMutablePointer<JSHttpResponseInfo>' results in a dangling pointer 128 logger.trace("❗️ \(self.requestID, privacy: .public) \(url, privacy: .public)") 129 responseCb(responseCbCls, responseInfoPtr) 130 return 131 } else { // data == nil 132 logger.error("‼️\(self.requestID, privacy: .public) \(method, privacy: .public) \(response.statusCode, privacy: .public) \(errmsg, privacy: .public)") 133 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 134 status: status, 135 errmsg: errmsg_p0, 136 response_headers: cHeaders.pointer, 137 num_response_headers: numHeaders, 138 body: nil, 139 body_len: 0) 140 let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) 141 responseCb(responseCbCls, responseInfoPtr) 142 } 143 } else { // pass error to walletCore 144 Task.detached { 145 self.logger.error("⁉️ \(self.requestID, privacy: .public) \(method, privacy: .public) \(error, privacy: .public)") 146 Controller.shared.checkInternetConnection() 147 if let errmsg = error?.localizedDescription, 148 let errmsgC = errmsg.cString(using: .utf8) 149 { 150 let errmsg_p1 = UnsafeMutablePointer<CChar>(mutating: errmsgC) 151 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 152 status: 0, 153 errmsg: errmsg_p1, 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 } else { 161 // should never happen 162 let errmsg_p1 = UnsafeMutablePointer<CChar>(nil) 163 var responseInfo = JSHttpResponseInfo(request_id: self.requestID, 164 status: 0, 165 errmsg: errmsg_p1, 166 response_headers: nil, 167 num_response_headers: 0, 168 body: nil, 169 body_len: 0) 170 let responseInfoPtr = UnsafeMutablePointer<JSHttpResponseInfo>(&responseInfo) 171 responseCb(responseCbCls, responseInfoPtr) 172 } 173 } 174 } 175 requests[requestID] = nil 176 } 177 dataTask?.resume() 178 } 179 } 180 }