I want to expose a synchronous API that calls an async under the hood. The async API calls my server back when the data are available on its side (if data are not available in a reasonable amount of time my API returns a 404).
I've implemented a solution based on CompletableFuture, prototyped another one using an Emitter that I store in a context that can be retrieved when the incoming callback arrives but I'm not sure to do it the right way (most simple?).
What do you think about?
Thanks in advance
Patrice
#Path("/status")
class DeviceStatusController(
private val deviceStatusService: DeviceStatusService
) {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
fun getStatus(#Valid #NotNull requestStatus: RequestStatus): Uni<String> {
return deviceStatusService.getStatus(requestStatus)
}
}
#ApplicationScoped
#Path("/notifications")
class NotificationController(val service: DeviceStatusService) {
#POST
fun receive(#RequestBody #Valid notification: Notification) {
service.receive(notification)
}
}
#ApplicationScoped
class DeviceStatusService {
private val logger: Logger = Logger.getLogger(this.javaClass.canonicalName)
data class UniContext(val emitter: CompletableFuture<String>)
//Todo: Change for redis registration with TTL
val notificationMap = mutableMapOf<String, UniContext>()
fun getStatus(requestStatus: RequestStatus): Uni<String> {
// Call async service here ... it will call us back on notifications endpoint
// It will just reply ... your request is accepted
val emitter = CompletableFuture<String>()
notificationMap[requestStatus.device] = UniContext(emitter)
return Uni.createFrom().completionStage(emitter.minimalCompletionStage())
.ifNoItem().after(Duration.ofSeconds(60))
.failWith {
cleanContexts(requestStatus.device)
TimeoutException("No reply for ${requestStatus.device}")
}
}
fun cleanContexts(id: String) {
notificationMap.remove(id)
}
fun receive(notification: Notification) {
logger.fine("Notification received for ${notification.device}")
notificationMap[notification.device]?.let {
it.emitter.complete(notification.status)
notificationMap.remove(notification.device)
}
}
}
The full sample is in my github repo
I'm currently developing an Integration Platform, but having some issues finding an idiotmatic way to set up the Spring Security Filter Chain.
Spring Security is used in 2 ways.
To authenticate the user of the application. (we'll call this the primary client registration)
To allow the user to authenticate with an external system.
In the second case, we saved their access/refresh tokens and use those later to interact with the external system(s) APIs and sync data.
When the user authenticated with the external system, the new token will overwrite the Security Context.
So far, the best solution we could come up with was overriding the SecurityContext Strategy.
class CompositeSecurityContextHolderStrategy(
val primaryClientRegistrationId: String = "primary"
) : SecurityContextHolderStrategy {
companion object {
val contextHolder = ThreadLocal<CompositeSecurityContext>()
}
override fun createEmptyContext(): SecurityContext = CompositeSecurityContext()
override fun clearContext() {
contextHolder.remove()
}
override fun getContext(): CompositeSecurityContext {
if (contextHolder.get() == null) {
contextHolder.set(createEmptyContext() as CompositeSecurityContext)
}
return contextHolder.get()
}
override fun setContext(context: SecurityContext) {
val currentContext = getContext()
val authentication = context.authentication
if (authentication is OAuth2AuthenticationToken) {
if (authentication.authorizedClientRegistrationId == primaryClientRegistrationId) currentContext.authentication = authentication
else currentContext.addAssociateAuthentication(authentication)
} else {
currentContext.authentication = context.authentication
}
}
}
class CompositeSecurityContext : SecurityContextImpl() {
val associatedAuthentications = mutableMapOf<String, Authentication>()
fun addAssociateAuthentication(authentication: Authentication) {
if (authentication is OAuth2AuthenticationToken) associatedAuthentications[authentication.authorizedClientRegistrationId] = authentication
}
fun retrieveAssociatedAuthentication(clientRegistrationId: String): Authentication? = associatedAuthentications[clientRegistrationId]
}
SecurityContextHolder.setContextHolderStrategy(CompositeSecurityContextHolderStrategy("primary"))
This works as intended. The Primary session is never overrwritten and associated token is available for consumption when our custom implementation of OAuth2AuthorizedClientService is invoked.
However, it still feels like a hack, but cannot find a better way to achieve authentication with token retrieval, without overwriting the security context.
I've seen a few older posts reference overriding OAuth2ClientAuthenticationProcessingFilter, but that is now deprecated in newer versions of SpringSecurity 5.
Any help will be appreciated.
I am receiving back this kotlinx.coroutines.channels.ClosedReceiveChannelException upon about 50% of my api calls to a post url through Ktor HttpClient.
Our code looks like
Module.kt
bind<ServiceClient>() with singleton {
val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
ServiceClient( client, instance() )
}
and our implementation of the call is
suspend fun post(request: RequestObject): List<ResponseObjects> =
client.post(endpoint) {
contentType(ContentType.Application.Json)
body = request
}
Sometimes I am able to receive back the expected results and other times I get the client closed exception. What am I doing wrong here?
I am on Ktor version 1.6.4
I'm using a spring security in my app. I need it to secure two api's endpoints. Here is security config:
#Configuration
#EnableWebSecurity
class SecurityConfig(
private val authProps: AuthenticationProperties
) : WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder) {
val encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
auth.inMemoryAuthentication()
.passwordEncoder(encoder)
.withUser(authProps.user)
.password(encoder.encode(authProps.password))
.roles("USER")
}
override fun configure(http: HttpSecurity) {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/firstapi/**", "/secondapi/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic()
}
}
Also, i'm integrating with an external service, that uses oauth2, here it's restTemplate config -
#Configuration
class ExternalServiceConfiguration {
#Bean("externalProperties")
#ConfigurationProperties("http.external.api")
fun externalHttpClientConfig() = HttpClientProperties()
#Bean("externalDetails")
#ConfigurationProperties("http.external.api.security.oauth2")
fun externalOAuth2Details() = ResourceOwnerPasswordResourceDetails()
#Bean("externalRestTemplate")
fun externalClientRestTemplate(
#Qualifier("externalProperties") externalHttpClientProperties: HttpClientProperties,
#Qualifier("externalDetails") externalOAuth2Details: ResourceOwnerPasswordResourceDetails,
customizerProviders: ObjectProvider<RestTemplateCustomizer>,
objectMapper: ObjectMapper,
): RestTemplate {
val template = OAuth2RestTemplate(externalOAuth2Details).apply {
messageConverters = listOf(MappingJackson2HttpMessageConverter(objectMapper))
requestFactory = requestFactory(externalHttpClientProperties)
errorHandler = IntegrationResponseErrorHandler()
}
customizerProviders.orderedStream().forEach { it.customize(template) }
return template
}
}
Somehow spring-security-oauth clashes with spring-security. When i try to obtain a token i'm failing at lib class method:
AccessTokenProviderChain.obtainAccessToken(...)
, because instead of null i have an AnonymousAuthenticationToken authentication at context, when it calls
SecurityContextHolder.getContext().getAuthentication()
So spring security merges anonymous context somehow, i am not sure when and how, so that it affects external calls.
Could anyone help me with advice, how can i find the collision???
I got something like this:
private val client = HttpClient {
install(JsonFeature) {
serializer = GsonSerializer()
}
install(ExpectSuccess)
}
and make request like
private fun HttpRequestBuilder.apiUrl(path: String, userId: String? = null) {
header(HttpHeaders.CacheControl, "no-cache")
url {
takeFrom(endPoint)
encodedPath = path
}
}
but I need to check request and response body, is there any way to do it? in console/in file?
You can achieve this with the Logging feature.
First add the dependency:
implementation "io.ktor:ktor-client-logging-native:$ktor_version"
Then install the feature:
private val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
}
Bonus:
If you need to have multiple HttpClient instances throughout your application and you want to reuse some of the configuration, then you can create an extension function and add the common logic in there. For example:
fun HttpClientConfig<*>.default() {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
// Add all the common configuration here.
}
And then initialize your HttpClient like this:
private val client = HttpClient {
default()
}
I ran into this as well. I switched to using the Ktor OkHttp client as I'm familiar with the logging mechanism there.
Update your pom.xml or gradle.build to include that client (copy/paste from the Ktor site) and also add the OkHttp Logging Interceptor (again, copy/paste from that site). Current version is 3.12.0.
Now configure the client with
val client = HttpClient(OkHttp) {
engine {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = Level.BODY
addInterceptor(loggingInterceptor)
}
}
Regardless of which client you use or framework you are on, you can implement your own logger like so:
private val client = HttpClient {
// Other configurations...
install(Logging) {
logger = CustomHttpLogger()
level = LogLevel.BODY
}
}
Where CustomHttpLogger is any class that implements the ktor Logger interface, like so:
import io.ktor.client.features.logging.Logger
class CustomHttpLogger(): Logger {
override fun log(message: String) {
Log.d("loggerTag", message) // Or whatever logging system you want here
}
}
You can read more about the Logger interface in the documentation here or in the source code here
It looks like we should handle the response in HttpReceivePipeline. We could clone the origin response and use it for logging purpose:
scope.receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
val (loggingContent, responseContent) = response.content.split(scope)
launch {
val callForLog = DelegatedCall(loggingContent, context, scope, shouldClose = false)
....
}
...
}
The example implementation could be found here: https://github.com/ktorio/ktor/blob/00369bf3e41e91d366279fce57b8f4c97f927fd4/ktor-client/ktor-client-core/src/io/ktor/client/features/observer/ResponseObserver.kt
and would be available in next minor release as a client feature.
btw: we could implement the same scheme for the request.
A custom structured log can be created with the HttpSend plugin
Ktor 2.x:
client.plugin(HttpSend).intercept { request ->
val call = execute(request)
val response = call.response
val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
Log.i("NETWORK", "[${response.status.value}] ${request.url.build()} ($durationMillis ms)")
call
}
Ktor 1.x:
client.config {
install(HttpSend) {
intercept { call, _ ->
val request = call.request
val response = call.response
val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
Log.i("NETWORK", "[${response.status.value}] ${request.url} ($durationMillis ms)")
call
}
}
}
Check out Kotlin Logging, https://github.com/MicroUtils/kotlin-logging it isused by a lot of open source frameworks and takes care of all the prety printing.
You can use it simply like this:
private val logger = KotlinLogging.logger { }
logger.info { "MYLOGGER INFO" }
logger.warn { "MYLOGGER WARNING" }
logger.error { "MYLOGGER ERROR" }
This will print the messages on the console.