我正在尝试使用 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 配置中。