I would like my server to call the login endpoint of another server and cache the auth token for later use. My issue is that when the server tries to reuse the existing token it hangs indefinitely or infinite loops.
@Component
class ApiWebClient {
private var authToken = Mono.just(AuthToken("", Instant.ofEpochSecond(0)))
fun login(): Mono<AuthToken> {
authToken = doesTokenNeedRefreshing().flatMap { needsRefreshing ->
if (needsRefreshing) {
WebClient.create().post()
.uri("https://example.com/login")
.body(
Mono.just("Credentials"),
String::class.java
).exchangeToMono { response ->
response.bodyToMono<LoginResponse>()
}.map { response ->
LOGGER.info("Successfully logged in")
AuthToken(response.token, Instant.now())
}
} else {
LOGGER.info("Reuse token")
authToken
}
}.cache()
return authToken
}
private fun doesTokenNeedRefreshing(): Mono<Boolean> {
return authToken.map {
Instant.now().minusMillis(ONE_MINUTE_IN_MILLIS).isAfter(it.lastModified)
}
}
class AuthToken(
var token: String,
var lastModified: Instant
)
companion object {
private const val ONE_MINUTE_IN_MILLIS = 60 * 1000L
@Suppress("JAVA_CLASS_ON_COMPANION")
@JvmStatic
private val LOGGER = LoggerFactory.getLogger(javaClass.enclosingClass)
}
}
If login gets called twice within the ONE_MINUTE_IN_MILLIS
amount of time then it just hangs. I suspect this is because the doesTokenNeedRefreshing()
calls a .map {}
on authToken
and then later down the chain authToken
is reassigned to itself. As well, there's an attempt to recache that exact same value. I've played around with recreating AuthToken
each time instead of returning the same instance but no luck. The server either hangs or infinite loops.
How can I achieve returning the same instance of the cached value so I don't have to make a web request each time?
What I was looking for was a switchIfEmpty
statement to return the existing cached value if it doesn't need to be refreshed.
@Component
class ApiWebClient {
private var authToken = Mono.just(AuthToken("", Instant.ofEpochSecond(0)))
fun login(): Mono<AuthToken> {
authToken = doesTokenNeedRefreshing().flatMap { needsRefreshing ->
if (needsRefreshing) {
WebClient.create().post()
.uri("https://example.com/login")
.body(
Mono.just("Credentials"),
String::class.java
).exchangeToMono { response ->
response.bodyToMono<LoginResponse>()
}.map { response ->
LOGGER.info("Successfully logged in")
AuthToken(response.token, Instant.now())
}
} else {
LOGGER.info("Reuse token")
Mono.empty()
}
}.cache()
.switchIfEmpty(authToken)
return authToken
}
}
In this example, if the token doesn't need to be refreshed then the stream returns an empty Mono
. Then the switchIfEmpty
statement returns the original auth token. This, therefore, avoids "recursively" returning the same stream over and over again.