2021年5月7日 星期五

Andoid 使用 kotlin 的 coroutine 實現簡單的 http 伺服器

程式複製到  MainActivity.kt:

package com.example.simplehttp
import android.os.Bundle
import android.net.wifi.WifiManager
import androidx.appcompat.app.AppCompatActivity
import java.net.ServerSocket
import java.net.Socket
import java.net.SocketTimeoutException
import java.io.IOException
import java.io.PrintWriter
import java.util.*
import kotlinx.coroutines.*
typealias _ReplyURI = (Socket) -> Unit
class MainActivity : AppCompatActivity() {
    private val patternBegin = "GET "
    private val patternEnd = " HTTP/1.1"
    private val hformat = "HTTP/1.0 200 OK\nServer: Simple\nContent-Type: %s\nContent-Length: %11d\nConnection: close\n\n"
    private val socketPrintHTML:(Socket, String) -> Unit = {sfd, html ->
        try {
            val writer = PrintWriter(sfd.getOutputStream(), true)
            writer.printf(hformat, "text/html", html.length)
            writer.printf("%s", html)
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
    data class OneResponse(val uri:String, val lambda: _ReplyURI)
    inner class ToResponse(val fd: Socket) {
        private val field = ArrayList<OneResponse>()
        private val lambdaNull: _ReplyURI = { socketPrintHTML(it, "is lambda Null ok?") }
        val execute:(String) -> Unit = { uri ->
            if(! fd.isClosed) {
                val matchURI = uri.toLowerCase()
                for(i in field.indices) if(field[i].uri == matchURI) {
                    field[i].lambda(fd)
                }
                socketPrintHTML(fd, "not found $uri")
            }
        }
        fun toReply(uri:String, lambda:_ReplyURI? = null) {
            field.add(OneResponse(
                uri.toLowerCase(),
                lambda ?: lambdaNull
            ))
        }
    }
    private fun uhttpRequest(res:ToResponse) {
        val rxSize = 1024
        val rxBuffer = ByteArray(rxSize)
        val len = res.fd.getInputStream().read(rxBuffer, 0, rxSize - 1)
        if(len > 0) {
            rxBuffer[len] = 0
            val str = String(rxBuffer)
            if (str.startsWith(patternBegin) && str.contains(patternEnd)) res.
                execute( str.substring( patternBegin.length, str.indexOf(patternEnd) ))
        }
    }
    private lateinit var serverJob: Job
    private var portTCP: Int = 8080
    private var snapURI: String = "/"
    private  fun uListen() {
        var streamingLock = false
        val tcpServer = ServerSocket(portTCP).apply {
            soTimeout = 40
        }
        while (serverJob.isActive) {
            try {
                val socketfd = tcpServer.accept()
                val req = ToResponse(socketfd). apply {
                    toReply("/Hello1")
                    toReply("/Hello2") { sfd ->
                        socketPrintHTML(sfd, "You are welcome")
                    }
                    toReply(snapURI) { sfd -> runBlocking {
                        while(streamingLock) delay(100)
                        streamingLock = true //...
                            socketPrintHTML(sfd, "Coroutine runBlocking ok")                        
                        streamingLock = false
                    }}
                }
                GlobalScope.launch(Dispatchers.IO) {
                    uhttpRequest(req)
                    socketfd.close()
                }
            } catch (e: SocketTimeoutException) {                
            }
        }
        tcpServer.close()
    }    
    override fun onResume() {        
        val info = (applicationContext.
            getSystemService(WIFI_SERVICE) as WifiManager).connectionInfo
        val a = info.ipAddress        and 0xff
        val b = info.ipAddress shr  8 and 0xff
        val c = info.ipAddress shr 16 and 0xff
        val d = info.ipAddress shr 24 and 0xff
        title = "http://$a.$b.$c.$d:${portTCP}${snapURI}"
        serverJob = GlobalScope.launch(Dispatchers.IO) {
            uListen()            
        }
    }    
    override fun onPause() {
        runBlocking {
            serverJob.cancelAndJoin()
        }
        super.onPause()
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (savedInstanceState == null) {
        }
    }
}

沒有留言:

張貼留言

使用 pcie 轉接器連接 nvme SSD

之前 AM4 主機板使用 pcie ssd, 但主機板故障了沒辦法上網, 只好翻出以前買的 FM2 舊主機板, 想辦法讓老主機復活, 但舊主機板沒有 nvme 的界面, 因此上網買了 pcie 轉接器用來連接 nvme ssd, 遺憾的是 grub2 bootloader 無法識...