Skip to content

Commit

Permalink
Merge branch 'main' into renovate/tomcat.jakarta
Browse files Browse the repository at this point in the history
  • Loading branch information
e5l authored Feb 12, 2025
2 parents 642748f + 7f0979a commit b20edcc
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 78 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ kotlin.code.style=official

# config
group=io.ktor
version=3.1.0-SNAPSHOT
version=3.1.0

## Performance

Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

kotlin = "2.1.10"
kotlinx-html = "0.12.0"
kotlinx-datetime = "0.6.1"
kotlinx-datetime = "0.6.2"
kotlinx-io = "0.6.0"
coroutines = "1.10.1"
atomicfu = "0.27.0"
Expand Down Expand Up @@ -54,7 +54,7 @@ java-jwt = "4.4.0"
jwks-rsa = "0.22.1"
mustache = "0.9.14"
freemarker = "2.3.34"
pebble = "3.2.2"
pebble = "3.2.3"
velocity = "2.4.1"
velocity-tools = "3.1"
webjars-locator = "0.59"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public final class io/ktor/client/plugins/auth/providers/BasicAuthProvider : io/
public fun <init> (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun addRequestHeaders (Lio/ktor/client/request/HttpRequestBuilder;Lio/ktor/http/auth/HttpAuthHeader;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun clearToken ()V
public fun getSendWithoutRequest ()Z
public fun isApplicable (Lio/ktor/http/auth/HttpAuthHeader;)Z
public fun refreshToken (Lio/ktor/client/statement/HttpResponse;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down Expand Up @@ -120,6 +121,7 @@ public final class io/ktor/client/plugins/auth/providers/DigestAuthProvider : io
public fun <init> (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun addRequestHeaders (Lio/ktor/client/request/HttpRequestBuilder;Lio/ktor/http/auth/HttpAuthHeader;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun clearToken ()V
public final fun getAlgorithmName ()Ljava/lang/String;
public final fun getRealm ()Ljava/lang/String;
public fun getSendWithoutRequest ()Z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ final class io.ktor.client.plugins.auth.providers/BasicAuthProvider : io.ktor.cl
final val sendWithoutRequest // io.ktor.client.plugins.auth.providers/BasicAuthProvider.sendWithoutRequest|{}sendWithoutRequest[0]
final fun <get-sendWithoutRequest>(): kotlin/Boolean // io.ktor.client.plugins.auth.providers/BasicAuthProvider.sendWithoutRequest.<get-sendWithoutRequest>|<get-sendWithoutRequest>(){}[0]

final fun clearToken() // io.ktor.client.plugins.auth.providers/BasicAuthProvider.clearToken|clearToken(){}[0]
final fun isApplicable(io.ktor.http.auth/HttpAuthHeader): kotlin/Boolean // io.ktor.client.plugins.auth.providers/BasicAuthProvider.isApplicable|isApplicable(io.ktor.http.auth.HttpAuthHeader){}[0]
final fun sendWithoutRequest(io.ktor.client.request/HttpRequestBuilder): kotlin/Boolean // io.ktor.client.plugins.auth.providers/BasicAuthProvider.sendWithoutRequest|sendWithoutRequest(io.ktor.client.request.HttpRequestBuilder){}[0]
final suspend fun addRequestHeaders(io.ktor.client.request/HttpRequestBuilder, io.ktor.http.auth/HttpAuthHeader?) // io.ktor.client.plugins.auth.providers/BasicAuthProvider.addRequestHeaders|addRequestHeaders(io.ktor.client.request.HttpRequestBuilder;io.ktor.http.auth.HttpAuthHeader?){}[0]
Expand Down Expand Up @@ -131,6 +132,7 @@ final class io.ktor.client.plugins.auth.providers/DigestAuthProvider : io.ktor.c
final val sendWithoutRequest // io.ktor.client.plugins.auth.providers/DigestAuthProvider.sendWithoutRequest|{}sendWithoutRequest[0]
final fun <get-sendWithoutRequest>(): kotlin/Boolean // io.ktor.client.plugins.auth.providers/DigestAuthProvider.sendWithoutRequest.<get-sendWithoutRequest>|<get-sendWithoutRequest>(){}[0]

final fun clearToken() // io.ktor.client.plugins.auth.providers/DigestAuthProvider.clearToken|clearToken(){}[0]
final fun isApplicable(io.ktor.http.auth/HttpAuthHeader): kotlin/Boolean // io.ktor.client.plugins.auth.providers/DigestAuthProvider.isApplicable|isApplicable(io.ktor.http.auth.HttpAuthHeader){}[0]
final fun sendWithoutRequest(io.ktor.client.request/HttpRequestBuilder): kotlin/Boolean // io.ktor.client.plugins.auth.providers/DigestAuthProvider.sendWithoutRequest|sendWithoutRequest(io.ktor.client.request.HttpRequestBuilder){}[0]
final suspend fun addRequestHeaders(io.ktor.client.request/HttpRequestBuilder, io.ktor.http.auth/HttpAuthHeader?) // io.ktor.client.plugins.auth.providers/DigestAuthProvider.addRequestHeaders|addRequestHeaders(io.ktor.client.request.HttpRequestBuilder;io.ktor.http.auth.HttpAuthHeader?){}[0]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.plugins.auth.providers
Expand Down Expand Up @@ -162,6 +162,24 @@ public class BasicAuthProvider(
tokensHolder.setToken(credentials)
return true
}

/**
* Clears the currently stored authentication tokens from the cache.
*
* This method should be called in the following cases:
* - When the credentials have been updated and need to take effect
* - When you want to force re-authentication
* - When you want to clear sensitive authentication data
*
* Note: The result of [credentials] invocation is cached internally.
* Calling this method will force the next authentication attempt to fetch fresh credentials.
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.client.plugins.auth.providers.BasicAuthProvider.clearToken)
*/
@InternalAPI // TODO KTOR-8180: Provide control over tokens to user code
public fun clearToken() {
tokensHolder.clearToken()
}
}

internal fun constructBasicAuthValue(credentials: BasicAuthCredentials): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.plugins.auth.providers
Expand Down Expand Up @@ -162,6 +162,19 @@ public class BearerAuthProvider(
return newToken != null
}

/**
* Clears the currently stored authentication tokens from the cache.
*
* This method should be called in the following cases:
* - When access or refresh tokens have been updated externally
* - When you want to clear sensitive token data (for example, during logout)
*
* Note: The result of `loadTokens` invocation is cached internally.
* Calling this method will force the next authentication attempt to fetch fresh tokens
* through the configured `loadTokens` function.
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.client.plugins.auth.providers.BearerAuthProvider.clearToken)
*/
public fun clearToken() {
tokensHolder.clearToken()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.plugins.auth.providers
Expand All @@ -13,7 +13,7 @@ import io.ktor.util.*
import io.ktor.utils.io.*
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.*
import kotlinx.atomicfu.atomic

/**
* Installs the client's [DigestAuthProvider].
Expand Down Expand Up @@ -218,4 +218,21 @@ public class DigestAuthProvider(
val digest = Digest(algorithmName)
return digest.build(data.toByteArray(Charsets.UTF_8))
}

/**
* Clears the currently stored authentication tokens from the cache.
*
* This method should be called in the following cases:
* - When the credentials have been updated and need to take effect
* - When you want to clear sensitive authentication data
*
* Note: The result of [credentials] invocation is cached internally.
* Calling this method will force the next authentication attempt to fetch fresh credentials.
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.client.plugins.auth.providers.DigestAuthProvider.clearToken)
*/
@InternalAPI // TODO KTOR-8180: Provide control over tokens to user code
public fun clearToken() {
tokenHolder.clearToken()
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
/*
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.client.plugins.auth

import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.http.auth.*
import kotlin.test.*
import io.ktor.utils.io.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

class BasicProviderTest {
@Test
Expand Down Expand Up @@ -44,6 +51,25 @@ class BasicProviderTest {
assertTrue(provider.isApplicable(header), "Provider with capitalized scheme should be applicable")
}

@OptIn(InternalAPI::class)
@Test
fun `update credentials after clearToken`() = runTest {
var credentials = BasicAuthCredentials("admin", "admin")
val provider = BasicAuthProvider(credentials = { credentials })

val requestBuilder = HttpRequestBuilder()
suspend fun assertAuthorizationHeader(expected: String) {
provider.addRequestHeaders(requestBuilder, authHeader = null)
assertEquals(expected, requestBuilder.headers[HttpHeaders.Authorization])
}

assertAuthorizationHeader("Basic YWRtaW46YWRtaW4=")
credentials = BasicAuthCredentials("user", "qwerty")
assertAuthorizationHeader("Basic YWRtaW46YWRtaW4=")
provider.clearToken()
assertAuthorizationHeader("Basic dXNlcjpxd2VydHk=")
}

private fun buildAuthString(username: String, password: String): String =
constructBasicAuthValue(BasicAuthCredentials(username, password))
}
Original file line number Diff line number Diff line change
Expand Up @@ -607,16 +607,21 @@ private fun Route.staticContentRoute(
remotePath: String,
autoHead: Boolean,
handler: suspend (ApplicationCall).() -> Unit
) = route(remotePath) {
route("{$pathParameterName...}") {
get {
call.handler()
}
if (autoHead) {
method(HttpMethod.Head) {
install(StaticContentAutoHead)
handle {
call.handler()
) = createChild(object : RouteSelector() {
override suspend fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation =
RouteSelectorEvaluation.Success(quality = RouteSelectorEvaluation.qualityTailcard)
}).apply {
route(remotePath) {
route("{$pathParameterName...}") {
get {
call.handler()
}
if (autoHead) {
method(HttpMethod.Head) {
install(StaticContentAutoHead)
handle {
call.handler()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.server.plugins.calllogging

import io.ktor.events.*
import io.ktor.server.application.*
import io.ktor.server.application.hooks.*
import io.ktor.server.http.content.*
Expand Down Expand Up @@ -59,7 +58,6 @@ public val CallLogging: ApplicationPlugin<CallLoggingConfig> = createApplication
}

setupMDCProvider()
setupLogging(application.monitor, ::log)

on(CallSetup) { call ->
call.attributes.put(CALL_START_TIME, clock())
Expand Down Expand Up @@ -96,23 +94,3 @@ private fun PluginBuilder<CallLoggingConfig>.logCallsWithMDC(logSuccess: (Applic
}
}
}

private fun setupLogging(events: Events, log: (String) -> Unit) {
val starting: (Application) -> Unit = { log("Application starting: $it") }
val started: (Application) -> Unit = { log("Application started: $it") }
val stopping: (Application) -> Unit = { log("Application stopping: $it") }
var stopped: (Application) -> Unit = {}

stopped = {
log("Application stopped: $it")
events.unsubscribe(ApplicationStarting, starting)
events.unsubscribe(ApplicationStarted, started)
events.unsubscribe(ApplicationStopping, stopping)
events.unsubscribe(ApplicationStopped, stopped)
}

events.subscribe(ApplicationStarting, starting)
events.subscribe(ApplicationStarted, started)
events.subscribe(ApplicationStopping, stopping)
events.subscribe(ApplicationStopped, stopped)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.server.plugins.calllogging
Expand Down Expand Up @@ -63,33 +63,6 @@ class CallLoggingTest {
messages = ArrayList()
}

@Test
fun `can log application lifecycle events`() = runTest {
var hash: String? = null

runTestApplication {
environment { environment() }
application {
install(CallLogging) { clock { 0 } }
hash = hashCode().toString(radix = 16)
}
}

assertTrue(messages.size >= 3, "It should be at least 3 message logged:\n$messages")
val startingMessageIndex = messages.indexOfFirst {
it == "INFO: Application started: io.ktor.server.application.Application@$hash"
}
val stoppingMessageIndex = messages.indexOfFirst {
it == "INFO: Application stopping: io.ktor.server.application.Application@$hash"
}
val stoppedMessageIndex = messages.indexOfFirst {
it == "INFO: Application stopped: io.ktor.server.application.Application@$hash"
}
assertTrue { startingMessageIndex >= 0 }
assertTrue { startingMessageIndex < stoppingMessageIndex }
assertTrue { stoppingMessageIndex < stoppedMessageIndex }
}

@Test
fun `can log an unhandled get request`() = testApplication {
environment { environment() }
Expand Down Expand Up @@ -398,7 +371,6 @@ class CallLoggingTest {
}
}
}
lateinit var hash: String

runTestApplication {
application {
Expand All @@ -407,11 +379,11 @@ class CallLoggingTest {
clock { 0 }
}
}
application { hash = hashCode().toString(radix = 16) }
client.get("/")
}

assertTrue(customMessages.isNotEmpty())
assertTrue(customMessages.all { it.startsWith("CUSTOM TRACE:") && it.contains(hash) })
assertTrue(customMessages.all { it.startsWith("CUSTOM TRACE:") })
assertTrue(messages.isEmpty())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@

package io.ktor.tests.resources

import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.resources.*
import io.ktor.server.http.content.*
import io.ktor.server.resources.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import java.math.*
import kotlin.test.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.math.BigDecimal
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class ResourcesTestJvm {

Expand Down Expand Up @@ -57,4 +67,25 @@ class ResourcesTestJvm {
"/?bd=123456789012345678901234567890&bi=123456789012345678901234567890"
)
}

@Resource("/")
class Home

@Test
fun testHomeResourceWithStaticResource() = testResourcesApplication {
var executed = false
routing {
get<Home> {
executed = true
call.respondText("OK")
}
staticResources("/", "static")
}

client.get("/").let { response ->
assertTrue(executed)
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("OK", response.bodyAsText())
}
}
}

0 comments on commit b20edcc

Please sign in to comment.