Kotlin x Nodejs 封装方案,愉快的玩转服务端开发吧

Posted by rarnu on 04-24,2019

前几天写了《Kotlin x Nodejs》的具体操作方法,然而这些操作方法却并不简单,甚至有不少地方是完全没有代码提示的(取决于 dynamic 的特性,无法提示),这会对开发造成很大的困扰。

因此我们就必须有办法来对相应的 js 库进行包装,把相关的 api 暴露出来。按下面的这段代码举例:

app.post("/upload", upload.single("file")) { req, resp ->
    println(req.file.path)
    resp.end()
}

其中 req.file.path 怎么来的?说白了很简单,无非是 req 对象里有一个 file 对象,而 file 对象里又有一个名为 path 的属性,问题是我们怎么知道有这些。在标准的 Nodejs 开发中,我们可以看到相关模块的源码,而在 Kotlin 中却无法很直观的去看了。

这个时候我们有两种做法,一种当然是去 Nodejs 模块的源码里翻了,还有一种比较简单:

app.post("/upload", upload.single("file")) { req, resp ->
    js("for (var prop in req.file) { console.log(prop); }")
    resp.end()
}

然后就可以在终端看到打印出来的内容:

fieldname
originalname
encoding
mimetype
destination
filename
path
size

这就是对象里面包含的字段了,如果对字段里的具体内容感兴趣,可以改造一下代码:

js("for (var prop in req.file) { console.log(prop + ' = ' + req.file[prop]); }")

这样就可以把具体内容也打印出来,方便我们判断数据类型。这里可能会有人问,js() 命令执行一句 js 代码,而 req.file 其实是在这段代码外的,为什么可以执行到呢,对象是如何传进去的?其实在这边,就是一个典型的 “想多了” 的场景。因为 Kotlin Javascript 在编译后,就是一个 js,生成的代码如下:

function App$get$lambda(closure$callback) {
    return function (req, resp) {
        for (var prop in req.file) {
              console.log(prop + ' = ' + req.file[prop]);
        }
        closure$callback(new Request(req), new Response(resp));
        return Unit;
    };
}

所以在这里,是完全不会有上下文的问题的,对象可以传入的原理就这么简单。

然后的事情就会比较有意思了,对 req.file 做一个简单的封装:

class File(private val base: dynamic) {
    val fieldname: String get() = base.fieldname
    val originalname: String get() = base.originalname
    val encoding: String get() = base.encoding
    val mimetype: String get() = base.mimetype
    val destination: String get() = base.destination
    val filename: String get() = base.filename
    val path: String get() = base.path
    val size: Long get() = base.size
    companion object
}

然后我们就可以在 app.post() 里这么用:

app.post("/upload", upload.single("file")) { req, resp ->
    val file = File(req.file)
    println(file.path)
    resp.end()
}

由于有了这么一个类的包装,Idea 终于可以提示代码了,这意味着会使开发变得更加的方便。


所以,在这些的基础上,我们可以轻松的写出各种包装器,把 Nodejs 相关的库进一步 Kotlin 化。例如:

class App(val base: dynamic) {
    fun use(c: dynamic) = base.use(c)
    fun set(key: String, value: dynamic) = base.set(key, value)
    fun listen(port: Int = 8888, callback: () -> Unit) = base.listen(port) { callback() }
    fun get(path: String, callback: (req: Request, resp: Response) -> Unit) =
        base.get(path) { req, resp -> callback(Request(req), Response(resp)) }
    fun post(path: String, callback: (req: Request, resp: Response) -> Unit) =
        base.post(path) { req, resp -> callback(Request(req), Response(resp)) }
    fun post(path: String, option: dynamic, callback: (req: Request, resp: Response) -> Unit) =
        base.post(path, option) { req, resp -> callback(Request(req), Response(resp)) }
}

好了,说了这么多,也是时候来点真材实料的东西了,其实我已经封装好了一个最简单的 Nodejs + Express 的包装器,在开发的时候简单的使用就好了,在这包装器的基础上,编写 Kotlin 代码是非常轻松的,如下:

fun main(args: Array<String>) {
    errorHandler = object : ErrorHandler() {  // 全局的异常处理
        override fun handleRejection(err: String?, promise: dynamic) { }
        override fun handleException(err: String?) { }
    } 
    initServer()  // 初始化服务器
    mongoConnect("sampledb") { succ -> }   // 连接到 mongoldb

    routing("/index") { req, resp ->  // 路由处理
        if (req.session.views) {   // session 处理
            req.session.views += 1
        } else {
            req.session.views = 1
        }
        println("view => ${req.session.views}")
        val name = req.query.name  // 参数处理
        resp.type("text/html")
        val html = renderFile("index.html", optionOf("username" to name))    // 页面模板渲染
        resp.send(html)
    }

    routing("/sql") { _, resp ->  // 查询 mongodb
        mongo?.select("user", listOf("name", "age"), optionOf()) { succ, result ->
            if (succ) {
                result.forEach {
                    println("data => ${it.name}, ${it.age}")
                }
            }
            resp.end()
        }
    }

    routing("/user/:id") { req, resp ->  // restful 路由处理
        val id = req.params.id
        println("userid => $id")
        resp.end()
    }

    startListen(8888)  // 开始监听
}

其实关键问题不在代码本身,而在于可以很爽的进行开发,在大部分的情况下,你的 IDE 都会给予提示了。


其实 Kotlin 服务端系列(含Ktor)讲到这里已经差不多没有什么额外的内容了,下面我会写一些 Kotlin 前端的开发,包括与 React 协同等。

另外,按目前的经验,官方提供的新建项目的向导非常的不友好,每次写文,光是建项目,改配置就有一大堆的事情要做,我打算在近期开发一个全新的新建向导,以便帮助到各位。

最后,本文所说的 wrapper 在哪里?点击此处访问我的Github 就好了!