commit c7689737fddf6f885ed30af975b8cacd6fa2ea29
parent 133c53fa27edfef038b743322d553e55837e0525
Author: Antoine A <>
Date: Tue, 28 Nov 2023 12:34:59 +0000
Remove unused code and dependencies, minify fat jar and improve logs
Diffstat:
9 files changed, 102 insertions(+), 267 deletions(-)
diff --git a/Makefile b/Makefile
@@ -90,9 +90,9 @@ assemble:
./gradlew assemble
.PHONY: check
-check: install-bank-files
+check: install-nobuild-bank-files
./gradlew check
.PHONY: test
-test: install-bank-files
+test: install-nobuild-bank-files
./gradlew test --tests $(test) -i
diff --git a/bank/build.gradle b/bank/build.gradle
@@ -34,20 +34,28 @@ dependencies {
implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
implementation("io.ktor:ktor-server-status-pages:$ktor_version")
implementation("io.ktor:ktor-server-netty:$ktor_version")
- implementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
+ // UNIX domain sockets support (used to connect to PostgreSQL)
+ implementation("com.kohlschutter.junixsocket:junixsocket-core:2.8.1")
+
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
+ testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
testImplementation(project(":util"))
-
- // UNIX domain sockets support (used to connect to PostgreSQL)
- implementation("com.kohlschutter.junixsocket:junixsocket-core:2.8.1")
}
application {
mainClass = "tech.libeufin.bank.MainKt"
applicationName = "libeufin-bank"
applicationDefaultJvmArgs = ['-Djava.net.preferIPv6Addresses=true']
+}
+
+shadowJar {
+ minimize {
+ exclude(dependency("io.ktor:ktor-serialization-kotlinx-json:.*"))
+ exclude(dependency("com.kohlschutter.junixsocket:junixsocket-core:.*"))
+ exclude(dependency("ch.qos.logback:logback-classic:.*"))
+ }
}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -71,10 +71,10 @@ data class ConversionRate (
val cashout_min_amount: TalerAmount,
)
-data class ServerConfig(
- val method: String,
- val port: Int
-)
+sealed class ServerConfig {
+ data class Unix(val path: String, val mode: Int): ServerConfig()
+ data class Tcp(val port: Int): ServerConfig()
+}
fun talerConfig(configPath: String?): TalerConfig = catchError {
val config = TalerConfig(BANK_CONFIG_SOURCE)
@@ -90,10 +90,12 @@ fun TalerConfig.loadDbConfig(): DatabaseConfig = catchError {
}
fun TalerConfig.loadServerConfig(): ServerConfig = catchError {
- ServerConfig(
- method = requireString("libeufin-bank", "serve"),
- port = requireNumber("libeufin-bank", "port")
- )
+ val method = requireString("libeufin-bank", "serve")
+ when (method) {
+ "tcp" -> ServerConfig.Tcp(requireNumber("libeufin-bank", "port"))
+ "unix" -> ServerConfig.Unix(requireString("libeufin-bank", "unixpath"), requireNumber("libeufin-bank", "unixpath_mode"))
+ else -> throw Exception("Unknown server method '$method' expected 'tcp' or 'unix'")
+ }
}
fun TalerConfig.loadBankConfig(): BankConfig = catchError {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
@@ -19,6 +19,9 @@
package tech.libeufin.bank
import io.ktor.http.*
+import io.ktor.server.response.*
+import io.ktor.server.application.ApplicationCall
+import io.ktor.util.AttributeKey
import kotlinx.serialization.Serializable
import net.taler.common.errorcodes.TalerErrorCode
import tech.libeufin.util.*
@@ -42,19 +45,49 @@ class LibeufinBankException(
*/
@Serializable
data class TalerError(
+ @kotlinx.serialization.Transient val err: TalerErrorCode = TalerErrorCode.END,
val code: Int,
val hint: String? = null,
val detail: String? = null
)
+private val LOG_MSG = AttributeKey<String>("log_msg");
-fun libeufinError(
+fun ApplicationCall.logMsg(): String? = attributes.getOrNull(LOG_MSG)
+
+suspend fun ApplicationCall.err(
status: HttpStatusCode,
hint: String?,
error: TalerErrorCode
+) {
+ err(
+ LibeufinBankException(
+ httpStatus = status, talerError = TalerError(
+ code = error.code, err = error, hint = hint
+ )
+ )
+ )
+}
+
+suspend fun ApplicationCall.err(
+ err: LibeufinBankException
+) {
+ attributes.put(LOG_MSG, "${err.talerError.err.name} ${err.talerError.hint}")
+ respond(
+ status = err.httpStatus,
+ message = err.talerError
+ )
+}
+
+
+fun libeufinError(
+ status: HttpStatusCode,
+ hint: String?,
+ error: TalerErrorCode,
+ detail: String? = null
): LibeufinBankException = LibeufinBankException(
httpStatus = status, talerError = TalerError(
- code = error.code, hint = hint
+ code = error.code, err = error, hint = hint, detail = detail
)
)
@@ -81,8 +114,10 @@ fun conflict(
): LibeufinBankException = libeufinError(HttpStatusCode.Conflict, hint, error)
fun badRequest(
- hint: String? = null, error: TalerErrorCode = TalerErrorCode.GENERIC_JSON_INVALID
-): LibeufinBankException = libeufinError(HttpStatusCode.BadRequest, hint, error)
+ hint: String? = null,
+ error: TalerErrorCode = TalerErrorCode.GENERIC_JSON_INVALID,
+ detail: String? = null
+): LibeufinBankException = libeufinError(HttpStatusCode.BadRequest, hint, error, detail)
fun BankConfig.checkRegionalCurrency(amount: TalerAmount) {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -112,7 +112,15 @@ fun Application.corebankWebApp(db: Database, ctx: BankConfig) {
this.level = Level.DEBUG
this.logger = tech.libeufin.bank.logger
this.format { call ->
- "${call.response.status()}, ${call.request.httpMethod.value} ${call.request.path()}"
+ val status = call.response.status()
+ val httpMethod = call.request.httpMethod.value
+ val path = call.request.path()
+ val msg = call.logMsg()
+ if (msg != null) {
+ "$status, $httpMethod $path, $msg"
+ } else {
+ "$status, $httpMethod $path"
+ }
}
}
install(CORS) {
@@ -169,54 +177,38 @@ fun Application.corebankWebApp(db: Database, ctx: BankConfig) {
else -> TalerErrorCode.GENERIC_JSON_INVALID
}
- call.respond(
- status = HttpStatusCode.BadRequest,
- message = TalerError(
- code = talerErrorCode.code,
- hint = cause.message,
- detail = rootCause?.message
+ call.err(
+ badRequest(
+ cause.message,
+ talerErrorCode,
+ rootCause?.message
)
)
}
exception<LibeufinBankException> { call, cause ->
- logger.error(cause.talerError.hint)
- call.respond(
- status = cause.httpStatus,
- message = cause.talerError
- )
+ call.err(cause)
}
exception<SQLException> { call, cause ->
- cause.printStackTrace()
- val err = when (cause.sqlState) {
- PSQLState.SERIALIZATION_FAILURE.state -> libeufinError(
+ when (cause.sqlState) {
+ PSQLState.SERIALIZATION_FAILURE.state -> call.err(
HttpStatusCode.InternalServerError,
"Transaction serialization failure",
TalerErrorCode.BANK_SOFT_EXCEPTION
)
- else -> libeufinError(
+ else -> call.err(
HttpStatusCode.InternalServerError,
"Unexpected sql error with state ${cause.sqlState}",
TalerErrorCode.BANK_UNMANAGED_EXCEPTION
)
}
- logger.error(err.talerError.hint)
- call.respond(
- status = err.httpStatus,
- message = err.talerError
- )
}
// Catch-all branch to mean that the bank wasn't able to manage one error.
exception<Exception> { call, cause ->
- val err = libeufinError(
+ call.err(
HttpStatusCode.InternalServerError,
cause.message,
TalerErrorCode.BANK_UNMANAGED_EXCEPTION
)
- logger.error(err.talerError.hint)
- call.respond(
- status = err.httpStatus,
- message = err.talerError
- )
}
}
routing {
@@ -331,10 +323,6 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
val ctx = cfg.loadBankConfig()
val dbCfg = cfg.loadDbConfig()
val serverCfg = cfg.loadServerConfig()
- if (serverCfg.method.lowercase() != "tcp") {
- logger.error("Can only serve libeufin-bank via TCP")
- exitProcess(1)
- }
val db = Database(dbCfg.dbConnStr, ctx.regionalCurrency, ctx.fiatCurrency)
runBlocking {
if (ctx.allowConversion) {
@@ -364,13 +352,28 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
db.conn { it.execSQLUpdate(sqlProcedures.readText()) }
// Remove conversion info from the database ?
}
- }
- embeddedServer(Netty, port = serverCfg.port) {
- corebankWebApp(db, ctx)
- }.start(wait = true)
+ }
+
+ val env = applicationEngineEnvironment {
+ connector {
+ when (serverCfg) {
+ is ServerConfig.Tcp -> {
+ port = serverCfg.port
+ }
+ is ServerConfig.Unix -> {
+ logger.error("Can only serve libeufin-bank via TCP")
+ exitProcess(1)
+ }
+ }
+ }
+ module { corebankWebApp(db, ctx) }
+ }
+ embeddedServer(Netty, env).start(wait = true)
}
}
+
+
class ChangePw : CliktCommand("Change account password", name = "passwd") {
private val configFile by option(
"--config", "-c",
diff --git a/util/build.gradle b/util/build.gradle
@@ -19,21 +19,17 @@ sourceSets.main.java.srcDirs = ["src/main/kotlin"]
dependencies {
implementation("ch.qos.logback:logback-classic:1.4.5")
- implementation("io.ktor:ktor-server-netty:$ktor_version")
// XML Stuff
implementation("javax.xml.bind:jaxb-api:2.3.1")
implementation("org.glassfish.jaxb:jaxb-runtime:2.3.1")
implementation("org.apache.santuario:xmlsec:2.2.2")
// Crypto
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
- // Unix domain socket to serve HTTP
- implementation("io.netty:netty-all:$netty_version")
- implementation("io.netty:netty-transport-native-epoll:$netty_version")
- implementation("io.ktor:ktor-server-test-host:$ktor_version")
// Database helper
implementation("org.postgresql:postgresql:42.6.0")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
+ implementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
implementation("com.github.ajalt.clikt:clikt:4.2.1")
diff --git a/util/src/main/kotlin/UnixDomainSocket.kt b/util/src/main/kotlin/UnixDomainSocket.kt
@@ -1,98 +0,0 @@
-import io.ktor.client.plugins.*
-import io.ktor.client.request.*
-import io.ktor.server.application.*
-import io.ktor.client.statement.*
-import io.ktor.http.HttpHeaders
-import io.ktor.http.HttpMethod
-import io.ktor.server.testing.*
-import io.netty.bootstrap.ServerBootstrap
-import io.netty.buffer.ByteBufInputStream
-import io.netty.buffer.Unpooled
-import io.netty.channel.*
-import io.netty.channel.epoll.EpollEventLoopGroup
-import io.netty.channel.epoll.EpollServerDomainSocketChannel
-import io.netty.channel.unix.DomainSocketAddress
-import io.netty.handler.codec.http.*
-import io.netty.handler.codec.http.DefaultHttpResponse
-import io.netty.handler.logging.LoggingHandler
-import io.netty.handler.stream.ChunkedStream
-import io.netty.handler.stream.ChunkedWriteHandler
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import java.io.ByteArrayInputStream
-
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.UnixDomainSocket")
-
-fun startServer(
- unixSocketPath: String,
- app: Application.() -> Unit
-) {
- val boss = EpollEventLoopGroup()
- val worker = EpollEventLoopGroup()
- try {
- val serverBootstrap = ServerBootstrap()
- serverBootstrap.group(boss, worker).channel(
- EpollServerDomainSocketChannel::class.java
- ).childHandler(LibeufinHttpInit(app))
- val socketPath = DomainSocketAddress(unixSocketPath)
- logger.debug("Listening on $unixSocketPath ..")
- serverBootstrap.bind(socketPath).sync().channel().closeFuture().sync()
- } finally {
- boss.shutdownGracefully()
- worker.shutdownGracefully()
- }
-}
-
-class LibeufinHttpInit(
- private val app: Application.() -> Unit
-) : ChannelInitializer<Channel>() {
- override fun initChannel(ch: Channel) {
- ch.pipeline(
- ).addLast(LoggingHandler("tech.libeufin.dev")
- ).addLast(HttpServerCodec() // in- and out- bound
- ).addLast(HttpObjectAggregator(Int.MAX_VALUE) // only in- bound
- ).addLast(ChunkedWriteHandler()
- ).addLast(LibeufinHttpHandler(app)) // in- bound, and triggers out- bound.
- }
-}
-
-class LibeufinHttpHandler(
- private val app: Application.() -> Unit
-) : SimpleChannelInboundHandler<FullHttpRequest>() {
- // @OptIn(EngineAPI::class)
- override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) {
- testApplication {
- application(app)
- val httpVersion = msg.protocolVersion()
- // Proxying the request to Ktor API.
- val r = client.request(msg.uri()) {
- expectSuccess = false
- method = HttpMethod(msg.method().name())
- setBody(ByteBufInputStream(msg.content()).readAllBytes())
- }
- // Responding to Netty API.
- val response = DefaultHttpResponse(
- httpVersion,
- HttpResponseStatus.valueOf(r.status.value)
- )
- var chunked = false
- r.headers.forEach { s, list ->
- if (s == HttpHeaders.TransferEncoding && list.contains("chunked"))
- chunked = true
- response.headers().set(s, list.joinToString())
- }
- ctx.writeAndFlush(response)
- if (chunked) {
- ctx.writeAndFlush(
- HttpChunkedInput(
- ChunkedStream(
- ByteArrayInputStream(r.readBytes())
- )
- )
- )
- } else {
- ctx.writeAndFlush(Unpooled.wrappedBuffer(r.readBytes()))
- }
- }
- }
-}
-\ No newline at end of file
diff --git a/util/src/main/kotlin/startServer.kt b/util/src/main/kotlin/startServer.kt
@@ -1,85 +0,0 @@
-package tech.libeufin.util
-
-import io.ktor.server.application.*
-import io.ktor.server.engine.*
-import io.ktor.server.netty.*
-import io.netty.channel.unix.Errors
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import kotlin.system.exitProcess
-
-private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.util.startServer")
-
-const val EAFNOSUPPORT = -97 // Netty defines errors negatively.
-class StartServerOptions(
- var ipv4OnlyOpt: Boolean,
- val localhostOnlyOpt: Boolean,
- val portOpt: Int
-)
-
-// Core function starting the server.
-private fun serverMain(options: StartServerOptions, app: Application.() -> Unit) {
- val server = embeddedServer(
- Netty,
- environment = applicationEngineEnvironment {
- connector {
- this.port = options.portOpt
- this.host = if (options.localhostOnlyOpt) "127.0.0.1" else "0.0.0.0"
- }
- if (!options.ipv4OnlyOpt) connector {
- this.port = options.portOpt
- this.host = if (options.localhostOnlyOpt) "[::1]" else "[::]"
- }
- module(app)
- },
- // Maybe remove this? Was introduced
- // to debug concurrency issues..
- configure = {
- connectionGroupSize = 1
- workerGroupSize = 1
- callGroupSize = 1
- }
- )
- /**
- * NOTE: excepted server still need the stop(), otherwise
- * it leaves the port locked and prevents the IPv4 retry.
- */
- try {
- server.start(wait = true)
- } catch (e: Exception) {
- server.stop()
- logger.debug("Rethrowing: ${e.message}")
- throw e // Rethrowing for retry policies.
- }
-}
-
-// Wrapper function that retries when IPv6 fails.
-fun startServerWithIPv4Fallback(
- options: StartServerOptions,
- app: Application.() -> Unit
-) {
- var maybeRetry = false
- try {
- serverMain(options, app)
- } catch (e: Exception) {
- logger.warn(e.message)
- // Find reasons to retry.
- if (e is Errors.NativeIoException) {
- logger.debug("errno: ${e.expectedErr()}")
- if ((e.expectedErr() == EAFNOSUPPORT) && (!options.ipv4OnlyOpt))
- maybeRetry = true
- }
- }
- // Fail, if no retry policy applies. The catch block above logged the error.
- if (!maybeRetry) {
- exitProcess(1)
- }
- logger.info("Retrying to start the server on IPv4")
- options.ipv4OnlyOpt = true
- try {
- serverMain(options, app)
- } catch (e: Exception) {
- logger.error(e.message)
- exitProcess(1)
- }
-}
-\ No newline at end of file
diff --git a/util/src/test/kotlin/DomainSocketTest.kt b/util/src/test/kotlin/DomainSocketTest.kt
@@ -1,23 +0,0 @@
-import io.ktor.server.application.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-import org.junit.Test
-import org.junit.Ignore
-
-class DomainSocketTest {
- @Test @Ignore
- fun bind() {
- startServer("/tmp/java.sock") {
- routing {
- get("/") {
- this.call.respond(object {})
- return@get
- }
- post("/") {
- this.call.respond(object {})
- return@post
- }
- }
- }
- }
-}
-\ No newline at end of file