I. Introduction of Ktor

Ktor is a high-performance, Kotlin-based Web development framework that supports Kotlin Coroutines, DSL, and more.

Ktor is a Web framework built by Kotlin’s team for creating asynchronous, high-performance, and lightweight Web servers and building non-blocking multi-platform Web clients using Kotlin’s preferred apis.

The server side of Ktor is limited to the JVM, but the client side of Ktor is a Multiplatform library.

When building cross-platform projects using Kotlin Multiplatform, using Ktor’s client as an Http framework is a good choice.

Ktor consists of two parts: a server engine and a flexible asynchronous HTTP client. The current version focuses on HTTP clients. The client is a multi-platform library that supports JVM, JS, Android and iOS and is now commonly used in cross-platform mobile applications.

2. Use of Ktor server

We can run Ktor server programs in several ways:

  • Call embeddedServer in main() to launch the Ktor application
  • Run an EngineMain main() and use the HOCON application.conf configuration file
  • As a Servlet in a Web server
  • Use withTestApplication to launch the Ktor application in the test

2.1 Gradle Configure Ktor

Kotlin’s version requires 1.3.x because the Ktor base relies on Kotlin Coroutines.

Add the following dependencies to modules that need to use Ktor:

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

Later examples will also introduce other Ktor artifacts, such as Freemarker, Gson, and more.

2.2 embeddedServer

When using embeddedServer, Ktor uses DSL to configure the application and server engine. Currently, Ktor supports Netty, Jetty, Tomcat, and Coroutine I/O (CIO) as server engines. (Of course, you can also create your own engine and provide custom configurations for it.)

Using Netty as a server engine, start the Ktor application through embeddedServer:

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

2.3 ApplicationCall && Routing

When a request enters a Ktor application (it can be an HTTP, HTTP / 2, or WebSocket request), the request is converted to an ApplicationCall and passed through a pipe owned by the application. Ktor’s pipeline is made up of one or more pre-installed interceptors that provide some functionality, such as routing, compression, etc., and will eventually process the request.

ApplicationCall provides access to the two main properties ApplicationRequest and ApplicationResponse. They correspond to incoming requests and outgoing responses. In addition to this, ApplicationCall provides an ApplicationEnvironment and some useful functionality to help respond to client requests.

Routing is a feature installed in the application to simplify and build page request processing. Ktor’s Routing supports a variety of Restful methods, as well as configuration using DSLS.

Routing supports nesting, known as a Routing Tree, and can recursively match complex rules and process requests.

2.4 CORS

By default, Ktor provides interceptors to enable appropriate support for cross-domain resource sharing (CORS).

First, install CORS functionality into your application.

fun Application.main(a){... install(CORS) ... }Copy the code

The default configuration of the Ktor CORS function only handles GET, POST, and HEAD HTTP methods and subheaders:

  HttpHeaders.Accept
  HttpHeaders.AcceptLanguages
  HttpHeaders.ContentLanguage
  HttpHeaders.ContentType
Copy the code

The following example shows how to configure CORS functionality

fun Application.main(a){... 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)}... }Copy the code

2.5 Packing

When deploying Ktor applications, you can use fat JARS or WAR packages.

Using fat Jar as an example, gradle’s Shadow plugin makes it easy to package Ktor applications.

Add the shadow plugin dependency to build.gradle at the root of your project:

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com. Making. Jengelman. Gradle. Plugins: shadow: 5.2.0'. }}Copy the code

Then add the shadow plugin to the module that you want to package, the output jar package name, and the jar package entry Main function:

plugins {
    id 'java'
    id 'kotlin'
    id 'com.github.johnrengelman.shadow'}... shadowJar { baseName ='xxx'  // Jar package name
    manifest {
        attributes["Main-Class"] = "xxx.xxx.xxx.xxx"  // The main function of the jar package}}Copy the code

Example 3.

Using RxCache as an example, this article will introduce the development of a Local Cache browser using Ktor, which is used to read data from the disk Cache.

RxCache is a Local Cache that supports Java and Android. Currently supports memory, off-heap memory, and disk caching.

Background: We have some desktop applications deployed on Ubuntu that need to be buried, and RxCache itself supports disk caching. Therefore, I use RxCache to store buried data, so I need a browser program to view the local buried data.

3.1 Configuring RxCache

RxCache is a singleton. You need to call config() to configure RxCache.

RxCache supports two levels of caching: Memory, Persistence, and multiple serialization methods. This can be done through configuration.

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 persistence layer storage address
                    if(! cacheDirectory.exists()) { cacheDirectory.mkdir() } DiskImpl(cacheDirectory, converter) }"okio"- > {val cacheDirectory = File(Config.path) // rxCache persistence layer storage address
                    if(! cacheDirectory.exists()) { cacheDirectory.mkdir() } OkioImpl(cacheDirectory, converter) }"mapdb"- > {val cacheDirectory = File(Config.path) // rxCache persistence layer storage address
                    MapDBImpl(cacheDirectory, converter)
                }
                "diskmap"- > {val cacheDirectory = File(Config.path) // rxCache persistence layer storage address
                    DiskMapImpl(cacheDirectory, converter)
                }
                else- > {val cacheDirectory = File(Config.path) // rxCache persistence layer storage address
                    if(! cacheDirectory.exists()) { cacheDirectory.mkdir() } DiskImpl(cacheDirectory, converter) } } } } RxCache.getRxCache() }Copy the code

3.2 the module

The Ktor Module is a developer-defined function that receives the Application class (which is responsible for configuring the server pipes, installing features, registering routes, processing requests, and so on).

In this example, DefaultHeaders, CallLogging, FreeMarker, ContentNegotiation, Routing are installed.

fun Application.module(a) {

    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) { ...... }}Copy the code

3.3 Routing

Routing provides external pages.

    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)
        }
    }
Copy the code

Index. HTML is used to configure RxCache.

SaveConfig is used to show the saved RxCache data, which uses the FreeMarker template 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>
Copy the code

The list and detail interfaces are used to display the key of disk data storage and query the detailed storage content based on the key.

The INFO interface is used to display information in the cache.

3.4 start

Browser is configured with Kotlinx -CLI, which can parse parameters from the command line. Currently, only ‘-p’ is supported to indicate the port number for starting Ktor applications.

Browser uses Netty as the server engine.

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()
}
Copy the code

4. Summary

Ktor builds applications that require very little code and configuration.

It is very suitable for simple Web projects and OpenAPI projects that provide interfaces externally. Of course, it is also possible to use it to build microservices, and it also has rich Features.

RxCache project address: github.com/fengzhizi71… Example code: github.com/fengzhizi71…