如何使用 Ktor 快速开发 Web 项目

2023-11-10

photo-of-woman-wearing-pink-top-2810803.jpg

一. Ktor 介绍

Ktor 是一个高性能的、基于 Kotlin 的 Web 开发框架,支持 Kotlin Coroutines、DSL 等特性。

Ktor 是一个由 Kotlin 团队打造的 Web 框架,可用于创建异步、高性能和轻量级的 Web 服务器,并使用 Kotlin 惯用的 API 构建非阻塞的多平台 Web 客户端。

Ktor 的服务端仅限于 JVM,但是 Ktor 的客户端是一个 Multiplatform 的库。

如果使用 Kotlin Multiplatform 构建跨平台项目时,使用 Ktor 的客户端作为 Http 框架是一个不错的选择。

Ktor 由两部分组成:服务器引擎和灵活的异步 HTTP 客户端。当前版本主要集中在 HTTP 客户端上。客户端是一个支持 JVM,JS,Android 和 iOS 的多平台库,现在经常在跨平台移动应用程序中使用。

二. Ktor 服务端的使用

我们可以通过多种方式运行 Ktor 服务端程序:

Ktor Server.png
  • 在 main() 中调用 embeddedServer 来启动 Ktor 应用

  • 运行一个 EngineMain 的 main() 并使用 HOCON application.conf 配置文件

  • 作为 Web 服务器中的 Servlet

  • 在测试中使用 withTestApplication 来启动 Ktor 应用

2.1 Gradle 配置 Ktor

Kotlin 的版本需要 1.3.x,因为 Ktor 底层会依赖到 Kotlin Coroutines。

在需要使用 Ktor 的 module 中添加如下的依赖:

dependencies {
    ...
    implementation "io.ktor:ktor-server-core:${libs.ktor}"
    implementation "io.ktor:ktor-server-netty:${libs.ktor}"
}

后面的例子还会介绍 Ktor 其他的 artifact,例如:freemarker、gson 等。

2.2 embeddedServer

当使用 embeddedServer 时,Ktor 使用 DSL 来配置应用程序和服务器引擎。目前,Ktor 支持 Netty、Jetty、Tomcat、CIO(Coroutine I/O) 作为服务器引擎。(当然,也支持创建自己的引擎并为其提供自定义配置。)

以 Netty 作为服务器引擎为例,通过 embeddedServer 启动 Ktor 应用:

fun main() {
    embeddedServer(Netty, port?:8080, watchPaths = listOf("MainKt"), module = Application::module).start()
}

2.3 ApplicationCall && Routing

当一个请求进入 Ktor 应用时(可以是 HTTP,HTTP / 2 或 WebSocket 请求),该请求将被转换为 ApplicationCall 并通过该应用程序拥有的管道。Ktor 的管道是由一个或多个预先安装的拦截器组成,这些拦截器提供某些功能,例如:路由,压缩等,最终将处理请求。

ApplicationCall 提供对两个主要属性 ApplicationRequest 和 ApplicationResponse 的访问。它们对应于传入请求和传出响应。 除了这些之外,ApplicationCall 还提供了一个 ApplicationEnvironment 和一些有用的功能来帮助响应客户端请求。

Routing 是一项安装在应用程序中的功能,用于简化和构建页面请求处理。Ktor 的 Routing 支持 Restful 的各种方法,以及使用 DSL 进行配置。

Routing 支持嵌套,被称为 Routing Tree,可以通过递归匹配复杂的规则和处理请求。

2.4 CORS

默认情况下,Ktor 提供拦截器以实现对跨域资源共享(CORS)的适当支持。

首先,将 CORS 功能安装到应用中。

fun Application.main() {
  ...
  install(CORS)
  ...
}

Ktor CORS 功能的默认配置仅处理 GET,POST 和 HEAD HTTP 方法以及以下标头:

  HttpHeaders.Accept
  HttpHeaders.AcceptLanguages
  HttpHeaders.ContentLanguage
  HttpHeaders.ContentType

下面的例子展示了如何配置 CORS 功能

fun Application.main() {
  ...
  install(CORS)
  {
    method(HttpMethod.Options)
    header(HttpHeaders.XForwardedProto)
    anyHost()
    host("my-host")
    // host("my-host:80")
    // host("my-host", subDomains = listOf("www"))
    // host("my-host", schemes = listOf("http", "https"))
    allowCredentials = true
    allowNonSimpleContentTypes = true
    maxAge = Duration.ofDays(1)
  }
  ...
}

2.5 Packing

部署 Ktor 应用时,可以使用 fat jar 或者 war 包。

我们以 fat jar 为例,使用 gradle 的 shadow 插件可以方便地打包 Ktor 的应用。

在项目根目录下的 build.gradle 中添加 shadow 插件的依赖:

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
        ......
    }
}

然后在需要打包的 module 中添加 shadow 插件和输出 jar 包名称以及 jar 包的入口 Main 函数:

plugins {
    id 'java'
    id 'kotlin'
    id 'com.github.johnrengelman.shadow'
}

......

shadowJar {
    baseName = 'xxx'  // jar 包名称
    manifest {
        attributes["Main-Class"] = "xxx.xxx.xxx.xxx"  // jar 包的主函数
    }
}

三. 例子

以 RxCache(https://github.com/fengzhizi715/RxCache) 为例,本文会介绍使用 Ktor 开发一个 Local Cache 的 browser(浏览器),用于读取磁盘缓存中的数据。

RxCache 是一款支持 Java 和 Android 的 Local Cache 。目前支持内存、堆外内存、磁盘缓存。

开发的背景:我们存在一些桌面程序部署在 Ubuntu 上,并需要对这些程序进行埋点,而 RxCache 本身支持磁盘的缓存。因此,我使用 RxCache 存储埋点的数据,所以需要一个浏览器的程序来查看本地的埋点数据。

3.1 RxCache 的配置

RxCache 是一个单例,使用时需要先调用 config() 配置 RxCache。

RxCache 支持二级缓存:Memory、Persistence,并拥有多种序列化方式。这些可以通过配置来体现。

val rxCache: RxCache by lazy {

    val converter: Converter = when (Config.converter) {
        "gson"      -> GsonConverter()。
        "fastjson"  -> FastJSONConverter()
        "moshi"     -> MoshiConverter()
        "kryo"      -> KryoConverter()
        "hessian"   -> HessianConverter()
        "fst"       -> FSTConverter()
        "protobuf"  -> ProtobufConverter()
        else        -> GsonConverter()
    }

    RxCache.config {
        RxCache.Builder().persistence {
            when (Config.type) {
                "disk"   -> {
                    val cacheDirectory = File(Config.path) // rxCache 持久层存放地址
                    if (!cacheDirectory.exists()) {
                        cacheDirectory.mkdir()
                    }
                    DiskImpl(cacheDirectory, converter)
                }
                "okio"   -> {
                    val cacheDirectory = File(Config.path) // rxCache 持久层存放地址
                    if (!cacheDirectory.exists()) {
                        cacheDirectory.mkdir()
                    }
                    OkioImpl(cacheDirectory, converter)
                }
                "mapdb"  -> {
                    val cacheDirectory = File(Config.path) // rxCache 持久层存放地址
                    MapDBImpl(cacheDirectory, converter)
                }
                "diskmap"-> {
                    val cacheDirectory = File(Config.path) // rxCache 持久层存放地址
                    DiskMapImpl(cacheDirectory, converter)
                }
                else     -> {
                    val cacheDirectory = File(Config.path) // rxCache 持久层存放地址
                    if (!cacheDirectory.exists()) {
                        cacheDirectory.mkdir()
                    }
                    DiskImpl(cacheDirectory, converter)
                }
            }
        }
    }

    RxCache.getRxCache()
}

3.2 module

Ktor module 是一个开发者定义的函数,它用于接收 Application 类(该类负责配置服务器管道,安装功能,注册路由,处理请求等)。

在本例子中,安装了 DefaultHeaders、CallLogging、FreeMarker、ContentNegotiation、Routing。

fun Application.module() {

    install(DefaultHeaders)
    install(CallLogging)
    install(FreeMarker) {
        templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
        defaultEncoding = "utf-8"
    }
    install(ContentNegotiation) {
        gson {
            setDateFormat(DateFormat.LONG)
            setPrettyPrinting()
        }
    }
    install(Routing) {
         ......
    }
}

3.3 Routing

Routing 提供了对外的页面。

    install(Routing) {
        static("/") {
            defaultResource("index.html", "web")
        }

        post("/saveConfig") {

            val postParameters: Parameters = call.receiveParameters()

            Config.path = postParameters["path"] ?: ""
            Config.type = postParameters["type"] ?: ""
            Config.converter = postParameters["converter"] ?: ""

            call.respond(FreeMarkerContent("save.ftl", mapOf("config" to Config)))
        }
        get("/list") {

            val file = File(Config.path)
            val array = file.list()
            call.respond(array)
        }
        get("/detail/{key}") {

            val key = call.parameters["key"]
            val json = rxCache.getStringData(key)
            call.respondText(json)
        }
        get("/info") {

            val json = rxCache.info
            call.respondText(json)
        }
    }

其中 index.html 用于配置 RxCache。

saveConfig 用于展示保存的 RxCache 的数据,其中用到了 FreeMarker 的模板 save.ftl

<html>
<h2>Hi</h2>

RxCache's path: ${config.path} </br>
RxCache's persistence: ${config.type} </br>
RxCache's serialization: ${config.converter} </br>
</html>

list 接口、detail 接口分别用于展示磁盘存储数据的 key,以及根据 key 来查询详细的存储内容。

list 接口
detail 接口

info 接口用于显示缓存中的信息。

info 接口

3.4 启动

browser 配置了 kotlinx-cli,它可以通过命令行解析参数。目前,只支持 '-p' 用于表示启动 Ktor 应用的端口号。

browser 使用 Netty 作为服务器引擎。

fun main(args: Array<String>) {

    val parser = ArgParser("rxcache-browser")
    val port            by parser.option(ArgType.Int, shortName = "p", description = "Port number of the local web service")
    parser.parse(args)

    embeddedServer(Netty, port?:8080, watchPaths = listOf("MainKt"), module = Application::module).start()
}

四. 小结

Ktor 构建的应用,只需少量代码和配置即可完成,非常简便。

非常适用于简单的 Web 项目、对外提供接口的 OpenAPI 项目。当然使用它来构建微服务也是可以,它也有丰富的 Features(https://ktor.io/servers/features.html)。

RxCache 项目地址:https://github.com/fengzhizi715/RxCache 

例子的代码:https://github.com/fengzhizi715/RxCache/tree/master/browser

关注【Java与Android技术栈】

更多精彩内容请关注扫码

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Ktor 快速开发 Web 项目 的相关文章

  • 将链接对象转换为流或集合

    我想迭代堆栈跟踪 堆栈跟踪由可抛出对象组成 其 getCause 返回下一个可抛出对象 最后一次调用 getCause 返回 null 示例 a gt b gt null 我尝试使用 Stream iterable 这会导致 NullPoi
  • 使用 Checkstyle Plugin 时从插件调用代码时出现问题:“org.eclipse.jface”

    我正在尝试在 Rational Software Architect 7 0 0 4 上使用 eclipse cs 插件 我最近卸载了旧的 beta2 版本并安装了 beta3 插件本身按照之前的配置工作 但是每当我尝试通过 Windows
  • JTree 节点不会被直观地选择

    不知何故 我无法为我的 JTree 节点启用 选择突出显示 我正在我的项目中使用自定义单元格渲染器 这很可能导致此问题 这是完整的渲染器类代码 protected class ProfessionTreeCellRenderer exten
  • 有没有好的方法来解析用户代理字符串?

    我有一个Java接收模块User Agent来自最终用户浏览器的字符串的行为需要略有不同 具体取决于浏览器类型 浏览器版本甚至操作系统 例如 FireFox 7 0 Win7 Safari 3 2 iOS9 我明白了User Agent由于
  • Google Inbox 类似 RecyclerView 项目打开动画

    目前 我正在尝试实现 Google Inbox 例如RecyclerView行为 我对电子邮件打开动画很好奇 我的问题是 该怎么做 我的意思是 他们使用了哪种方法 他们用过吗ItemAnimator dispatchChangeStarti
  • 方法断点可能会大大减慢调试速度

    每当向方法声明行添加断点 在 Intellij IDEA 或 Android Studio 中 时 都会出现一个弹出窗口 方法断点可能会大大减慢调试速度 为什么会这样戏剧性地减慢调试速度 是我的问题吗 将断点放在函数的第一行有什么不同 Th
  • 服务器到 Firebase HTTP POST 结果为响应消息 200

    使用 Java 代码 向下滚动查看 我使用 FCM 向我的 Android 发送通知消息 当提供正确的服务器密钥令牌时 我收到如下所示的响应消息 之后从 FCM 收到以下响应消息 Response 200 Success Message m
  • 如何将 XMP XML 块序列化为现有的 JPEG 图像?

    我有许多 JPEG 图像 其中包含损坏的 XMP XML 块 我可以轻松修复这些块 但我不确定如何将 固定 数据写回图像文件 我目前正在使用 JAVA 但我愿意接受任何能让这项任务变得容易的事情 这是目标关于 XMP XML 的另一个问题
  • 如何从 Dockerfile 安装 Python 3.7 和 Pip

    我正在尝试构建基于 Ubuntu 18 04 的自定义 Docker 映像 Ubuntu 预装了 Python 3 6 但我想 1 安装 Python 3 7 2 将其设置为默认 Python 版本 这样就可以使用python代替pytho
  • 尝试在没有 GatewayIntent 的情况下访问消息内容

    我希望每当我写一条打招呼的消息时 机器人都会在控制台中响应一条消息 但它只是给我一个错误 JDA MainWS ReadThread WARN JDA Attempting to access message content without
  • 参数动态时如何构建 JPQL 查询?

    我想知道是否有一个好的解决方案来构建基于过滤器的 JPQL 查询 我的查询太 富有表现力 我无法使用 Criteria 就像是 query Select from Ent if parameter null query WHERE fiel
  • 为什么 ConcurrentHashMap::putIfAbsent 比 ConcurrentHashMap::computeIfAbsent 更快?

    使用 ConcurrentHashMap 我发现computeIfAbsent 比putIfAbsent 慢两倍 这是简单的测试 import java util ArrayList import java util List import
  • tomcat 过滤所有 web 应用程序

    问题 我想对所有网络应用程序进行过滤 我创建了一个过滤器来监视对 apache tomcat 服务器的请求 举例来说 它称为 MyFilter 我在 netbeans 中创建了它 它创建了 2 个独立的目录 webpages contain
  • 从 Java 日历迁移到 Joda 日期时间

    以前 当我第一次设计股票应用相关软件时 我决定使用java util Date表示股票的日期 时间信息 后来我体会到了大部分方法java util Date已弃用 因此 很快 我重构了所有代码以利用java util Calendar 然而
  • 从 Stax XMLStreamReader 读取以解组部分

    我正在使用 Stax 游标 API 从大型 xml 文件中提取数据 当前 我转到特殊标签的开头并使用 JAXB 解组该标签 这对于格式良好的 xml 文件效果很好 但不久前我有一个文档 其中数十万个标签中有一个未关闭 JAXB 使用 XML
  • 使用 Java 从 S3 上的文件在 S3 上创建 zip 文件

    我在 S3 上有很多文件 需要对其进行压缩 然后通过 S3 提供压缩文件 目前 我将它们从流压缩到本地文件 然后再次上传该文件 这会占用大量磁盘空间 因为每个文件大约有 3 10MB 而且我必须压缩多达 100 000 个文件 所以一个 z
  • 来自客户端的超时 Web 服务调用

    我正在使用 RestEasy 客户端调用网络服务 一项要求是 如果调用运行时间超过 5 秒 则中止 超时调用 我如何使用 RestEasy 客户端实现这一目标 我只看到服务器端超时 即如果在一定时间内未完成请求 Rest Easy 网络服务
  • 从一个文本文件中获取数据并将其移动到新的文本文件

    我有一个文件 里面有数据 在我的主要方法中 我读入文件并关闭文件 我调用另一种方法 在原始文件的同一文件夹内创建一个新文件 所以现在我有两个文件 原始文件和通过我调用的方法生成的文件 我需要另一种方法 从原始文件中获取数据并将其写入创建的新
  • Spring Boot MSSQL Kerberos 身份验证

    目前在我的春季靴子中application properties文件中 我指定以下行来连接到 MSSql 服务器 spring datasource url jdbc sqlserver localhost databaseName spr
  • 使用 eclipse IDE 配置 angularjs

    我想开始使用 AngularJs 和 Java Spring 进行开发 我使用 Eclipse 作为 IDE 我想配置我的 Eclipse 以使这些框架无缝工作 我知道我可能要求太多 但相信我 我已经做了很多研究 你们是我最后的选择 任何帮

随机推荐

  • python强化学习--gym安装与使用

    最近开始学习强化学习 第一步肯定是要学会安装和使用pym 原本以为很简单 事实上确实很简单 但是遇到一个小问题 就是安装gym之后 在应用的过程中 游戏界面没有显示出来 了解后才知道是gym版本不对 一种可用的版本匹配是 python 3
  • 前端面试题汇总

    总结一下前端面试官会经常问到的一些面试题 目录 总结一下前端面试官会经常问到的一些面试题 一阶段 HTML5 CSS3 隐藏页面中某个元素的办法 区别 请说明 px em rem vw vh rpx 等单位的特性 什么是 BFC 盒子模型总
  • JAVA课程设计(小游戏贪吃蛇)完整源码附素材(一)

    JAVA课程设计 小游戏贪吃蛇 完整源码附素材 一 JAVA课程设计 小游戏贪吃蛇 完整源码附素材 二 JAVA课程设计 小游戏贪吃蛇 完整源码附素材 三 文章目录 前言 一 任务描述 1 1 课程设计目的 1 2 课程设计内容和要求 二
  • 设计一个回合制战斗系统Combat(C++)

    C 设计一个回合制战斗系统Combat 目录 C 设计一个回合制战斗系统Combat 题目 重要提醒 Soldier类 Wizard类 Master类 Warsystem类 题目 设计和实现回合制战斗系统Combat 1 Soldier战士
  • Weex学习第二篇:Hello world

    曾经何时 我以为学习一门语言或者是新技术 只要能写出Hello world 就算是学会了 这个思想困扰了我很久 以至于之前整理电脑的时候发现php python ruby phonegap react native go node js n
  • Windows中的WSL2(子系统)开机启动配置

    常规做法 通常在Linux中开机启动可以通过 1 编辑 etc rc loacl 2 在 etc init d 下添加启动脚本 3 配置systemd 但这几种方式在子系统中无法使用 我们可以通过Windows 间接的启动子系统中的服务 在
  • C#基础(json解析)

    json是一种轻量级的数据交换格式 采用完全独立于语言的文本格式 易于解析和生成 在c 中 解析json数据通常是利用vs中自带的litjson包 然后进行解析 首先新建一个文本文件 创建一个json数据 如下 id 1 name 寄生者
  • Jenkins学习笔记第九篇pipeline 接口自动化持续集成测试

    Scripts Pipeline 基于groovy的语法 Declarative pipeline V2 5之后引入 结构化方式 script pipeline书写形式如下 node def mvnHome stage Preparatio
  • secureCRT 登录Ubuntu20.04提示Key exchange failed. No compatible key exchange method

    问题描述 之前在Ubuntu18 04上按照博客文章 ubuntu18 04系统搭建以及配置 配置ssh 登录是没有问题的 但最新新的项目需要安装Ubuntu20 04 在安装了ubuntu20 04后 以前老版本的secureCRT通过s
  • springboot2.x redis Lettuce版本使用时报错

    springboot2 x redis Lettuce版本使用时报错 springboot2 x redis使用时报错 原因 解决方法 springboot2 x redis使用时报错 原因 在springboot2 x 以后 官方默认使用
  • Unity中行星和恒星的旋转——Rotate和RotateAround

    Unity中的旋转 以行星环绕为例 实现效果 一 与之相关的两种旋转方式 1 Rotate 2 transform RotateAround 二 行星案例的实现 Step1 我们先在场景中创建一个球体 并将它放大作为被环绕的恒星 我这里自己
  • 范数的数学意义

    L0 L1 L2范数的数学意义 如有不当 敬请斧正 Tips 范数所表示的一些数学意义 众数 中位数 均值 A mathcal A A L0范数 求L0范数最小时 表示的是数据中的众数modes 假设
  • 家里电脑dnf无线连接服务器,win7系统dnf正在连接服务器的解决方法

    我们在操作win7系统电脑的时候 常常会遇到 2 关闭后点击 开始游戏即可正常进入游戏 出现这样的现象是由于当前win7系统电脑与dnf游戏服务器连接失败导致的 这个问题早在以往就会有发生 但是到了跨区合并游戏大区后问题又被进一步放大了 和
  • windows一键安装mysql脚本bat

    下载需要的zip版本的mysql压缩文件 解压 在bin目录创建mysql init bat 复制内容保存 cd dp0 cd del cd my ini echo 删除完成 echo mysqld gt gt my ini echo 设置
  • python接收mysql语句进行查询

    mysql语句作为外部参数传入进行查询 最近在做自动化测试时遇到一个问题 需要将sql语句传入python脚本里面进行查询 支持不同类型的sql语句 只需在外部修改sql语句就可以进行mysql的增删改查 代码 coding utf 8 i
  • CSS(简)

    CSS CSS概述 CSS是 Cascading Style Sheets 级联样式表 CSS是一种样式表语言 用于为HTML文档控制外观 定义布局 例如 CSS涉及字体 颜色 边距 高度 宽度 背景图像 高级定位等方面 可将页面的内容与表
  • C语言中求和、计算平均值、方差和标准差

    计算C语言中的求和 标准差 方差和标准差等 需要加上头文件 include
  • SpringBoot笔记梳理

    本次笔记目录结构如下 1 SpringBoot自动配置原理 2 SpringBoot获取模块bean的几种方式 2 1 包路径放大 import注解进行导入配置类 2 2 自定义注解 EnableUse 2 3 使用ImportSector
  • js基础之继承

    js继承 是指一个对象可以继承另一个对象的属性和方法 以便利用现有的代码来创建新的对象 在JavaScript中 继承主要有以下几种常见的实现方式 通过原型链继承 构造函数继承 组合继承 即原型链继承 构造函数继承 寄生组合继承 es6类的
  • 如何使用 Ktor 快速开发 Web 项目

    photo of woman wearing pink top 2810803 jpg 一 Ktor 介绍 Ktor 是一个高性能的 基于 Kotlin 的 Web 开发框架 支持 Kotlin Coroutines DSL 等特性 Kto