summaryrefslogtreecommitdiff
path: root/nexus/src/main/kotlin/tech/libeufin/nexus/server
diff options
context:
space:
mode:
authorMS <ms@taler.net>2022-12-30 19:37:36 +0100
committerMS <ms@taler.net>2022-12-30 19:37:36 +0100
commitce74df3f515cc5caec39381c00645714f3d7f5ba (patch)
tree740ef9b1a557d44a459b5b2c8136f37f31610e8b /nexus/src/main/kotlin/tech/libeufin/nexus/server
parentfd4889f4a19a7993e65af6d9bd6b0231604534a6 (diff)
downloadlibeufin-ce74df3f515cc5caec39381c00645714f3d7f5ba.tar.gz
libeufin-ce74df3f515cc5caec39381c00645714f3d7f5ba.tar.bz2
libeufin-ce74df3f515cc5caec39381c00645714f3d7f5ba.zip
Use Ktor 2.2.1 and general polishing.
Diffstat (limited to 'nexus/src/main/kotlin/tech/libeufin/nexus/server')
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt79
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt47
2 files changed, 59 insertions, 67 deletions
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
index 8c4e6dbe..b39c72ec 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt
@@ -20,6 +20,8 @@
package tech.libeufin.nexus.server
import UtilError
+import io.ktor.serialization.jackson.*
+import io.ktor.server.plugins.contentnegotiation.*
import com.fasterxml.jackson.core.util.DefaultIndenter
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
import com.fasterxml.jackson.databind.JsonNode
@@ -28,20 +30,19 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.exc.MismatchedInputException
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
-import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
-import io.ktor.application.*
+import com.fasterxml.jackson.module.kotlin.*
import io.ktor.client.*
-import io.ktor.features.*
import io.ktor.http.*
-import io.ktor.jackson.*
import io.ktor.network.sockets.*
-import io.ktor.request.*
-import io.ktor.response.*
-import io.ktor.routing.*
+import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
+import io.ktor.server.plugins.*
+import io.ktor.server.plugins.callloging.*
+import io.ktor.server.plugins.statuspages.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
import io.ktor.util.*
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.and
@@ -51,11 +52,12 @@ import tech.libeufin.nexus.*
import tech.libeufin.nexus.bankaccount.*
import tech.libeufin.nexus.ebics.*
import tech.libeufin.nexus.iso20022.CamtBankAccountEntry
+import tech.libeufin.sandbox.SandboxErrorDetailJson
+import tech.libeufin.sandbox.SandboxErrorJson
import tech.libeufin.util.*
import java.net.BindException
import java.net.URLEncoder
import kotlin.system.exitProcess
-import java.net.URL
/**
* Return facade state depending on the type.
@@ -121,18 +123,6 @@ fun ApplicationCall.expectUrlParameter(name: String): String {
?: throw NexusError(HttpStatusCode.BadRequest, "Parameter '$name' not provided in URI")
}
-suspend inline fun <reified T : Any> ApplicationCall.receiveJson(): T {
- try {
- return this.receive()
- } catch (e: MissingKotlinParameterException) {
- throw NexusError(HttpStatusCode.BadRequest, "Missing value for ${e.pathReference}")
- } catch (e: MismatchedInputException) {
- throw NexusError(HttpStatusCode.BadRequest, "Invalid value for '${e.pathReference}'")
- } catch (e: JsonParseException) {
- throw NexusError(HttpStatusCode.BadRequest, "Invalid JSON")
- }
-}
-
fun requireBankConnectionInternal(connId: String): NexusBankConnectionEntity {
return transaction {
NexusBankConnectionEntity.find { NexusBankConnectionsTable.connectionId eq connId }.firstOrNull()
@@ -157,6 +147,7 @@ val nexusApp: Application.() -> Unit = {
this.level = Level.DEBUG
this.logger = tech.libeufin.nexus.logger
}
+ install(LibeufinDecompressionPlugin)
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
@@ -164,12 +155,21 @@ val nexusApp: Application.() -> Unit = {
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
indentObjectsWith(DefaultIndenter(" ", "\n"))
})
- registerModule(KotlinModule(nullisSameAsDefault = true))
+ registerModule(
+ KotlinModule.Builder()
+ .withReflectionCacheSize(512)
+ .configure(KotlinFeature.NullToEmptyCollection, false)
+ .configure(KotlinFeature.NullToEmptyMap, false)
+ .configure(KotlinFeature.NullIsSameAsDefault, enabled = true)
+ .configure(KotlinFeature.SingletonSupport, enabled = false)
+ .configure(KotlinFeature.StrictNullChecks, false)
+ .build()
+ )
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
}
install(StatusPages) {
- exception<NexusError> { cause ->
+ exception<NexusError> { call, cause ->
logger.error("Caught exception while handling '${call.request.uri} (${cause.reason})")
call.respond(
status = cause.statusCode,
@@ -180,7 +180,7 @@ val nexusApp: Application.() -> Unit = {
)
)
}
- exception<JsonMappingException> { cause ->
+ exception<JsonMappingException> { call, cause ->
logger.error("Exception while handling '${call.request.uri}'", cause)
call.respond(
HttpStatusCode.BadRequest,
@@ -191,7 +191,7 @@ val nexusApp: Application.() -> Unit = {
)
)
}
- exception<UtilError> { cause ->
+ exception<UtilError> { call, cause ->
logger.error("Exception while handling '${call.request.uri}'", cause)
call.respond(
cause.statusCode,
@@ -202,7 +202,7 @@ val nexusApp: Application.() -> Unit = {
)
)
}
- exception<EbicsProtocolError> { cause ->
+ exception<EbicsProtocolError> { call, cause ->
logger.error("Caught exception while handling '${call.request.uri}' (${cause.reason})")
call.respond(
cause.httpStatusCode,
@@ -213,7 +213,19 @@ val nexusApp: Application.() -> Unit = {
)
)
}
- exception<Exception> { cause ->
+ exception<BadRequestException> { call, cause ->
+ tech.libeufin.sandbox.logger.error("Exception while handling '${call.request.uri}', ${cause.message}")
+ call.respond(
+ HttpStatusCode.BadRequest,
+ SandboxErrorJson(
+ error = SandboxErrorDetailJson(
+ type = "util-error",
+ description = cause.message ?: "Bad request but did not find exact cause."
+ )
+ )
+ )
+ }
+ exception<Exception> { call, cause ->
logger.error("Uncaught exception while handling '${call.request.uri}'")
cause.printStackTrace()
call.respond(
@@ -226,7 +238,6 @@ val nexusApp: Application.() -> Unit = {
)
}
}
- install(RequestBodyDecompression)
intercept(ApplicationCallPipeline.Fallback) {
if (this.call.response.status() == null) {
call.respondText("Not found (no route matched).\n", ContentType.Text.Plain, HttpStatusCode.NotFound)
@@ -332,7 +343,7 @@ val nexusApp: Application.() -> Unit = {
// change a user's password
post("/users/{username}/password") {
- val body = call.receiveJson<ChangeUserPassword>()
+ val body = call.receive<ChangeUserPassword>()
val targetUsername = ensureNonNull(call.parameters["username"])
transaction {
requireSuperuser(call.request)
@@ -351,7 +362,7 @@ val nexusApp: Application.() -> Unit = {
// Add a new ordinary user in the system (requires superuser privileges)
post("/users") {
- val body = call.receiveJson<CreateUserRequest>()
+ val body = call.receive<CreateUserRequest>()
val requestedUsername = requireValidResourceName(body.username)
transaction {
requireSuperuser(call.request)
@@ -896,7 +907,7 @@ val nexusApp: Application.() -> Unit = {
name = f.facadeName,
type = f.type,
baseUrl = URLBuilder(call.request.getBaseUrl()).apply {
- pathComponents("facades", f.facadeName, f.type)
+ this.appendPathSegments(listOf("facades", f.facadeName, f.type))
encodedPath += "/"
}.buildString(),
config = getFacadeState(f.type, f)
@@ -921,7 +932,7 @@ val nexusApp: Application.() -> Unit = {
name = it.facadeName,
type = it.type,
baseUrl = URLBuilder(call.request.getBaseUrl()).apply {
- pathComponents("facades", it.facadeName, it.type)
+ this.appendPathSegments(listOf("facades", it.facadeName, it.type))
encodedPath += "/"
}.buildString(),
config = getFacadeState(it.type, it)
@@ -1041,7 +1052,7 @@ val nexusApp: Application.() -> Unit = {
talerFacadeRoutes(this)
}
route("/facades/{fcid}/anastasis") {
- anastasisFacadeRoutes(this, client)
+ anastasisFacadeRoutes(this)
}
// Hello endpoint.
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt
index 76a71414..0eb5f57d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/server/RequestBodyDecompression.kt
@@ -19,9 +19,9 @@
package tech.libeufin.nexus.server
-import io.ktor.application.*
-import io.ktor.features.*
-import io.ktor.request.*
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.request.*
import io.ktor.util.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
@@ -30,37 +30,18 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.zip.InflaterInputStream
-/**
- * Decompress request bodies.
- */
-class RequestBodyDecompression private constructor() {
- companion object Feature :
- ApplicationFeature<Application, RequestBodyDecompression.Configuration, RequestBodyDecompression> {
- override val key: AttributeKey<RequestBodyDecompression> = AttributeKey("Request Body Decompression")
- override fun install(
- pipeline: Application,
- configure: RequestBodyDecompression.Configuration.() -> Unit
- ): RequestBodyDecompression {
- pipeline.receivePipeline.intercept(ApplicationReceivePipeline.Before) {
- if (this.context.request.headers["Content-Encoding"] == "deflate") {
- val deflated = this.subject.value as ByteReadChannel
- val brc = withContext(Dispatchers.IO) {
- val inflated = InflaterInputStream(deflated.toInputStream())
- // False positive in current Kotlin version, we're already in Dispatchers.IO!
- @Suppress("BlockingMethodInNonBlockingContext") val bytes = inflated.readAllBytes()
- ByteReadChannel(bytes)
- }
- proceedWith(ApplicationReceiveRequest(this.subject.typeInfo, brc))
- return@intercept
+val LibeufinDecompressionPlugin = createApplicationPlugin("RequestingBodyDecompression") {
+ onCallReceive { call ->
+ transformBody { data ->
+ if (call.request.headers[HttpHeaders.ContentEncoding] == "deflate") {
+ val brc = withContext(Dispatchers.IO) {
+ val inflated = InflaterInputStream(data.toInputStream())
+ @Suppress("BlockingMethodInNonBlockingContext")
+ val bytes = inflated.readAllBytes()
+ ByteReadChannel(bytes)
}
- proceed()
- return@intercept
- }
- return RequestBodyDecompression()
+ brc
+ } else data
}
}
-
- class Configuration {
-
- }
} \ No newline at end of file