如何使用 Kotlin 的 ktor 构建安全的 (wss://...) WebSocket 服务器?

3
我找到了一个不安全的ktor websocket服务器(ws://...)的文档:https://ktor.io/docs/creating-web-socket-chat.html#creating-the-chat-client
我找到了一个安全的ktor http服务器(https://...)的文档:https://github.com/ktorio/ktor-documentation/tree/main/codeSnippets/snippets/ssl-embedded-server
但我似乎找不到或弄不清楚如何提供一个安全的ktor websocket服务器(wss://...)。
我不想使用像nginx这样的SSL反向代理。
编辑:以下是代码:
import io.ktor.application.*
import io.ktor.http.cio.websocket.*
import io.ktor.network.tls.certificates.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.websocket.*
import java.io.*

fun main() {
    val keyStoreFile = File("build/keystore.jks")
    val keystore = generateCertificate(
        file = keyStoreFile,
        keyAlias = "sampleAlias",
        keyPassword = "foobar",
        jksPassword = "foobar"
    )

    val environment = applicationEngineEnvironment {
        sslConnector(
            keyStore = keystore,
            keyAlias = "sampleAlias",
            keyStorePassword = { "foobar".toCharArray() },
            privateKeyPassword = { "foobar".toCharArray() }) {
            port = 8443
            keyStorePath = keyStoreFile
        }
        module(Application::module)
    }

    embeddedServer(Netty, environment).start(wait = true)
}

private fun Application.module() {
    install(WebSockets)
    routing {
        get("/") { // works at https://localhost:8443 in Firefox after approving cert
            call.respondText("This is https")
        }
        webSocket("/chat") { // fails at wss://localhost:8443/chat in Websocket js client with "Firefox can’t establish a connection to the server"
            send("This is wss")
        }
    }
}

您可以像配置HTTP服务器一样配置它。有关详细信息,请阅读SSL和证书文档https://ktor.io/docs/ssl.html。 - Aleksei Tirman
@AlekseiTirman 我尝试使用sslConnector(...),它配置了https并且可以工作,但似乎对websocket没有任何作用。我已经在我的问题中添加了代码。 - tashburn
2个回答

2
问题出在使用自签名证书上。我尝试了Chrome,并把它设置为接受本地主机的自签名证书,这样就可以工作了。Firefox提供的错误消息表明无法建立连接,实际上是拒绝了该证书。
像@AleksiTierman所建议的那样,使用ktor创建一个安全的WebSocket服务器确实可以正常工作。它的配置方式与https服务器相同。

0
我已经用 Postman 测试过了,运行正常。 在浏览器中无法使用 ws:// 或 wss:// 协议,请使用 Postman 或其他 WebSocket 客户端代替。
Application.kt:
import com.example.plugins.configureRouting
import com.example.plugins.configureSerialization
import com.example.plugins.configureSockets
import io.ktor.network.tls.certificates.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import org.slf4j.LoggerFactory
import java.io.File


fun main() {
    val keyStoreFile = File("build/keystore.jks")
    val keystore = generateCertificate(
        file = keyStoreFile,
        keyAlias = "sampleAlias",
        keyPassword = "foobar",
        jksPassword = "foobar"
    )

    val environment = applicationEngineEnvironment {
        log = LoggerFactory.getLogger("ktor.application")
        
        // no ssl, ws://127.0.0.1:6751
        connector {
            host = "127.0.0.1"
            port = 6751
        }
        
        // with ssl, wss://127.0.0.1:6752
        sslConnector(
            keyStore = keystore,
            keyAlias = "sampleAlias",
            keyStorePassword = { "foobar".toCharArray() },
            privateKeyPassword = { "foobar".toCharArray() }) {
            keyStorePath = keyStoreFile
            host = "127.0.0.1"
            port = 6752
        }

        this.module {
            configureSockets()
            configureRouting()
        }
    }

    embeddedServer(Netty, environment).start(wait = true)

/*
    embeddedServer(Netty, port = 6752, host = "127.0.0.1") {
        configureSockets()
        configureRouting()
    }.start(wait = true)

 */
}

Sockets.kt:

import io.ktor.serialization.kotlinx.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.serialization.json.Json
import java.time.Duration


fun Application.configureSockets() {
    routing {
        this@configureSockets.install(WebSockets) {
            contentConverter = KotlinxWebsocketSerializationConverter(Json)
            pingPeriod = Duration.ofSeconds(3)
            timeout = Duration.ofSeconds(5)
            maxFrameSize = Long.MAX_VALUE
            masking = false
        }

        webSocket("/echo") {
            println("onConnect")
            try {
                for (frame in incoming) {
                    val text = (frame as Frame.Text).readText()
                    println("onMessage")
                    send(Frame.Text(text))
                }
            } catch (e: ClosedReceiveChannelException) {
                e.printStackTrace()
                println("onClose ${closeReason.await()}")
            } catch (e: Throwable) {
                e.printStackTrace()
                println("onError ${closeReason.await()}")
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接