NetworkInterface.kt (4461B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2024 Taler Systems S.A. 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 package net.taler.wallet.backend 18 19 import android.util.Log 20 import io.ktor.client.call.body 21 import io.ktor.client.plugins.ResponseException 22 import io.ktor.client.request.header 23 import io.ktor.client.request.headers 24 import io.ktor.client.request.request 25 import io.ktor.client.request.setBody 26 import io.ktor.client.request.url 27 import io.ktor.util.flattenForEach 28 import kotlinx.coroutines.DelicateCoroutinesApi 29 import kotlinx.coroutines.GlobalScope 30 import kotlinx.coroutines.Job 31 import kotlinx.coroutines.launch 32 import kotlinx.serialization.SerializationException 33 import net.taler.common.getDefaultHttpClient 34 import net.taler.common.toHttpMethod 35 import net.taler.qtart.Networking 36 import net.taler.wallet.main.TAG 37 import java.io.IOException 38 import java.util.concurrent.ConcurrentHashMap 39 40 @OptIn(DelicateCoroutinesApi::class) 41 class NetworkInterface: Networking.RequestHandler { 42 private val requests: ConcurrentHashMap<Int, Job> = ConcurrentHashMap() 43 44 override fun handleRequest( 45 req: Networking.RequestInfo, 46 id: Int, 47 sendResponse: (resp: Networking.ResponseInfo) -> Unit 48 ) { 49 Log.d(TAG, "HTTP: handleRequest($req, $id") 50 51 requests[id] = GlobalScope.launch { 52 val client = getDefaultHttpClient( 53 timeoutMs = req.timeoutMs, 54 followRedirect = req.redirectMode == Networking.RedirectMode.Transparent, 55 logging = req.debug, 56 ) 57 58 var errorMsg: String? = null 59 60 val resp = try { 61 // TODO: reuse the same client for every request 62 client.request { 63 url(req.url) 64 65 method = req.method.toHttpMethod() 66 ?: error("invalid method") 67 68 headers { 69 parseHeaders(req.headers).map { 70 header(it.key, it.value) 71 } 72 } 73 74 if (req.body != null) { 75 setBody(req.body) 76 } 77 } 78 } catch (e: ResponseException) { 79 e.response // send non-200 responses to wallet-core anyway 80 } catch (e: IOException) { 81 Log.d(TAG, "Exception handling HTTP response", e) 82 errorMsg = e.message 83 null 84 } catch (e: SerializationException) { 85 Log.d(TAG, "Exception handling HTTP response", e) 86 errorMsg = e.message 87 null 88 } finally { 89 cleanupRequest(id) 90 client.close() 91 } 92 93 // HTTP response status code or 0 on error. 94 val status = if (resp?.status?.value != null) resp.status.value else 0 95 96 val headers = mutableListOf<String>().apply { 97 resp?.headers?.flattenForEach { k, v -> add("$k: $v") } 98 }.toTypedArray() 99 100 Log.d(TAG, "Sending response to wallet-core") 101 sendResponse( 102 Networking.ResponseInfo( 103 requestId = id, 104 status = status, 105 errorMsg = errorMsg, 106 headers = headers, 107 body = resp?.body(), 108 ) 109 ) 110 } 111 } 112 113 private fun parseHeaders(headers: Array<String>) = headers.associate { 114 val parts = it.split(':', limit = 2) 115 parts[0] to parts[1] 116 } 117 118 override fun cancelRequest(id: Int): Boolean { 119 Log.d(TAG, "HTTP: cancelRequest($id") 120 requests[id]?.let { job -> 121 job.cancel() 122 requests.remove(id) 123 } 124 125 return true 126 } 127 128 private fun cleanupRequest(id: Int) { 129 requests.remove(id) 130 } 131 }