0

我正在尝试使用 Ktor 和 KMongo 实现一个 RESTful api。我连接了一个 mongoDB Atlas 数据库,它运行良好。

然后我准备在 Heroku 上部署第一个版本的 api,现在当我在本地运行它时,对数据库的每个查询都会抛出这个异常:

2021-01-07 15:10:30.211 [eventLoopGroupProxy-4-2] INFO  org.mongodb.driver.cluster - Cluster created with settings {hosts=[127.0.0.1:27017], srvHost=cluster0.euib1.mongodb.net, mode=MULTIPLE, requiredClusterType=REPLICA_SET, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500, requiredReplicaSetName='atlas-hl2rus-shard-0'}
15:10:30 web.1   |  2021-01-07 15:10:30.258 [cluster-ClusterId{value='5ff7165648f4085f7af773f9', description='null'}-srv-cluster0.euib1.mongodb.net] INFO  org.mongodb.driver.cluster - Adding discovered server cluster0-shard-00-00.euib1.mongodb.net:27017 to client view of cluster
15:10:30 web.1   |  2021-01-07 15:10:30.268 [eventLoopGroupProxy-4-2] ERROR Application - Unhandled: GET - /books
15:10:30 web.1   |  java.lang.NullPointerException: Cannot invoke "com.mongodb.connection.ClusterDescription.getConnectionMode()" because "clusterDescription" is null
15:10:30 web.1   |      at com.mongodb.async.client.ClientSessionHelper.getServerDescriptionListToConsiderForSessionSupport(ClientSessionHelper.java:107)
15:10:30 web.1   |      at com.mongodb.async.client.ClientSessionHelper.createClientSession(ClientSessionHelper.java:63)
15:10:30 web.1   |      at com.mongodb.async.client.ClientSessionHelper.withClientSession(ClientSessionHelper.java:51)
15:10:30 web.1   |      at com.mongodb.async.client.OperationExecutorImpl.execute(OperationExecutorImpl.java:66)
15:10:30 web.1   |      at com.mongodb.async.client.MongoIterableImpl.batchCursor(MongoIterableImpl.java:161)
15:10:30 web.1   |      at com.mongodb.async.client.MongoIterableSubscription.requestInitialData(MongoIterableSubscription.java:46)
15:10:30 web.1   |      at com.mongodb.async.client.AbstractSubscription.tryRequestInitialData(AbstractSubscription.java:151)
15:10:30 web.1   |      at com.mongodb.async.client.AbstractSubscription.request(AbstractSubscription.java:84)
15:10:30 web.1   |      at com.mongodb.reactivestreams.client.internal.ObservableToPublisher$1$1.request(ObservableToPublisher.java:50)
15:10:30 web.1   |      at kotlinx.coroutines.reactive.SubscriptionChannel.onReceiveEnqueued(Channel.kt:64)
15:10:30 web.1   |      at kotlinx.coroutines.channels.AbstractChannel.enqueueReceive(AbstractChannel.kt:592)
15:10:30 web.1   |      at kotlinx.coroutines.channels.AbstractChannel.access$enqueueReceive(AbstractChannel.kt:488)
15:10:30 web.1   |      at kotlinx.coroutines.channels.AbstractChannel$Itr.hasNextSuspend(AbstractChannel.kt:841)
15:10:30 web.1   |      at kotlinx.coroutines.channels.AbstractChannel$Itr.hasNext(AbstractChannel.kt:827)
15:10:30 web.1   |      at org.litote.kmongo.coroutine.CoroutinePublisherKt.toList(CoroutinePublisher.kt:56)
15:10:30 web.1   |      at org.litote.kmongo.coroutine.CoroutinePublisher.toList(CoroutinePublisher.kt:46)
15:10:30 web.1   |      at com.flockware.BookControllerKt$booksRoutes$1$1.invokeSuspend(BookController.kt:29)
15:10:30 web.1   |      at com.flockware.BookControllerKt$booksRoutes$1$1.invoke(BookController.kt)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
15:10:30 web.1   |      at io.ktor.features.StatusPages$interceptCall$2.invokeSuspend(StatusPages.kt:102)
15:10:30 web.1   |      at io.ktor.features.StatusPages$interceptCall$2.invoke(StatusPages.kt)
15:10:30 web.1   |      at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
15:10:30 web.1   |      at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:189)
15:10:30 web.1   |      at io.ktor.features.StatusPages.interceptCall(StatusPages.kt:101)
15:10:30 web.1   |      at io.ktor.features.StatusPages$Feature$install$2.invokeSuspend(StatusPages.kt:142)
15:10:30 web.1   |      at io.ktor.features.StatusPages$Feature$install$2.invoke(StatusPages.kt)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
15:10:30 web.1   |      at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
15:10:30 web.1   |      at io.ktor.routing.Routing.executeResult(Routing.kt:151)
15:10:30 web.1   |      at io.ktor.routing.Routing.interceptor(Routing.kt:35)
15:10:30 web.1   |      at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:103)
15:10:30 web.1   |      at io.ktor.routing.Routing$Feature$install$1.invoke(Routing.kt)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
15:10:30 web.1   |      at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:110)
15:10:30 web.1   |      at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
15:10:30 web.1   |      at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
15:10:30 web.1   |      at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invokeSuspend(DefaultEnginePipeline.kt:118)
15:10:30 web.1   |      at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$2.invoke(DefaultEnginePipeline.kt)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:243)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:113)
15:10:30 web.1   |      at io.ktor.util.pipeline.SuspendFunctionGun.execute(SuspendFunctionGun.kt:133)
15:10:30 web.1   |      at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:77)
15:10:30 web.1   |      at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invokeSuspend(NettyApplicationCallHandler.kt:118)
15:10:30 web.1   |      at io.ktor.server.netty.NettyApplicationCallHandler$handleRequest$1.invoke(NettyApplicationCallHandler.kt)
15:10:30 web.1   |      at kotlinx.coroutines.intrinsics.UndispatchedKt.startCoroutineUndispatched(Undispatched.kt:55)
15:10:30 web.1   |      at kotlinx.coroutines.BuildersKt__Builders_commonKt.startCoroutineImpl(Builders.common.kt:182)
15:10:30 web.1   |      at kotlinx.coroutines.BuildersKt.startCoroutineImpl(Unknown Source)
15:10:30 web.1   |      at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:145)
15:10:30 web.1   |      at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:54)
15:10:30 web.1   |      at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
15:10:30 web.1   |      at io.ktor.server.netty.NettyApplicationCallHandler.handleRequest(NettyApplicationCallHandler.kt:43)
15:10:30 web.1   |      at io.ktor.server.netty.NettyApplicationCallHandler.channelRead(NettyApplicationCallHandler.kt:34)
15:10:30 web.1   |      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
15:10:30 web.1   |      at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:61)
15:10:30 web.1   |      at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:370)
15:10:30 web.1   |      at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
15:10:30 web.1   |      at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
15:10:30 web.1   |      at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
15:10:30 web.1   |      at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
15:10:30 web.1   |      at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
15:10:30 web.1   |      at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:216)
15:10:30 web.1   |      at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
15:10:30 web.1   |      at java.base/java.lang.Thread.run(Thread.java:832)

我以这种方式连接数据库:

KMongo.createClient("mongodb+srv://<username>:<password>@cluster0.euib1.mongodb.net/<dbname>?retryWrites=true&w=majority").coroutine

我的 Gradle 文件:

buildscript {
    repositories {
        jcenter()
    }
    
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
    }
}

apply plugin: 'kotlin'
apply plugin: 'application'

group 'com.example'
version '0.0.1'
mainClassName = 'com.example.ApplicationKt'

apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
    manifest {
        attributes 'Main-Class': mainClassName
    }
}

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
    maven { url 'https://kotlin.bintray.com/ktor' }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    implementation "ch.qos.logback:logback-classic:$logback_version"
    implementation "io.ktor:ktor-server-core:$ktor_version"
    implementation "io.ktor:ktor-server-host-common:$ktor_version"
    implementation "io.ktor:ktor-jackson:$ktor_version"
    implementation "io.ktor:ktor-locations:$ktor_version"
    implementation "io.ktor:ktor-metrics:$ktor_version"
    testImplementation "io.ktor:ktor-server-tests:$ktor_version"


    implementation 'org.litote.kmongo:kmongo-coroutine:3.10.0'
    implementation group: 'org.koin', name: 'koin-ktor', version: '2.0.0-beta-1'
    implementation group: 'org.koin', name: 'koin-core-ext', version: '2.0.0-beta-1'
    testImplementation group: 'org.koin', name: 'koin-test', version: '2.0.0-beta-1'
}

这是 ApplicationKt.kt:

fun main(args: Array<String>) {
    embeddedServer(Netty, commandLineEnvironment(args)).start(true)
}

@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
    install(CORS) {
        method(HttpMethod.Options)
        method(HttpMethod.Post)
        method(HttpMethod.Put)
        method(HttpMethod.Delete)
        method(HttpMethod.Patch)
        header(HttpHeaders.Authorization)
        header("MyCustomHeader")
        allowCredentials = true
        anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
    }

    install(ContentNegotiation) {
        jackson {
            enable(SerializationFeature.INDENT_OUTPUT)
        }
    }

    install(Locations) {}

    installKoin {
        modules(mongoModule)
    }

    routing {
        
        booksRoutes()

        install(StatusPages) {
            exception<AuthenticationException> { cause ->
                call.respond(HttpStatusCode.Unauthorized)
            }
            exception<AuthorizationException> { cause ->
                call.respond(HttpStatusCode.Forbidden)
            }

        }
    }
}

class AuthenticationException : RuntimeException()
class AuthorizationException : RuntimeException()

这些是路线:

fun Route.booksRoutes() {
    val logger: Logger = LoggerFactory.getLogger("BooksController")
    val client: CoroutineClient by inject()

    route("/books") {
        get("") {
            val books = client.getDatabase(DB_NAME)
                .getCollection<Book>(BOOKS_COLLECTION)
                .find()
                .toList()
            call.respond(HttpStatusCode.OK, books)
        }

        post<Book>("") { request ->
            logger.debug("$request")
            client.getDatabase(DB_NAME)
                    .getCollection<Book>(BOOKS_COLLECTION)
                    .insertOne(request)
            call.respond(HttpStatusCode.OK)
        }

        get("/{bookId}") {
            val bookId:String = call.parameters["bookId"]!!
            val book = client.getDatabase(DB_NAME)
                    .getCollection<Book>(BOOKS_COLLECTION)
                    .findOneById(bookId)
            if (book == null)
                call.respond(HttpStatusCode.NotFound)
            else
                call.respond(HttpStatusCode.OK, book)
        }
    }
}

这是我第一次使用 MongoDB,所以这可能是一个愚蠢的错误。但是无法理解问题是在我的代码中还是在 MongoDB Atlas 配置中。

4

1 回答 1

0

我在使用标准连接字符串连接到 mongo 云时也有类似的经历。最后,起作用的是在 atlas 云上创建一个 X.509 证书,然后使用它来连接一个连接字符串,如下所示:

mongodb+srv://mflix.beal2.mongodb.net/sample_mflix?**authMechanism=MONGODB-X509**&replicaSet=atlas-a7tqy4-shard-0&connectTimeoutMS=7000&maxPoolSize=50&wTimeoutMS=2500&readPreference=primary&**authSource=%24external**

您需要将 X.509 证书加载到 keystone 中

openssl pkcs12 -export -out mongodb.pkcs12 -in X509-cert.pem

然后确保您的进程可以访问密钥库:

javax.net.ssl.keyStore=./mongodb.pkcs12
javax.net.ssl.keyStorePassword=PWD

我在这篇文章中更详细地介绍了这一点:
mongo-loves-data.medium.com/kotlin-coroutines-and-ktor

于 2021-11-30T08:26:45.753 回答