2 Комити df5df932c7 ... ead0f90b5d

Аутор SHA1 Порука Датум
  herlanS_ ead0f90b5d prepare for cluster twister пре 3 година
  herlanS_ a4fe5d3516 add resource not found for unregistered client пре 3 година

+ 31 - 98
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/BridgeFIlter.kt

@@ -3,7 +3,6 @@ package co.id.datacomsolusindo.ipphonebridge
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.databind.ObjectWriter
 import org.apache.logging.log4j.LogManager
-import org.springframework.context.annotation.Configuration
 import org.springframework.core.Ordered
 import org.springframework.core.annotation.Order
 import org.springframework.core.io.ResourceLoader
@@ -12,17 +11,10 @@ import org.springframework.http.HttpMethod
 import org.springframework.http.HttpStatus
 import org.springframework.messaging.simp.SimpMessagingTemplate
 import org.springframework.stereotype.Component
-import org.springframework.web.server.ResponseStatusException
-import org.springframework.web.servlet.config.annotation.EnableWebMvc
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
-import org.springframework.web.servlet.resource.PathResourceResolver
 import java.io.File
 import java.io.IOException
 import java.io.Serializable
 import java.net.InetAddress
-import java.time.Duration
-import java.time.LocalDateTime
 import java.util.*
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.ConcurrentMap
@@ -30,7 +22,6 @@ import java.util.concurrent.TimeUnit
 import javax.servlet.*
 import javax.servlet.http.HttpServletRequest
 import javax.servlet.http.HttpServletResponse
-import kotlin.concurrent.fixedRateTimer
 import kotlin.streams.toList
 
 
@@ -81,17 +72,9 @@ class BridgeFilter(private val template: SimpMessagingTemplate, val resourceLoad
             if (search != null && !search.endsWith("assets/bootstrap.js")) {
                 res.sendRedirect("/resources/$search")
             } else {
-                if(!ClientHolder.get().containsKey(clientNumber.toString())){
-                    res.status = HttpStatus.NOT_FOUND.value()
-                    res.writer.write("{\"message\":\"resource not found\"}")
-                    res.flushBuffer()
-                    return
-
-                }
                 LogManager.getLogger("client-$client")
                     .info("Request Start, UA: " + req.getHeader("User-Agent") + " From: " + InetAddress.getLocalHost().hostAddress + " To: " + req.requestURL.toString() + "?" + req.queryString)
 
-//                if (req.getHeader("User-Agent").contains("(dart:io)") || req.requestURL.toString().endsWith("api/license")) {
                 val reqId = UUID.randomUUID().toString()
                 try {
                     val headerMap = req.headerNames.toList().associateBy({ it }, { req.getHeader(it) })
@@ -114,38 +97,46 @@ class BridgeFilter(private val template: SimpMessagingTemplate, val resourceLoad
                         val body = req.reader.lines().toList().joinToString(System.lineSeparator())
                         rb.body = body
                     }
-
-                    Singletons.requestInstance.getOrPut(reqId){ RequestQue(reqId, rb, null)}
+                    Singletons.requestInstance.getOrPut(reqId) { RequestQue(reqId, rb, null) }
                     val ow: ObjectWriter = ObjectMapper().writer().withDefaultPrettyPrinter()
-                    val partStr = ow.writeValueAsString(rb).chunked(1500)
+                    val chunkSize = 2000
+                    val partStr = ow.writeValueAsString(rb).chunked(chunkSize)
                     val partialID = UUID.randomUUID().toString()
                     val st2 = System.nanoTime()
+                    if (!ClientHolder.get().containsKey(clientNumber.toString())) {
+//                        subscribeGW /topic/request/reqID
+//                        sendGW "/app/partial/$client"
+                        chain.doFilter(request, response)
+                        return
+                    } else {
+                        partStr.forEachIndexed { idx, it ->
+                            template.convertAndSend(
+                                "/topic/partial/$client",
+                                PartialData(partialID, partStr.size, idx + 1, it, st2)
+                            )
+                        }
 
-//                    println("req id $reqId")
-                    partStr.forEachIndexed { idx, it ->
-                        template.convertAndSend(
-                            "/topic/partial/$client",
-                            PartialData(partialID, partStr.size, idx + 1, it, st2)
-                        )
-                    }
 
+                    }
 
                     var i = 0
 
                     while (Singletons.responseQue.getOrElse(reqId) { null } == null) {
-                        TimeUnit.MILLISECONDS.sleep(100)
+                        TimeUnit.MILLISECONDS.sleep(50)
                         i++
                         if (i >= 600 * partStr.size) {
                             res.status = HttpStatus.REQUEST_TIMEOUT.value()
                             res.writer.write("{\"message\":\"Request timeout. Client not responding\"}")
                             res.flushBuffer()
                             Singletons.requestInstance.remove(reqId)
+                            Singletons.buildSocketChunkData.remove(reqId)
+                            Singletons.responseQue.remove(reqId)
                             val endTime = System.nanoTime()
                             val duration =
                                 (endTime - startTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
                             LogManager.getLogger("client-$client")
                                 .info("Request failed, No Response. Time: $duration  UA: " + req.getHeader("User-Agent") + " From: " + InetAddress.getLocalHost().hostAddress + " To: " + req.requestURL.toString() + "?" + req.queryString)
-
+//                        unsubscribeGW /topic/request/reqID
                             ClientHolder.addFailedRequest(client)
                             return
                         }
@@ -169,9 +160,13 @@ class BridgeFilter(private val template: SimpMessagingTemplate, val resourceLoad
                     res.flushBuffer()
 
                     Singletons.requestInstance.remove(reqId)
+                    Singletons.buildSocketChunkData.remove(reqId)
+                    Singletons.responseQue.remove(reqId)
+                    //                        unsubscribeGW /topic/request/reqID
                     val endTime = System.nanoTime()
 
-                    val duration = (endTime - startTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
+                    val duration =
+                        (endTime - startTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
                     LogManager.getLogger("client-$client")
                         .info("Request Success. Time: $duration  UA: " + req.getHeader("User-Agent") + " From: " + req.remoteAddr + " To: " + req.requestURL.toString() + "?" + req.queryString)
 
@@ -185,6 +180,8 @@ class BridgeFilter(private val template: SimpMessagingTemplate, val resourceLoad
                     res.writer.write("{\"message\":\"Request timeout. Unknown error\"}")
                     res.flushBuffer()
                     Singletons.requestInstance.remove(reqId)
+                    Singletons.buildSocketChunkData.remove(reqId)
+                    Singletons.responseQue.remove(reqId)
                     val endTime = System.nanoTime()
                     val duration = (endTime - startTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
                     LogManager.getLogger("client-$client")
@@ -197,17 +194,6 @@ class BridgeFilter(private val template: SimpMessagingTemplate, val resourceLoad
             chain.doFilter(request, response)
             return
         }
-//        if (
-//            client.startsWith("_")
-//            || client.startsWith("resources")
-//            || client.startsWith("clientStat")
-//            || client.startsWith("actuator")
-//        ) {
-//            chain.doFilter(request, response)
-//            return
-//        } else {
-//
-//        }
     }
 
     fun isResourceAvailable(path: String, itr: Int = 1): String? {
@@ -224,9 +210,6 @@ class BridgeFilter(private val template: SimpMessagingTemplate, val resourceLoad
     }
 }
 
-class RequestQue(val id: String, val requestBuilder: RequestBuilder, var responseObj: Resp?)
-class PartialData(val id: String, val total: Int, val idx: Int, val data: String, val startTime: Long)
-
 class RequestBuilder(
     val id: String,
     val path: String,
@@ -238,6 +221,10 @@ class RequestBuilder(
     var parts: MutableList<FilePart>? = null
 }
 
+class RequestQue(val id: String, val requestBuilder: RequestBuilder, var responseObj: Resp?)
+
+class PartialData(val id: String, val total: Int, val idx: Int, val data: String, val startTime: Long)
+
 class FilePart(val name: String, val fileName: String, val data: String)
 
 class Resp(val body: ByteArray?, val statusCode: Int, val headers: Map<String, Array<String>>? = null)
@@ -249,59 +236,5 @@ object Singletons {
     val buildSocketChunkData: ConcurrentMap<String, ChunkCollector> by lazy { ConcurrentHashMap() }
 }
 
-class SocketChunkData(
-    val body: ByteArray?,
-    val header: HttpHeaders?,
-    val status: Int,
-    val part: Int,
-    val totalPart: Int
-)
-
-class ChunkCollector(private val id: String) {
-    private val mapSocketChunk: ConcurrentMap<Int, SocketChunkData> by lazy { ConcurrentHashMap() }
-
-    init {
-        val tStart = LocalDateTime.now()
-        fixedRateTimer("timer-$id", false, 20, 50) {
-            val listSocketChunk = mapSocketChunk.entries.sortedBy { it.key }.map { it.value }
-            if (listSocketChunk.size > 1 && listSocketChunk[0].totalPart == listSocketChunk.size) {
-                val sortedChunk = listSocketChunk.sortedBy { it.part }
-                val bodyList = sortedChunk.mapNotNull { it.body }
-                var bd = ByteArray(0)
-                bodyList.forEach { bd += it }
-                Singletons.responseQue.getOrPut(id) {
-                    Resp(
-                        bd,
-                        sortedChunk.last().status,
-                        listSocketChunk.firstOrNull { it.header != null }?.header?.entries?.associate {
-                            Pair(it.key, it.value.toTypedArray())
-                        }
-                    )
-                }
 
-                this.cancel()
-            }
-            if (Duration.between(tStart, LocalDateTime.now()).toMillis() > 10 * 60 * 1000) {
-                this.cancel()
-            }
-        }
-    }
-
-    fun add(dt: SocketChunkData) {
-        mapSocketChunk.getOrPut(dt.part) { dt }
-    }
-}
-
-@Configuration
-@EnableWebMvc
-class MvcConfig : WebMvcConfigurer {
-    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
-        registry
-            .addResourceHandler("resources/**")
-            .addResourceLocations("file:web/")
-            .setCachePeriod(3600 * 24 * 30)
-            .resourceChain(true)
-            .addResolver(PathResourceResolver())
-    }
-}
 

+ 33 - 33
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/BridgeRestController.kt

@@ -14,39 +14,39 @@ import javax.servlet.http.HttpServletResponse
 
 @RestController
 class BridgeRestController {
-    @PostMapping("/_response/{id}")
-    fun responseFromRest(@PathVariable("id") id: String, req: HttpServletRequest): String {
-        val objectMapper = ObjectMapper()
-        val typeRef: TypeReference<HashMap<String, Array<String>>> =
-            object : TypeReference<HashMap<String, Array<String>>>() {}
-        val body = IOUtils.toByteArray(req.getPart("body").inputStream)
-        Singletons.responseQue.getOrPut(id) {
-            Resp(
-                body,
-                String(IOUtils.toByteArray(req.getPart("status").inputStream)).toInt(),
-                if (req.getPart("header") != null) {
-                    objectMapper.readValue(String(IOUtils.toByteArray(req.getPart("header").inputStream)), typeRef)
-                } else {
-                    null
-                }
-            )
-        }
-
-        return "{\"success\":true}"
-    }
-
-    @GetMapping("/_request/{id}")
-    fun getResponseObj(@PathVariable("id") id: String): RequestBuilder {
-        if (Singletons.requestInstance.getOrElse(id) { null } == null) {
-            throw Exception("not found")
-        }
-        val rb = Singletons.requestInstance.getOrElse(id) { null }?.requestBuilder
-        if (rb == null) {
-            throw Exception("not found")
-        } else {
-            return rb
-        }
-    }
+//    @PostMapping("/_response/{id}")
+//    fun responseFromRest(@PathVariable("id") id: String, req: HttpServletRequest): String {
+//        val objectMapper = ObjectMapper()
+//        val typeRef: TypeReference<HashMap<String, Array<String>>> =
+//            object : TypeReference<HashMap<String, Array<String>>>() {}
+//        val body = IOUtils.toByteArray(req.getPart("body").inputStream)
+//        Singletons.responseQue.getOrPut(id) {
+//            Resp(
+//                body,
+//                String(IOUtils.toByteArray(req.getPart("status").inputStream)).toInt(),
+//                if (req.getPart("header") != null) {
+//                    objectMapper.readValue(String(IOUtils.toByteArray(req.getPart("header").inputStream)), typeRef)
+//                } else {
+//                    null
+//                }
+//            )
+//        }
+//
+//        return "{\"success\":true}"
+//    }
+//
+//    @GetMapping("/_request/{id}")
+//    fun getResponseObj(@PathVariable("id") id: String): RequestBuilder {
+//        if (Singletons.requestInstance.getOrElse(id) { null } == null) {
+//            throw Exception("not found")
+//        }
+//        val rb = Singletons.requestInstance.getOrElse(id) { null }?.requestBuilder
+//        if (rb == null) {
+//            throw Exception("not found")
+//        } else {
+//            return rb
+//        }
+//    }
 
     private val logSetting = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
             "<Configuration status=\"INFO\" name=\"MyApp\" packages=\"\">\n" +

+ 51 - 0
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/ChunkCollector.kt

@@ -0,0 +1,51 @@
+package co.id.datacomsolusindo.ipphonebridge
+
+import org.springframework.http.HttpHeaders
+import java.time.Duration
+import java.time.LocalDateTime
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentMap
+import kotlin.concurrent.fixedRateTimer
+
+class ChunkCollector(private val id: String) {
+    private val mapSocketChunk: ConcurrentMap<Int, SocketChunkData> by lazy { ConcurrentHashMap() }
+
+    init {
+        val tStart = LocalDateTime.now()
+        fixedRateTimer("timer-$id", false, 20, 50) {
+            val listSocketChunk = mapSocketChunk.entries.sortedBy { it.key }.map { it.value }
+            if (listSocketChunk.size > 1 && listSocketChunk[0].totalPart == listSocketChunk.size) {
+                val sortedChunk = listSocketChunk.sortedBy { it.part }
+                val bodyList = sortedChunk.mapNotNull { it.body }
+                var bd = ByteArray(0)
+                bodyList.forEach { bd += it }
+                Singletons.responseQue.getOrPut(id) {
+                    Resp(
+                        bd,
+                        sortedChunk.last().status,
+                        listSocketChunk.firstOrNull { it.header != null }?.header?.entries?.associate {
+                            Pair(it.key, it.value.toTypedArray())
+                        }
+                    )
+                }
+
+                this.cancel()
+            }
+            if (Duration.between(tStart, LocalDateTime.now()).toMillis() > 10 * 60 * 1000) {
+                this.cancel()
+            }
+        }
+    }
+
+    fun add(dt: SocketChunkData) {
+        mapSocketChunk.getOrPut(dt.part) { dt }
+    }
+}
+
+class SocketChunkData(
+    val body: ByteArray?,
+    val header: HttpHeaders?,
+    val status: Int,
+    val part: Int,
+    val totalPart: Int
+)

+ 75 - 0
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/ClientHolder.kt

@@ -0,0 +1,75 @@
+package co.id.datacomsolusindo.ipphonebridge
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentMap
+import kotlin.system.measureTimeMillis
+
+object ClientHolder {
+    private val mutexSucReq = Mutex()
+    private val clientMap: ConcurrentMap<String, Client> by lazy { ConcurrentHashMap() }
+    private suspend fun massiveRun(action: suspend () -> Unit) {
+        measureTimeMillis {
+            coroutineScope { // sc
+                launch {
+                    action()
+                }
+            }
+        }
+    }
+
+    fun removeBySessionId(sessionId: String?) {
+        clientMap.entries.filter { it.value.sessionId == sessionId }.forEach {
+            clientMap.remove(it.key)
+            //unsubscribe incoming data
+        }
+    }
+
+    fun put(key: String, client: Client) {
+        clientMap.getOrPut(key) { client }
+        //subscribe incoming data
+    }
+
+    fun get() = clientMap
+
+    fun addFailedRequest(clNum: String) {
+        runBlocking {
+            withContext(Dispatchers.Default) {
+                massiveRun {
+                    mutexSucReq.withLock {
+                        val cl = clientMap[clNum]
+                        if (cl != null) {
+                            cl.lastRequest =
+                                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
+                            cl.reqFailed += 1
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun addSuccessRequest(clNum: String, duration: Double) {
+        runBlocking {
+            withContext(Dispatchers.Default) {
+                massiveRun {
+                    // protect each increment with lock
+                    mutexSucReq.withLock {
+                        val cl = clientMap[clNum]
+                        if (cl != null) {
+                            cl.lastRequest =
+                                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
+                            cl.avgReqSuccessTime =
+                                ((cl.avgReqSuccessTime * cl.reqSuccess) + duration) / (cl.reqSuccess + 1)
+                            cl.reqSuccess += 1
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 296 - 0
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/GatewayClient.kt

@@ -0,0 +1,296 @@
+package co.id.datacomsolusindo.ipphonebridge
+
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import org.apache.commons.io.IOUtils
+import org.springframework.core.io.ByteArrayResource
+import org.springframework.core.io.Resource
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import org.springframework.http.HttpStatus
+import org.springframework.messaging.converter.MappingJackson2MessageConverter
+import org.springframework.messaging.simp.SimpMessagingTemplate
+import org.springframework.messaging.simp.stomp.StompCommand
+import org.springframework.messaging.simp.stomp.StompHeaders
+import org.springframework.messaging.simp.stomp.StompSession
+import org.springframework.messaging.simp.stomp.StompSessionHandler
+import org.springframework.util.LinkedMultiValueMap
+import org.springframework.util.MultiValueMap
+import org.springframework.web.client.HttpStatusCodeException
+import org.springframework.web.client.RestTemplate
+import org.springframework.web.socket.client.standard.StandardWebSocketClient
+import org.springframework.web.socket.messaging.WebSocketStompClient
+import java.io.Serializable
+import java.lang.reflect.Type
+import java.net.URI
+import java.nio.charset.Charset
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentMap
+
+class GatewayClient(
+    val socketHost: String,
+    val clientName: String,
+    val currentLocalPort: Int,
+    val socketPort: Int,
+    val srvTemplate: SimpMessagingTemplate
+) {
+    enum class State { INITIALIZED, CONNECTING, FAILED, CONNECT }
+
+    var state = State.INITIALIZED
+    var session: StompSession? = null
+
+    fun connect() {
+        try {
+            state = State.CONNECTING
+            val client = StandardWebSocketClient()
+            val stompClient = WebSocketStompClient(client)
+            stompClient.messageConverter = MappingJackson2MessageConverter()
+            val sessionHandler =
+                BridgeStompSessionHandler(srvTemplate)
+
+            AppLog.write(this.javaClass).info("Try connect to : ws://$socketHost:$socketPort/_websocket")
+            val cn = stompClient.connect("ws://$socketHost:$socketPort/_websocket", sessionHandler)
+
+            cn.addCallback({ s ->
+                session = s
+                if (s != null && s.isConnected) {
+                    AppLog.write(this.javaClass).info("Socket is connected.")
+                    state = State.CONNECT
+                }
+            }, { e ->
+                AppLog.write(this.javaClass).info("Can't connect to socket server.")
+                AppLog.write(this.javaClass).error(e.message, e)
+                state = State.FAILED
+            })
+            session = sessionHandler.session
+        } catch (e: Exception) {
+            AppLog.write(this.javaClass)
+                .info("Can't connect to socket server, check your configuration and log. retry in 5 second")
+            AppLog.write(this.javaClass).error(e.message, e)
+            state = State.FAILED
+        }
+    }
+}
+
+class BridgeStompSessionHandler(val srvTemplate: SimpMessagingTemplate) : StompSessionHandler {
+    var session: StompSession? = null
+
+    //    val partialDataMap: ConcurrentMap<String, MutableList<PartialData>> = ConcurrentHashMap()
+//    val partialDataMap: ConcurrentMap<String, ConcurrentMap<Int, PartialData>> = ConcurrentHashMap()
+
+    override fun handleException(
+        session: StompSession,
+        command: StompCommand?,
+        headers: StompHeaders,
+        btar: ByteArray,
+        error: Throwable
+    ) {
+        AppLog.write(this.javaClass).info("Socket Handle Error")
+        AppLog.write(this.javaClass).error("Socket Handle Error", error)
+    }
+
+    override fun handleTransportError(session: StompSession, error: Throwable) {
+        AppLog.write(this.javaClass).info("Socket Transport Error")
+        AppLog.write(this.javaClass).error("Socket Transport Error", error)
+    }
+
+    override fun getPayloadType(headers: StompHeaders): Type {
+        return Any::class.java
+    }
+
+    override fun afterConnected(ss: StompSession, sh: StompHeaders) {
+        AppLog.write(this.javaClass).info("Connected to gateway socket server")
+//        ss.subscribe("/topic/partial/$clientName", this)
+//        ss.subscribe("/topic/healthCheck", this)
+        this.session = ss
+    }
+
+    override fun handleFrame(stompHeaders: StompHeaders, payload: Any?) {
+//        "/topic/incoming/clnum"
+        if (payload != null) {
+            val reqId = Singletons.requestInstance.entries.map { it.key }
+                .firstOrNull { stompHeaders.destination?.endsWith(it) ?: false } ?: ""
+            val clientDest =
+                ClientHolder.get().map { it.key }.firstOrNull { stompHeaders.destination?.endsWith(it) ?: false } ?: ""
+//
+            when (stompHeaders.destination) {
+                "/topic/partial/$clientDest" -> {
+                    srvTemplate.convertAndSend(
+                        "/topic/partial/$clientDest",
+                        payload
+                    )
+                }
+                "/topic/response/$reqId" -> {
+                    SocketChecker().collectChunk(payload as SocketChunkData,reqId)
+                }
+            }
+        }
+//        when (stompHeaders.destination) {
+//            "/topic/healthCheck" -> {
+//                if ((1..100).random() == 5) {
+//                    val msgObj = payload as LinkedHashMap<*, *>
+//                    AppLog.write(javaClass).info("Socket Health Check " + msgObj["Hi"])
+//                }
+//            }
+//            "/topic/request/$clientName" -> {
+//                //process
+//                this.session!!.send("/app/confirm/req", "OK")
+//            }
+//
+//            "/topic/partial/$clientName" -> {
+//                val msgObj = payload as LinkedHashMap<*, *>
+//                val partData = PartialData(
+//                    msgObj["id"] as String,
+//                    msgObj["total"] as Int,
+//                    msgObj["idx"] as Int,
+//                    msgObj["data"] as String,
+//                    msgObj["startTime"] as Long
+//                )
+//
+//                val pt = partialDataMap.getOrPut(payload["id"] as String) {
+//                    ConcurrentHashMap()
+//                }
+//                pt.getOrPut(partData.idx){partData}
+//                val listChunk = pt.entries.sortedBy { it.key }.map { it.value }
+//                if (listChunk.isNotEmpty() && listChunk.size == listChunk[0].total) {
+//                    val rbJs = listChunk.joinToString("") { it.data }
+//
+//                    val mapper = jacksonObjectMapper()
+//                    val rb = mapper.readValue(rbJs, RequestBuilder::class.java)
+//                    requestBuilderProcessor(rb, listChunk[0].startTime)
+//                }
+//
+//            }
+//            else -> {
+//                AppLog.write(this.javaClass)
+//                    .info("unknown message from ${stompHeaders.destination} with message $payload")
+//            }
+//
+//        }
+    }
+
+//    fun requestBuilderProcessor(rb: RequestBuilder, startTime: Long) {
+//        var intStartTime = startTime
+//        val headers: MultiValueMap<String, String> = LinkedMultiValueMap<String, String>()
+//        rb.headers.entries.forEach { headers[it.key] = mutableListOf(it.value) }
+//        var url = "http://localhost:" + currentLocalPort + rb.path
+//        AppLog.write(this.javaClass).info("-----------request builder -----------")
+//        AppLog.write(this.javaClass).info(rb.body)
+//        AppLog.write(this.javaClass).info(rb.headers)
+//        AppLog.write(this.javaClass).info(rb.parts)
+//        AppLog.write(this.javaClass).info(rb.queryString)
+//        AppLog.write(this.javaClass).info("-----------end builder -----------")
+//
+//        val queryString = rb.queryString
+//
+//        if (queryString != null) {
+//            url = "$url?$queryString"
+//        }
+//
+//        val urlSend = URI(url)
+//        AppLog.write(this.javaClass).info("socket -> url to access $url")
+//
+//        val httpEnt = rb.parts?.let {
+//            val bodyPart: MultiValueMap<String, Any> = LinkedMultiValueMap()
+//            it.forEach { fp ->
+//                bodyPart.add(
+//                    fp.name,
+//                    FileNameAwareByteArrayResource(fp.fileName, Base64.getDecoder().decode(fp.data), null)
+//                )
+//            }
+//            HttpEntity(bodyPart, headers)
+//        } ?: HttpEntity(rb.body?.replace("\r\n", "\n")?.replace("\r", "\n")?.replace("\n", "\r\n"), headers)
+//
+//        val d1 = (System.nanoTime() - intStartTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
+//
+//        AppLog.write(this.javaClass).info("Socket prc preparation. Time: $d1, ID: ${rb.id}")
+//        intStartTime = System.nanoTime()
+//
+//        var hd: HttpHeaders? = null
+//        var stat: Int
+//        var bdz: ByteArray?
+//
+//        try {
+//            val response = restTemplate.exchange(urlSend, rb.method, httpEnt, Resource::class.java)
+//            bdz = if (response.body != null) {
+//                IOUtils.toByteArray(response.body!!.inputStream)
+//            } else {
+//                ByteArray(0)
+//            }
+//            hd = response.headers
+//            stat = response.statusCode.value()
+//        } catch (httpError: HttpStatusCodeException) {
+//            AppLog.write(this.javaClass).info("http error + ${httpError.message}")
+//            AppLog.write(this.javaClass).error("http error", httpError)
+//            bdz = httpError.responseBodyAsByteArray
+//            hd = httpError.responseHeaders
+//            stat = httpError.statusCode.value()
+//
+//        }
+//        catch (ex: Exception) {
+//            AppLog.write(this.javaClass).info("request error")
+//            AppLog.write(this.javaClass).error("request error", ex)
+//            stat = HttpStatus.BAD_REQUEST.ordinal
+//            bdz = "unknown error".toByteArray(Charset.defaultCharset())
+//        }
+//
+//        val d2 = (System.nanoTime() - intStartTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
+//        AppLog.write(this.javaClass).info("Socket loop internal prc. Time: $d2, ID: ${rb.id}")
+//        intStartTime = System.nanoTime()
+//
+//        this.session?.let {
+//            val chunkSize = 2000
+//            if (bdz != null && bdz.size > chunkSize) {
+//                val chunk = bdz.toList().chunked(chunkSize).map { bt -> bt.toByteArray() }
+//                chunk.forEachIndexed { idx, bt ->
+//                    if (idx < chunk.size - 1) {
+//                        it.send("/app/response/${rb.id}", SocketChunkData(bt, null, stat, idx, chunk.size))
+//                    } else {
+//                        it.send("/app/response/${rb.id}", SocketChunkData(bt, hd, stat, idx, chunk.size))//send
+//                    }
+//                }
+//            } else {
+//                //send
+//                it.send("/app/response/${rb.id}", SocketChunkData(bdz, hd, stat, 1, 1))
+//            }
+//        }
+//        val d3 = (System.nanoTime() - intStartTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
+//        AppLog.write(this.javaClass).info("Socket send response. Time: $d3, ID: ${rb.id}")
+//        val duration = (System.nanoTime() - startTime).toDouble() / 1000000 //divide by 1000000 to get milliseconds.
+//        AppLog.write(this.javaClass).info("Bridge total process Success. Time: $duration, ID: ${rb.id}, URL: $urlSend")
+//
+//    }
+//
+//    class FileNameAwareByteArrayResource(private val fName: String, byteArray: ByteArray, description: String?) :
+//        ByteArrayResource(byteArray, description) {
+//        override fun getFilename() = fName
+//    }
+//
+//    class PartialData(val id: String, val total: Int, val idx: Int, val data: String, val startTime: Long)
+//
+//    class RequestBuilder(
+//        val id: String,
+//        val path: String,
+//        var method: HttpMethod,
+//        var headers: MutableMap<String, String>
+//    ) : Serializable {
+//        var body: String? = null
+//        var queryString: String? = null
+//        var parts: MutableList<FilePart>? = null
+//    }
+//
+//    class FilePart(val name: String, val fileName: String, val data: String)
+//
+//    class SocketChunkData(
+//        val body: ByteArray?,
+//        val header: HttpHeaders?,
+//        val status: Int,
+//        val part: Int,
+//        val totalPart: Int
+//    )
+
+//    val messageMap: ConcurrentMap<String, MutableList<Message>> = ConcurrentHashMap()
+
+
+}

+ 0 - 164
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/IpPhoneBridgeApplication.kt

@@ -1,39 +1,15 @@
 package co.id.datacomsolusindo.ipphonebridge
 
-import kotlinx.coroutines.*
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import org.apache.logging.log4j.Level
 import org.apache.logging.log4j.LogManager
 import org.apache.logging.log4j.Logger
-import org.apache.logging.log4j.core.Filter
-import org.apache.logging.log4j.core.config.Configurator
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory
-import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder
-import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder
-import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder
 import org.ini4j.Wini
 import org.springframework.boot.SpringApplication
 import org.springframework.boot.autoconfigure.SpringBootApplication
-import org.springframework.boot.context.event.ApplicationReadyEvent
-import org.springframework.context.event.EventListener
-import org.springframework.messaging.simp.stomp.StompHeaderAccessor
-import org.springframework.messaging.support.GenericMessage
 import org.springframework.scheduling.annotation.EnableScheduling
-import org.springframework.stereotype.Component
-import org.springframework.web.socket.messaging.SessionConnectEvent
-import org.springframework.web.socket.messaging.SessionDisconnectEvent
-import org.springframework.web.socket.messaging.SessionSubscribeEvent
 import java.io.File
 import java.io.Serializable
-import java.net.URI
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
 import java.util.*
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.ConcurrentMap
 import kotlin.system.exitProcess
-import kotlin.system.measureTimeMillis
 
 
 @SpringBootApplication
@@ -51,7 +27,6 @@ fun main(args: Array<String>) {
         properties["server.port"] = System.getenv("PORT")
     }
     if (properties["server.port"] == null) {
-//        println("undefined port")
         exitProcess(1)
     }
 
@@ -84,145 +59,6 @@ class Client(
     var avgReqSuccessTime = 0.0
 }
 
-object ClientHolder {
-    private val mutexSucReq = Mutex()
-    private val clientMap: ConcurrentMap<String, Client> by lazy { ConcurrentHashMap() }
-    private suspend fun massiveRun(action: suspend () -> Unit) {
-        measureTimeMillis {
-            coroutineScope { // sc
-                launch {
-                    action()
-                }
-            }
-        }
-    }
-
-    fun removeBySessionId(sessionId: String?) {
-        clientMap.entries.filter { it.value.sessionId == sessionId }.forEach {
-            clientMap.remove(it.key)
-        }
-    }
-
-    fun put(key: String, client: Client) {
-        clientMap.getOrPut(key) { client }
-    }
-
-    fun get() = clientMap
-
-    fun addFailedRequest(clNum: String) {
-        runBlocking {
-            withContext(Dispatchers.Default) {
-                massiveRun {
-                    mutexSucReq.withLock {
-                        val cl = clientMap[clNum]
-                        if (cl != null) {
-                            cl.lastRequest =
-                                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
-                            cl.reqFailed += 1
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    fun addSuccessRequest(clNum: String, duration: Double) {
-        runBlocking {
-            withContext(Dispatchers.Default) {
-                massiveRun {
-                    // protect each increment with lock
-                    mutexSucReq.withLock {
-                        val cl = clientMap[clNum]
-                        if (cl != null) {
-                            cl.lastRequest =
-                                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
-                            cl.avgReqSuccessTime =
-                                ((cl.avgReqSuccessTime * cl.reqSuccess) + duration) / (cl.reqSuccess + 1)
-                            cl.reqSuccess += 1
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-@Component
-class WebSocketEventListener {
-    @EventListener(ApplicationReadyEvent::class)
-    fun doSomethingAfterStartup() {
-        logBuilder()
-    }
-
-    fun logBuilder() {
-        val confFile = File("conf/general.ini")
-        var port = if (confFile.exists()) {
-            val ini = Wini(File("conf/general.ini"))
-            ini.get("server", "port")
-        } else {
-            System.getenv("PORT")
-        }
-        port = if (port == null) {
-//        println("undefined port")
-            "9090"
-        } else {
-            port
-        }
-
-        Configurator.reconfigure(URI("http://127.0.0.1:$port/log-setting.xml"))
-    }
-
-    //    @EventListener
-//    private void handleSessionConnected(SessionConnectEvent event) {
-//        ...
-//    }
-//
-//    @EventListener
-//    private void handleSessionDisconnect(SessionDisconnectEvent event) {
-//        ...
-//    }
-    @EventListener
-    fun onConnect(event: SessionConnectEvent) {
-        val accessor = StompHeaderAccessor.wrap(event.message)
-        val sessionId = accessor.sessionId
-        AppLog.write(this.javaClass).info("connect with session id $sessionId")
-
-    }
-
-    @EventListener
-    fun onDisconnect(event: SessionDisconnectEvent) {
-        AppLog.write(this.javaClass).info("disconnect with session id ${event.sessionId}")
-        ClientHolder.removeBySessionId(event.sessionId)
-    }
-
-    @EventListener
-    fun handleSessionSubscribeEvent(event: SessionSubscribeEvent) {
-        val message = event.message as GenericMessage<*>
-        val simDestination = message.headers["simpDestination"] as String?
-        val accessor = StompHeaderAccessor.wrap(event.message)
-        val sessionId = accessor.sessionId
-        AppLog.write(this.javaClass).info("subscribe to $simDestination with session id $sessionId")
-
-
-        if (!(simDestination!!.startsWith("/topic/healthCheck") || simDestination.startsWith("/topic/notification"))) {
-            // do stuff
-            val clNum = simDestination.split("/")[3]
-            ClientHolder.put(
-                clNum, Client(
-                    clNum,
-                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")),
-                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")),
-                    sessionId
-                )
-            )
-
-            LogManager.getLogger(this.javaClass).info("clientConnected $simDestination")
-            logBuilder()
-            LogManager.getLogger("client-$clNum").info("Client $clNum Connected")
-        }
-    }
-}
-
 object AppLog {
     fun write(cls: Class<*>): Logger {
         return LogManager.getLogger(cls)

+ 20 - 0
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/MvcConfig.kt

@@ -0,0 +1,20 @@
+package co.id.datacomsolusindo.ipphonebridge
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
+import org.springframework.web.servlet.resource.PathResourceResolver
+
+@Configuration
+@EnableWebMvc
+class MvcConfig : WebMvcConfigurer {
+    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
+        registry
+            .addResourceHandler("resources/**")
+            .addResourceLocations("file:web/")
+            .setCachePeriod(3600 * 24 * 30)
+            .resourceChain(true)
+            .addResolver(PathResourceResolver())
+    }
+}

+ 21 - 21
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/SocketChecker.kt

@@ -8,12 +8,6 @@ import org.springframework.stereotype.Controller
 
 @Controller
 class SocketChecker {
-//    @MessageMapping("/notification/{id}")
-//    @SendTo("/topic/notification/{id}")
-//    @Throws(Exception::class)
-//    fun notification(@DestinationVariable("id") id: String, message: MutableMap<*, *>): MutableMap<*, *> {
-//        return mutableMapOf(Pair("Hi", "I'am Ok"))
-//    }
 
     @MessageMapping("/hi")
     @SendTo("/topic/healthCheck")
@@ -22,29 +16,35 @@ class SocketChecker {
         return mutableMapOf(Pair("Hi", "I'am Ok"))
     }
 
+    fun collectChunk(message: SocketChunkData, id: String){
+        if (message.totalPart == 1) {
+//                println("one part only with id $id")
+            Singletons.responseQue.getOrPut(id) {
+                Resp(
+                    message.body,
+                    message.status,
+                    message.header?.entries?.associate { Pair(it.key, it.value.toTypedArray()) }
+                )
+            }
+        } else {
+            val sc = Singletons.buildSocketChunkData.getOrPut(id) { ChunkCollector(id) }
+            sc.add(message)
+        }
+    }
+
     @MessageMapping("/response/{id}")
     @SendTo("/topic/healthCheck")
     @Throws(Exception::class)
     fun responseMsg(message: SocketChunkData, @DestinationVariable("id") id: String): MutableMap<*, *> {
         try {
-//            LogManager.getLogger(this.javaClass).info("req sent ${message.part} of ${message.totalPart}")
-//            println("req sent ${message.part} of ${message.totalPart}")
-            if (message.totalPart == 1) {
-//                println("one part only with id $id")
-                Singletons.responseQue.getOrPut(id) {
-                    Resp(
-                        message.body,
-                        message.status,
-                        message.header?.entries?.associate { Pair(it.key, it.value.toTypedArray()) }
-                    )
-                }
-//                println("status code " + Singletons.responseQue[id]?.statusCode ?: "unknown code")
+            if (Singletons.requestInstance.getOrElse(id) { null } == null) {
+                //redirect
+//                /app/response/$req-id
             } else {
-                val sc = Singletons.buildSocketChunkData.getOrPut(id) { ChunkCollector(id) }
-                sc.add(message)
+                collectChunk(message, id)
             }
         } catch (e: Exception) {
-            e.printStackTrace()
+            AppLog.write(this.javaClass).error("response error", e)
         }
 
         return mutableMapOf(Pair("Hi", "I'am Ok"))

+ 79 - 0
src/main/kotlin/co/id/datacomsolusindo/ipphonebridge/WebSocketEventListener.kt

@@ -0,0 +1,79 @@
+package co.id.datacomsolusindo.ipphonebridge
+
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.core.config.Configurator
+import org.ini4j.Wini
+import org.springframework.boot.context.event.ApplicationReadyEvent
+import org.springframework.context.event.EventListener
+import org.springframework.messaging.simp.stomp.StompHeaderAccessor
+import org.springframework.messaging.support.GenericMessage
+import org.springframework.stereotype.Component
+import org.springframework.web.socket.messaging.SessionConnectEvent
+import org.springframework.web.socket.messaging.SessionDisconnectEvent
+import org.springframework.web.socket.messaging.SessionSubscribeEvent
+import java.io.File
+import java.net.URI
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+@Component
+class WebSocketEventListener {
+    @EventListener(ApplicationReadyEvent::class)
+    fun doSomethingAfterStartup() {
+        logBuilder()
+    }
+
+    fun logBuilder() {
+        val confFile = File("conf/general.ini")
+        var port = if (confFile.exists()) {
+            val ini = Wini(File("conf/general.ini"))
+            ini.get("server", "port")
+        } else {
+            System.getenv("PORT")
+        }
+        port = port ?: "9090"
+
+        Configurator.reconfigure(URI("http://127.0.0.1:$port/log-setting.xml"))
+    }
+
+    @EventListener
+    fun onConnect(event: SessionConnectEvent) {
+        val accessor = StompHeaderAccessor.wrap(event.message)
+        val sessionId = accessor.sessionId
+        AppLog.write(this.javaClass).info("connect with session id $sessionId")
+
+    }
+
+    @EventListener
+    fun onDisconnect(event: SessionDisconnectEvent) {
+        AppLog.write(this.javaClass).info("disconnect with session id ${event.sessionId}")
+        ClientHolder.removeBySessionId(event.sessionId)
+    }
+
+    @EventListener
+    fun handleSessionSubscribeEvent(event: SessionSubscribeEvent) {
+        val message = event.message as GenericMessage<*>
+        val simDestination = message.headers["simpDestination"] as String?
+        val accessor = StompHeaderAccessor.wrap(event.message)
+        val sessionId = accessor.sessionId
+        AppLog.write(this.javaClass).info("subscribe to $simDestination with session id $sessionId")
+
+
+        if (!(simDestination!!.startsWith("/topic/healthCheck") || simDestination.startsWith("/topic/notification"))) {
+            // do stuff
+            val clNum = simDestination.split("/")[3]
+            ClientHolder.put(
+                clNum, Client(
+                    clNum,
+                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")),
+                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")),
+                    sessionId
+                )
+            )
+
+            LogManager.getLogger(this.javaClass).info("clientConnected $simDestination")
+            logBuilder()
+            LogManager.getLogger("client-$clNum").info("Client $clNum Connected")
+        }
+    }
+}