从服务端到客户端,一次Ktor的跨端实践

3,785 阅读11分钟

我想经常使用Kotlin写项目的人应该都对Ktor这个框架不陌生,它严格意义上不仅仅是个网络框架,而是一个可以创建异步,高性能和轻量级的Web服务框架,简单的来说它既能写服务端也能写客户端,之前我也用过Ktor写过一个客户端小项目,这次也刚好尝试下如何用Ktor来写服务端代码,并在客户端对它发起请求

服务端

1.创建项目

首先是要创建一个Ktor的服务端项目,创建的方式有多种,在Ktor的官网上有介绍,第一种是针对Intellij使用的是Ultimate的用户,可以直接在编辑器里面直接New Project,然后选择创建Ktor项目就好了

image.png

第二种方式是针对Intellij使用社区版本的用户,可以去start.ktor.io地址选择对应插件,然后创建项目,接着项目就会通过浏览器下载到你的本地,打开即可,这也是我选择的方式,一会还会详细说

image.png

第三种方式是手动添加配置,先自己创建个项目,然后通过Ktor提供的配置代码配置到你的项目中去,Ktor总共提供了Grovvy,Kotlin和Maven三种方式的配置代码供用户选择

image.png

接下来我们就使用第二种方式来创建项目,首先进入start.ktor.io,会先看到一个编辑框,需要先输入你的服务端项目的名字,这个随意写就好

image.png

然后点击Add plugins按钮进入下一个页面,也会看到一个输入框以及一个列表,列表里面是多种多样服务端插件,我选取几种插件,然后点击Generate project按钮,我们的项目包就下载到了我们的本地了

0804aa1.gif image.png

解压然后在IDE中打开,我们的初始项目就完成了

2.项目结构

当我们打开刚才下载完成的项目的时候,我们会看到整体项目结构呈现的会是下面这个样子

image.png

build.gradle.kts都很熟悉了,整个项目的一个配置文件,我们刚刚选择的插件的依赖,就在build.gralde.kts里面生成了,具体代码如下

image.png
  • ktor-server-core:提供核心的Ktor组件
  • ktor-server-host-common:查不到具体介绍,看名字应该是与配置域名有关
  • ktor-server-status-pages:用来处理各种网络异常
  • ktor-server-default-headers:用来给http的response添加头信息
  • ktor-server-netty:提供一个Netty引擎
  • ktor-server-content-negotiationktor-serialization-kotlinx-json:能够方便的将Kotlin的对象转换成序列化形式,常见的就是json
  • logback-classic:SLF4J的插件,让你可以清晰的在控制台看到格式化的log输出

resources文件夹下放的是一些配置文件,比如项目生成的这个logback.xml,主要用来定义一个log输出的结构,里面的代码是这样的

image.png

刚开始我们使用默认的格式就好,等熟练了可以自己去自定义,除了logback.xml文件,还可以放置一些资源文件,比如一些服务端的图片资源就可以在这里添加,稍后我们就会尝试一下,然后就是源码这里,首先是Application.kt文件,这是项目的主文件,里面是项目的入口函数以及一些项目的模块配置

image.png

main函数里面我们可以设置服务器的端口以及ip,以及一些模块功能,这些功能都在module函数里面,configureHttp()函数的功能是配置一些Http信息,比如每次发送出去的response自带的一些头信息就在这里配置,这些代码在Http.kt文件里面

image.png

configureSerialization函数定义在Serialization.kt文件里面,主要功能就是安装ContentNegotiation以及json序列化

image.png

configureRouting函数定义在Routing.kt文件里面,在这个函数里面就可以写一些接口,状态码,状态码对应的message以及配置一些资源信息,比如图片,从下下来的项目里我们可以看到在这个函数里面已经有一个事例接口在里面了

image.png

这是一个get请求,意思是直接访问我们的接口ip加端口号就能返回Hello world!字符串,我们来测试一下这个请求,来运行一下Application.kt文件里面的main函数

image.png

等运行成功之后,你会在控制台里面看到下面这些日志,说明你本地已经开启了一个服务,地址是http://0.0.0.0:8080

image.png

当我们在浏览器里面输入这个地址的时候,就会看到浏览器的界面上就输出了一行Hello world!的信息,也就是项目当中那个get请求

image.png

就这样一个最基本的服务端部分的代码就写完了,很简单是不是,现在我们就来尝试下写一个接口,这个接口就是获取一个图片信息的数组

3.第一个接口

既然是要返回一个图片信息的数组,那么首先需要定义一个图片信息的数据类,我们把它命名为SinglePhoto.kt,里面有这个图片的id,描述,以及这个图片的地址

image.png

然后我们再定义一个PhotoSource.kt文件,里面是获取图片数组的操作,比如从数据库查询等,数据库部分等我学会了再写,这里先固定返回一个数组,函数名叫queryPhotoList

image.png

图片的链接我们一会再添加,然后到Routing.kt文件里面,在那里的get请求下面再增加一个get请求,路径部分我们写上/queryPhotos,lambda表达式里面使用ApplicationCall对象的respond函数来返回一个图片信息的数组

image.png

此时我们再运行一遍代码,在浏览器里面输入http://0.0.0.0:8080/queryPhotos 地址,就得到了我们的图片信息数组的json字符串了

image.png

接下来就是配置图片的链接了,之前说过在resources文件夹下可以配置图片资源,那么我们找几张图放到resources文件夹底下

image.png

接着我们将这些图片代表的链接添加到queryPhotoList的返回数据里面

image.png

这个时候我们再重新运行一遍程序,在浏览器里面重新访问一下http://0.0.0.0:8080/queryPhotos 地址,返回的json字符串里面就带有图片链接了

image.png

另外我们发现现在接口返回的数据是一个list,而比较规范的返回数据的格式往往都是key-value形式,也就是我们需要将返回的list包在一个object里面,怎么做呢?很容易,直接respond一个map就好了

image.png

现在我们再重新运行一遍程序,接口返回的数据就是一个object了

image.png

我们随便找一个链接复制出来放在浏览器里面,来验证下能不能打开我们的图片,结果看到的景象是浏览器返回的是404,找不到这个图片链接,说明我们还少了点配置

image.png

少了什么配置呢,就是除了添加完图片资源之外,也必须在routing函数里面设置一下静态资源的路径,使用staticResources函数设置,这个函数第一个参数是remotePath,我觉得应该表示的是其他服务器的地址,这里暂时为空,第二个参数是basePackage,也就是图片资源所在的包名,我们目前图片直接是在resources的根目录,所以包名也是空,最终代码如下

image.png

这里需要注意的是我这个项目的ktor版本使用的是2.3.3,如果低于这个版本的话staticResources函数是会报红的,原因是这个函数是2.3.3版本新出来的,老版本使用的是另一种设置静态资源的方法,代码如下

image.png

同样的resources里面也是传入图片的包名,没有就传空字符串,新老版本ktor在这里是存在差异的,这个时候我们再运行一遍程序,在浏览器里面重新访问一遍之前的图片链接,我们的图片就打开了

image.png

而在真实的项目当中,图片的目录层级分很多种,有的还是按照模块来划分的,这个时候我们不得不将图片资源添加到不同的目录层级中,这个时候我们的图片资源就需要一个basePackage了,我们将这个basePackage命名为base,然后在base底下再新建两个包名,分别是onetwo,将一部分图片挪入one里面,另一部分图片挪入two里面,效果如下

image.png

图片的路径变了,那么返回值里面的图片链接也要跟着一起改变

image.png

可以看到图片地址里面并没有将根目录base写进去,而是将二级目录one或者two写进去了,但是虽然图片链接里面没有声明根目录,我们在staticResources函数里面需要将base设置到basePackage参数里面去,不然依然会报404

image.png

这个时候我们再运行一遍程序,将最新的图片链接输入到浏览器的地址栏里面,我们的图片依然成功地加载出来了

image.png

现在我们来写一个简单的Compose desktop的小demo,功能很简单,就是请求一下queryPhotos接口,并将返回的数据展示在界面上

客户端

1.创建项目

Compose desktop如何创建项目就不详细介绍了,不清楚的可以自行ChatGPT,先看下gradle里面都用到了哪些ktor插件

image.png

可以看到与之前的服务端配置相比,除去服务端特定的插件之外,基本使用的东西一样,一个是server版本,一个是client版本的,这里需要提到的是,我这边客户端使用的ktor版本是2.2.3,而服务端用的是2.3.3,所以大家做的时候也不用去特意将两端的ktor版本弄成一样,毕竟我看ktor官网里面也没有特地说明这一点,然后就是配置你的HttpClient

image.png

HttpClient的配置也很容易,首先传入的CIO是一种引擎,由于Ktor可以使用在不同的平台上,所以为了兼容各个平台的特性,使用的引擎也是因平台而异,下面是一张平台与引擎的对应表

image.png

除了引擎之外,接下去就是安装各种插件,方式就是调用install函数,这里安装的是DefaultRequest插件,日志插件以及ContentNegotiation插件,要使用什么插件按照自己的项目需求来安装,在DefaultRequest里面,配置的就是我们请求的协议以及地址,这里值得注意的是如果你的服务端地址是ip的话,一定要将端口另外拿出来设置,如果将你的端口与ip同时作为host的话,那么发起请求的时候就会报UnresolvedAddressException

image.png

然后就是定义我们的数据模型,这个模型跟服务端的基本一样所以可以直接拿过来用

image.png

最后就是请求接口部分,新建一个文件PhotoListService.kt,在里面写上一个getImageList函数用来对接我们queryPhotos接口

image.png

到这里所有客户端的网络层代码就写完了,剩下的就是在界面上调用getImageList函数将得到的数据渲染到界面上,我们直接更改事例代码中的App函数,在里面放置一个LazyColumn组件

image.png

由于getImageList函数是一个挂起函数,所以需要将它放在一个协程作用域里面,另外由于Compose desktop无法使用图片加载框架coil,因为它目前只支持移动端,所以这里另外找了一个支持Desktop版本的图片加载框架

image.png

客户端部分的所有代码也都开发完毕了,让我们运行一下看看效果

0807AA1.gif

总结

至此,我们第一次使用ktor同时进行服务端与客户端的开发就完成了,可以看到整体的项目结构都不是很复杂,基本看一下官方文档自己就能写了,尽管代码当中还有很多设计上的不足,比如服务端返回的数据最外层没有用code与message包起来,获取的数据也没有从数据库中加载出来,但这些都会在以后慢慢完善起来,从整体上来看,对于一个前端人来说,以后在开发过程中可以自己写服务端代码去调试自己的界面,等到后端同学提供接口之后再无缝切换,而不是通过写假数据来调试界面,这样既耽误时间,也有可能因为忘记删掉假数据而造成问题。