Skip to content

Commit

Permalink
Merge pull request #89 from navikt/oppgradering-ktor3-bekreftelse-api
Browse files Browse the repository at this point in the history
oppgraderer bekreftelse-api til ktor3
  • Loading branch information
robertkittilsen authored Dec 17, 2024
2 parents ae0975a + a6f26cf commit 5eb6e2d
Show file tree
Hide file tree
Showing 45 changed files with 1,150 additions and 57 deletions.
36 changes: 18 additions & 18 deletions apps/bekreftelse-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,40 @@ val image: String? by project
dependencies {
// Project
implementation(project(":lib:hoplite-config"))
implementation(project(":lib:error-handling"))
implementation(project(":lib:error-handling-ktor3"))
implementation(project(":lib:security"))
implementation(project(":lib:kafka-streams"))
implementation(project(":lib:kafka-key-generator-client"))
implementation(project(":lib:kafka-key-generator-client-ktor3"))
implementation(project(":domain:bekreftelse-interne-hendelser"))
implementation(project(":domain:bekreftelsesmelding-avro-schema"))

// Server
implementation(libs.bundles.ktorServerWithNettyAndMicrometer)
implementation(libs.ktor.server.contentNegotiation)
implementation(libs.ktor.server.statusPages)
implementation(libs.ktor.server.cors)
implementation(libs.ktor.server.callId)
implementation(libs.ktor.server.auth)
implementation(libs.bundles.ktor3ServerWithNettyAndMicrometer)
implementation(libs.ktor3.server.contentNegotiation)
implementation(libs.ktor3.server.statusPages)
implementation(libs.ktor3.server.cors)
implementation(libs.ktor3.server.callId)
implementation(libs.ktor3.server.auth)

// Client
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.contentNegotiation)
implementation(libs.ktor3.client.core)
implementation(libs.ktor3.client.cio)
implementation(libs.ktor3.client.contentNegotiation)

// Serialization
implementation(libs.ktor.serialization.jackson)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor3.serialization.jackson)
implementation(libs.ktor3.serialization.kotlinx.json)
implementation(libs.jackson.datatypeJsr310)

// Authentication
implementation(libs.nav.security.tokenValidationKtorV2)
implementation(libs.nav.security.tokenValidationKtorV3)

// Authorization
implementation(libs.nav.poao.tilgangClient)

// Documentation
implementation(libs.ktor.server.openapi)
implementation(libs.ktor.server.swagger)
implementation(libs.ktor3.server.openapi)
implementation(libs.ktor3.server.swagger)

// Logging
implementation(libs.logbackClassic)
Expand All @@ -71,8 +71,8 @@ dependencies {
implementation(libs.avro.kafkaStreamsSerde)

// Test
testImplementation(libs.ktor.server.testJvm)
testImplementation(libs.ktor.client.mock)
testImplementation(libs.ktor3.server.test.host)
testImplementation(libs.ktor3.client.mock)
testImplementation(libs.bundles.testLibsWithUnitTesting)
testImplementation(libs.test.mockOauth2Server)
testImplementation(libs.test.testContainers.postgresql)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package no.nav.paw.bekreftelse.api.plugins
import io.ktor.server.application.Application
import io.ktor.server.auth.authentication
import no.nav.paw.bekreftelse.api.context.ApplicationContext
import no.nav.security.token.support.v2.IssuerConfig
import no.nav.security.token.support.v2.RequiredClaims
import no.nav.security.token.support.v2.TokenSupportConfig
import no.nav.security.token.support.v2.tokenValidationSupport
import no.nav.security.token.support.v3.IssuerConfig
import no.nav.security.token.support.v3.RequiredClaims
import no.nav.security.token.support.v3.TokenSupportConfig
import no.nav.security.token.support.v3.tokenValidationSupport

fun Application.configureAuthentication(applicationContext: ApplicationContext) {
with(applicationContext.securityConfig) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package no.nav.paw.bekreftelse.api.plugins

import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.callloging.CallLogging
import io.ktor.server.plugins.calllogging.CallLogging
import io.ktor.server.request.path

fun Application.configureLogging() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.ktor.server.application.ApplicationStopping
import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.MonitoringEvent
import io.ktor.server.application.log
import io.ktor.util.KtorDsl
import io.ktor.utils.io.KtorDsl
import org.jetbrains.exposed.sql.Database
import javax.sql.DataSource

Expand All @@ -31,7 +31,7 @@ val DataSourcePlugin: ApplicationPlugin<DataSourcePluginConfig> =
on(MonitoringEvent(ApplicationStarted)) { application ->
application.log.info("Initializing data source")
Database.connect(dataSource)
application.environment.monitor.raise(DataSourceReady, application)
application.monitor.raise(DataSourceReady, application)
}

on(MonitoringEvent(ApplicationStopping)) { application ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.ktor.server.application.ApplicationPlugin
import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.MonitoringEvent
import io.ktor.server.application.log
import io.ktor.util.KtorDsl
import io.ktor.utils.io.KtorDsl
import org.flywaydb.core.Flyway
import javax.sql.DataSource

Expand Down Expand Up @@ -36,6 +36,6 @@ val FlywayPlugin: ApplicationPlugin<FlywayPluginConfig> =
on(MonitoringEvent(DataSourceReady)) { application ->
application.log.info("Running database migration")
flyway.migrate()
application.environment.monitor.raise(FlywayMigrationCompleted, application)
application.monitor.raise(FlywayMigrationCompleted, application)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.ktor.server.application.ApplicationStopping
import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.MonitoringEvent
import io.ktor.server.application.log
import io.ktor.util.KtorDsl
import io.ktor.utils.io.KtorDsl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.ktor.server.application.ApplicationStopping
import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.MonitoringEvent
import io.ktor.server.application.log
import io.ktor.util.KtorDsl
import io.ktor.utils.io.KtorDsl
import org.apache.kafka.clients.producer.Producer
import java.time.Duration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.MonitoringEvent
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.util.KtorDsl
import io.ktor.utils.io.KtorDsl
import no.nav.paw.bekreftelse.api.models.BekreftelseRow
import no.nav.paw.bekreftelse.api.plugins.custom.FlywayMigrationCompleted
import no.nav.paw.bekreftelse.api.repository.BekreftelseRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,8 @@ import no.nav.paw.health.model.LivenessHealthIndicator
import no.nav.paw.health.model.ReadinessHealthIndicator
import no.nav.paw.health.repository.HealthIndicatorRepository
import no.nav.paw.kafkakeygenerator.client.KafkaKeysClient
import no.nav.paw.security.authentication.config.AuthProvider
import no.nav.paw.security.authentication.config.AuthProviderClaims
import no.nav.paw.security.authentication.config.SECURITY_CONFIG
import no.nav.paw.security.authentication.config.SecurityConfig
import no.nav.paw.security.authentication.token.AzureAd
import no.nav.paw.security.authentication.token.IdPorten
import no.nav.paw.security.authentication.token.TokenX
import no.nav.poao_tilgang.client.PoaoTilgangClient
import no.nav.security.mock.oauth2.MockOAuth2Server
import org.apache.kafka.clients.consumer.KafkaConsumer
Expand Down
26 changes: 26 additions & 0 deletions lib/error-handling-ktor3/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
kotlin("jvm")
}

dependencies {
compileOnly(libs.ktor3.server.cors)
compileOnly(libs.ktor3.serialization.jackson)
compileOnly(libs.kafka.streams.core)
compileOnly(libs.logbackClassic)

//Test
testImplementation(libs.bundles.testLibsWithUnitTesting)
testImplementation(libs.ktor3.server.test.host)
testImplementation(libs.ktor3.server.contentNegotiation)
testImplementation(libs.ktor3.server.statusPages)
testImplementation(libs.ktor3.serialization.jackson)
testImplementation(libs.ktor3.client.contentNegotiation)
testImplementation(libs.ktor3.server.core)
testImplementation(libs.kafka.streams.core)
testImplementation(libs.jackson.datatypeJsr310)
testImplementation(libs.logbackClassic)
}

tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package no.nav.paw.error.exception

import io.ktor.http.HttpStatusCode
import java.net.URI

open class ClientResponseException(
val status: HttpStatusCode,
override val type: URI,
override val message: String,
override val cause: Throwable? = null
) : ErrorTypeAwareException(type, message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package no.nav.paw.error.exception

import java.net.URI

open class ErrorTypeAwareException(
open val type: URI,
override val message: String,
override val cause: Throwable? = null
) :
Exception(message, cause) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package no.nav.paw.error.exception

import no.nav.paw.error.model.ProblemDetails

open class ProblemDetailsException(val details: ProblemDetails): ErrorTypeAwareException(
type = details.type,
message = details.title
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package no.nav.paw.error.exception

import io.ktor.http.HttpStatusCode
import java.net.URI

open class ServerResponseException(
val status: HttpStatusCode,
override val type: URI,
override val message: String,
override val cause: Throwable? = null
) : ErrorTypeAwareException(type, message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package no.nav.paw.error.handler

import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.plugins.BadRequestException
import io.ktor.server.plugins.ContentTransformationException
import io.ktor.server.request.ApplicationRequest
import io.ktor.server.request.RequestAlreadyConsumedException
import io.ktor.server.request.uri
import io.ktor.server.response.respond
import no.nav.paw.error.exception.ClientResponseException
import no.nav.paw.error.exception.ServerResponseException
import no.nav.paw.error.model.ErrorType
import no.nav.paw.error.model.ProblemDetails
import no.nav.paw.error.model.ProblemDetailsBuilder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.MDC

private val logger: Logger = LoggerFactory.getLogger("no.nav.paw.logger.error.http")
private const val MDC_ERROR_ID_KEY = "x_error_id"
private const val MDC_ERROR_TYPE_KEY = "x_error_type"
private const val MDC_EXCEPTION_KEY = "exception"

suspend fun ApplicationCall.handleException(
throwable: Throwable,
resolver: (throwable: Throwable) -> ProblemDetails? = { null }
) {
val problemDetails = resolveProblemDetails(request, throwable, resolver)

MDC.put(MDC_ERROR_ID_KEY, problemDetails.id.toString())
MDC.put(MDC_ERROR_TYPE_KEY, problemDetails.type.toString())
MDC.put(MDC_EXCEPTION_KEY, throwable.javaClass.canonicalName)

logger.error(problemDetails.detail, throwable)

MDC.remove(MDC_ERROR_ID_KEY)
MDC.remove(MDC_ERROR_TYPE_KEY)
MDC.remove(MDC_EXCEPTION_KEY)

respond(problemDetails.status, problemDetails)
}

fun resolveProblemDetails(
request: ApplicationRequest,
throwable: Throwable,
resolver: (throwable: Throwable) -> ProblemDetails? = { null }
): ProblemDetails {
val problemDetails = resolver(throwable)
if (problemDetails != null) {
return problemDetails
}

when (throwable) {
is BadRequestException -> {
return ProblemDetailsBuilder.builder()
.type(ErrorType.domain("http").error("kunne-ikke-tolke-forespoersel").build())
.status(HttpStatusCode.BadRequest)
.detail("Kunne ikke tolke forespørsel")
.instance(request.uri)
.build()
}

is ContentTransformationException -> {
return ProblemDetailsBuilder.builder()
.type(ErrorType.domain("http").error("kunne-ikke-tolke-innhold").build())
.status(HttpStatusCode.BadRequest)
.detail("Kunne ikke tolke innhold i forespørsel")
.instance(request.uri)
.build()
}

is RequestAlreadyConsumedException -> {
return ProblemDetailsBuilder.builder()
.type(ErrorType.domain("http").error("forespoersel-allerede-mottatt").build())
.status(HttpStatusCode.InternalServerError)
.detail("Forespørsel er allerede mottatt. Dette er en kodefeil")
.instance(request.uri)
.build()
}

is ServerResponseException -> {
return ProblemDetailsBuilder.builder()
.type(throwable.type)
.status(throwable.status)
.detail(throwable.message)
.instance(request.uri)
.build()
}

is ClientResponseException -> {
return ProblemDetailsBuilder.builder()
.type(throwable.type)
.status(throwable.status)
.detail(throwable.message)
.instance(request.uri)
.build()
}

else -> {
return ProblemDetailsBuilder.builder()
.detail("Forespørsel feilet med ukjent feil")
.instance(request.uri)
.build()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package no.nav.paw.error.handler

import org.apache.kafka.streams.KafkaStreams
import org.apache.kafka.streams.errors.StreamsUncaughtExceptionHandler
import org.slf4j.Logger
import org.slf4j.LoggerFactory

private val logger: Logger = LoggerFactory.getLogger("no.nav.paw.logger.error.kafka")

fun KafkaStreams.withApplicationTerminatingExceptionHandler() {
this.setUncaughtExceptionHandler(createApplicationTerminatingExceptionHandler())
}

fun createApplicationTerminatingExceptionHandler() = StreamsUncaughtExceptionHandler { throwable ->
logger.error("Kafka Streams opplevde en uventet feil", throwable)
StreamsUncaughtExceptionHandler.StreamThreadExceptionResponse.SHUTDOWN_APPLICATION
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package no.nav.paw.error.model

import java.net.URI

object ErrorTypeDefaults {
const val TEAM = "paw"
const val DOMAIN = "default"
const val ERROR = "ukjent-feil"
}

class ErrorType(
var team: String = ErrorTypeDefaults.TEAM,
var domain: String = ErrorTypeDefaults.DOMAIN,
var error: String = ErrorTypeDefaults.ERROR,
) {
fun team(team: String) = apply { this.team = team }
fun domain(domain: String) = apply { this.domain = domain }
fun error(error: String) = apply { this.error = error }
fun build(): URI = URI.create("urn:${team.lowercase()}:${domain.lowercase()}:${error.lowercase()}")

companion object {
fun team(team: String): ErrorType = ErrorType(team = team)
fun domain(domain: String): ErrorType = ErrorType(domain = domain)
fun error(error: String): ErrorType = ErrorType(error = error)
fun default(): ErrorType = ErrorType()
}
}
Loading

0 comments on commit 5eb6e2d

Please sign in to comment.