taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

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 }