Add basic components for push-message-provider-hpk module

This commit is contained in:
Korna 2022-11-02 15:55:01 +03:00
parent 53a2bb680c
commit 3d67f4a1ce
6 changed files with 164 additions and 0 deletions

View File

@ -0,0 +1,3 @@
package ru.touchin.push.message.provider.hpk.base.builders
internal interface Buildable

View File

@ -0,0 +1,42 @@
package ru.touchin.push.message.provider.hpk.base.clients
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.web.reactive.function.client.ClientResponse
import ru.touchin.push.message.provider.hpk.base.clients.dto.ConditionalResponse
internal open class ConditionalWebClientParser(
private val objectMapper: ObjectMapper,
) {
open fun isOkResponse(clientResponse: ClientResponse): Boolean {
return clientResponse.statusCode().is2xxSuccessful
}
@Throws(Exception::class)
inline fun <reified S, reified F> parse(
clientResponse: ClientResponse,
body: String,
): ConditionalResponse<S, F> {
return if (isOkResponse(clientResponse)) {
ConditionalResponse<S, F>(
success = parseValue(body, S::class.java),
failure = null
)
} else {
ConditionalResponse(
success = null,
failure = parseValue(body, F::class.java)
)
}
}
private fun <T> parseValue(source: String?, clazz: Class<T>): T {
return if (clazz.canonicalName != String::class.java.canonicalName) {
objectMapper.readValue(source, clazz)
} else {
@Suppress("UNCHECKED_CAST")
source as T // T is String
}
}
}

View File

@ -0,0 +1,85 @@
package ru.touchin.push.message.provider.hpk.base.clients
import io.netty.channel.ChannelOption
import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.web.reactive.function.client.ClientResponse
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import reactor.netty.http.client.HttpClient
import ru.touchin.common.spring.web.webclient.BaseLogWebClient
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
import ru.touchin.push.message.provider.hpk.base.clients.dto.ConditionalResponse
import ru.touchin.push.message.provider.hpk.properties.HpkProperties
import java.util.concurrent.TimeUnit
abstract class ConfigurableWebClient(
webClientLogger: WebClientLogger,
webClientBuilder: WebClient.Builder,
protected val webService: HpkProperties.WebService,
) : BaseLogWebClient(webClientLogger, webClientBuilder) {
private val conditionalWebClientParser: Lazy<ConditionalWebClientParser> = lazy {
ConditionalWebClientParser(
objectMapper = getObjectMapper(),
)
}
protected fun WebClient.Builder.setTimeouts(): WebClient.Builder {
val httpClient: HttpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, webService.http.connectionTimeout.toMillis().toInt())
.doOnConnected { setup ->
setup.addHandlerLast(ReadTimeoutHandler(webService.http.readTimeout.toMillis(), TimeUnit.MILLISECONDS))
setup.addHandlerLast(WriteTimeoutHandler(webService.http.writeTimeout.toMillis(), TimeUnit.MILLISECONDS))
}
.let { httpClient ->
webService.ssl?.let { ssl ->
httpClient.secure { builder ->
builder
.sslContext(SslContextBuilder.forClient().build())
.handshakeTimeout(ssl.handshakeTimeout)
.closeNotifyFlushTimeout(ssl.notifyFlushTimeout)
.closeNotifyReadTimeout(ssl.notifyReadTimeout)
}
} ?: httpClient
}
return clientConnector(ReactorClientHttpConnector(httpClient))
}
internal inline fun <reified S, reified F> WebClient.RequestHeadersSpec<*>.exchangeWithWrap(
requestLogData: RequestLogData,
): Mono<ConditionalResponse<S, F>> {
return exchangeToMono { clientResponse ->
parse<S, F>(clientResponse)
}.doOnNext { responseWrapper ->
getLogger().log(
requestLogData.copy(
responseBody = responseWrapper.success ?: responseWrapper.failure
)
)
}
}
internal inline fun <reified S, reified F> parse(
clientResponse: ClientResponse,
): Mono<ConditionalResponse<S, F>> {
val responseBody = clientResponse
.bodyToMono(String::class.java)
.defaultIfEmpty(String())
.publishOn(Schedulers.parallel())
return responseBody
.map { body ->
conditionalWebClientParser.value.parse(
clientResponse = clientResponse,
body = body,
)
}
}
}

View File

@ -0,0 +1,15 @@
package ru.touchin.push.message.provider.hpk.base.clients.dto
internal open class ConditionalResponse<S, F>(
val success: S?,
val failure: F?,
) {
init {
// Only one value should be present
check((success == null) != (failure == null))
}
val isSuccess: Boolean = success != null
}

View File

@ -0,0 +1,12 @@
package ru.touchin.push.message.provider.hpk.base.enums
import com.fasterxml.jackson.annotation.JsonValue
internal interface ValueableSerializableEnum<T> {
val value: T
@JsonValue
fun toValue(): T = value
}

View File

@ -0,0 +1,7 @@
package ru.touchin.push.message.provider.hpk.base.extensions
import ru.touchin.push.message.provider.hpk.base.builders.Buildable
fun <V, B : Buildable> B.ifNotNull(value: V?, setter: B.(V) -> B): B {
return value?.let { setter(it) } ?: this
}