How to build a proxy for HTTP requests with Fuel - kotlin

I was using restTemplate and this was my method:
fun fetchAvailableCars(): Aggregations? {
val availableCarsUrl = UriComponentsBuilder
.fromHttpUrl(getCatalogUrl())
.query("aggsBy={aggregators}")
.buildAndExpand("brand,model")
.toString()
return restTemplate.getForEntity(availableCarsUrl, Aggregations::class.java).body
}
I'm trying to use Fuel to do basically the same thing (but handling errors), but I couldn't find a simple way to do that.
This is what I have so far:
fun fetchAvailableCarsWithFuel() {
val availableCarsUrl = UriComponentsBuilder
.fromHttpUrl(getCatalogUrl())
.query("aggsBy={aggregators}")
.buildAndExpand("brand,model")
.toString()
Fuel.get(availableCarsUrl)
.responseObject<Aggregations> { _, _, result ->
when (result) {
is Success -> {
result.get()
}
is Failure -> {
// log.error
}
}
}
}
but there's no easy way to return the body from inside the lambda. What are the common ways to do that?
P.S.: I'm using fuel-jackson to deserialize the response

Related

What is Kotlin's functional equivalent for finally?

This example is from the documentation of HttpUrlConnection:
URL url = new URL("http://www.android.com/");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
readStream(in);
}
finally {
urlConnection.disconnect();
}
The documentation says:
Once the response body has been read, the HttpURLConnection should be closed by calling disconnect().
I tried use the Java class to load an image in a functional style:
fun fetch (url: String): ImageBitmap =
URL(url)
.openConnection()
.also { it.setRequestProperty (authorization.header, authorization.basicauth()) }
.getInputStream()
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
Now I am wondering how to add the disconnect call?
I want to achieve this:
fun fetch (url: String): ImageBitmap {
var connection: HttpURLConnection? = null
return try {
URL(url)
.openConnection()
.also { it.setRequestProperty(authorization.header, authorization.basicauth()) }
.also { connection = it as HttpURLConnection }
.getInputStream()
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
} finally {
connection?.disconnect()
}
}
But in a less ugly manner.
There isn't a stdlib solution for your case, but kotlin has the use() extension method defined on (Auto)Closeable to make this pattern more functional for those interfaces.
You could add a use extension method to HttpUrlConnection yourself that calls its disconnect() method, using the same approach as the source of use.
Of course you would still need to write the try finally once, but it is now hidden when using HttpUrlConnection.
On first sight you'd end up with something like this, you might still need some null handling somewhere.
public fun <T : HttpURLConnection, R> T.use(block: (T) -> R): R {
try {
return block(this)
} finally {
disconnect()
}
}
(URL(url).openConnection() as HttpURLConnection).use {
// do things with connection `it`
}
// connection is now disconnected()
Your existing way of doing this is clear enough as it is. Kotlin is not a purely functional language, so trying to use higher-order functions all the time can sometimes make your code harder to read (See also Principle of least astonishment), not to mention that you are using a Java API, which isn't designed for something like this at all.
One way I've thought of though, is:
fun fetch (url: String) =
(URL(url).openConnection() as HttpURLConnection).apply {
runCatching {
setRequestProperty(authorization.header, authorization.basicauth())
inputStream
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
}.also { disconnect() }.getOrThrow()
}
I do the entire operation that could through inside a runCatching to catch any exceptions, disconnect from the connection, then throw the exception back out again.
In terms of the order of execution, this should be the same as try...finally like this:
fun fetch (url: String): ImageBitmap {
val connection = URL(url).openConnection() as HttpURLConnection
return try {
connection
.also { it.setRequestProperty(authorization.header, authorization.basicauth()) }
.getInputStream()
.buffered()
.use { BitmapFactory.decodeStream(it) }
.asImageBitmap()
} finally {
connection.disconnect()
}
}

Ktor Server/Application request/response body logging

Is there any way to log the request and response body from the ktor server communication?
The buildin CallLogging feature only logs the metadata of a call. I tried writing my own logging feature like in this example: https://github.com/Koriit/ktor-logging/blob/master/src/main/kotlin/korrit/kotlin/ktor/features/logging/Logging.kt
class Logging(private val logger: Logger) {
class Configuration {
var logger: Logger = LoggerFactory.getLogger(Logging::class.java)
}
private suspend fun logRequest(call: ApplicationCall) {
logger.info(StringBuilder().apply {
appendLine("Received request:")
val requestURI = call.request.path()
appendLine(call.request.origin.run { "${method.value} $scheme://$host:$port$requestURI $version" })
call.request.headers.forEach { header, values ->
appendLine("$header: ${values.firstOrNull()}")
}
try {
appendLine()
appendLine(String(call.receive<ByteArray>()))
} catch (e: RequestAlreadyConsumedException) {
logger.error("Logging payloads requires DoubleReceive feature to be installed with receiveEntireContent=true", e)
}
}.toString())
}
private suspend fun logResponse(call: ApplicationCall, subject: Any) {
logger.info(StringBuilder().apply {
appendLine("Sent response:")
appendLine("${call.request.httpVersion} ${call.response.status()}")
call.response.headers.allValues().forEach { header, values ->
appendLine("$header: ${values.firstOrNull()}")
}
when (subject) {
is TextContent -> appendLine(subject.text)
is OutputStreamContent -> appendLine() // ToDo: How to get response body??
else -> appendLine("unknown body type")
}
}.toString())
}
/**
* Feature installation.
*/
fun install(pipeline: Application) {
pipeline.intercept(ApplicationCallPipeline.Monitoring) {
logRequest(call)
proceedWith(subject)
}
pipeline.sendPipeline.addPhase(responseLoggingPhase)
pipeline.sendPipeline.intercept(responseLoggingPhase) {
logResponse(call, subject)
}
}
companion object Feature : ApplicationFeature<Application, Configuration, Logging> {
override val key = AttributeKey<Logging>("Logging Feature")
val responseLoggingPhase = PipelinePhase("ResponseLogging")
override fun install(pipeline: Application, configure: Configuration.() -> Unit): Logging {
val configuration = Configuration().apply(configure)
return Logging(configuration.logger).apply { install(pipeline) }
}
}
}
It works fine for logging the request body using the DoubleReceive plugin. And if the response is plain text i can log the response as the subject in the sendPipeline interception will be of type TextContent or like in the example ByteArrayContent.
But in my case i am responding a data class instance with Jackson ContentNegotiation. In this case the subject is of type OutputStreamContent and i see no options to geht the serialized body from it.
Any idea how to log the serialized response json in my logging feature? Or maybe there is another option using the ktor server? I mean i could serialize my object manually and respond plain text, but thats an ugly way to do it.
I'm not shure about if this is the best way to do it, but here it is:
public fun ApplicationResponse.toLogString(subject: Any): String = when(subject) {
is TextContent -> subject.text
is OutputStreamContent -> {
val channel = ByteChannel(true)
runBlocking {
(subject as OutputStreamContent).writeTo(channel)
val buffer = StringBuilder()
while (!channel.isClosedForRead) {
channel.readUTF8LineTo(buffer)
}
buffer.toString()
}
}
else -> String()
}

Is there a more idiomatic way to perform a subscribe & async / await operation?

I have a spring boot kotlin app that creates a web socket connection to another spring app, sends multiple "subscribe" messages, and then needs to wait for receipt of one response per subscription on the web socket connection. The number of subscriptions open at a given time could be up to a few thousand.
I've come up with a basic working solution using CompletableFuture and coroutines, as below. Is there a more idiomatic or concise way to do this task, or is this a fine solution? Any suggestions for improvement are appreciated.
// InputObject / ResponseObject are generic placeholders
fun getItems(inputObjects: List<InputObject>): List<ResponseObject> {
val ret: ConcurrentLinkedQueue<ResponseObject> = ConcurrentLinkedQueue()
// create a completable future for each input object
val subscriptions: MutableMap<String, CompletableFuture<ResponseObject>> = mutableMapOf()
inputObjects.forEach {
subscriptions[it.id] = CompletableFuture()
}
// create web socket client configured with a lambda handler to
// fulfill each subscription
// each responseObject.id matches one inputObject.id
val client = createWebSocketClient({
try {
val responseObject = objectMapper.readValue(it, ResponseObject::class.java)
subscriptions[responseObject.id]?.complete(responseObject)
} catch (e: Exception) {
logger.warn("Exception reading data: ${e.message}")
}
})
runBlocking {
coroutineScope {
for (item in inputObjects) {
launch {
// create and send a subscribe request
client.sendMessage(createSubscribe(item.id))
// wait for each future to complete
// uses CompletableFuture extension await() from kotlinx-coroutines-jdk8
val result = subscriptions[item.id]?.await()
if (result != null) {
ret.add(result)
}
}
}
}
}
client.close()
return ret.toList()
}
edit: I found a similar question: How to pass result as it comes using coroutines?
Which options makes the most sense?
fun getItems(inputObjects: List<InputObject>): List<ResponseObject> {
val subscriptions = ids.associateTo(mutableMapOf()) { it.id to CompletableFuture<ResponseObject>() }
val client = createWebSocketClient({
try {
val responseObject = objectMapper.readValue(it, ResponseObject::class.java)
subscriptions[responseObject.id]?.complete(responseObject)
} catch (e: Exception) {
logger.warn("Exception reading data: ${e.message}")
}
})
return runBlocking(Dispatchers.IO) {
inputObjects
.mapNotNull {
client.sendMessage(createSubscribe(item.id))
subscriptions[item.id]?.await()
}
}
}

How to add condition to method "retry" in kotlin and webflux when api return error data?

How to change the parameters with retry() in kotlin and webflux ?
There is a productInfo function, the function parameter is a collection of product ids.
When I input a wrong id in the list collection ids, the upstream interface will only return the wrong id. And get failed.
What I want to achieve is when the upstream interface returns the wrong id. The product info can remove the wrong id and have a second try with the right ids.
I tried to use retry() but I don't know how to change the parameters in the second try.
fun productInfo(ids: List<Pair<String, String>>): Flux<ProductItem> {
return productWebClient
.get()
.uri("product/items/${ids.joinToString(";") { "${it.second},${it.first}" }}")
.retrieve()
.bodyToFlux(ProductItem::class.java)
.onErrorResume {
logger.error("Fetch products failed." + it.message)
Mono.empty()
}
}
What you want is not retry(). I've built a solution making minor assumptions here and there. You can refer to this solution and make changes according to your requirements. I've used recursion here (productInfo()). You can replace the recursion call with webclient call if the error occurs only once.
fun productInfo(ids: List<Pair<String, String>>): Flux<ProductItem> {
val idsString = ids.joinToString(";") { "${it.second},${it.first}" }
return webClient
.get()
.uri("product/items/${idsString}")
.exchange()
.flatMapMany { response ->
if (response.statusCode().isError) {
response.body { clientHttpResponse, _ ->
clientHttpResponse.body.cast(String::class.java).collectList()
.flatMapMany<ProductItem> { eids ->
val ids2 = ids.filter { eids.contains("${it.second},${it.first}") }
productInfo(ids2)
}
}
} else {
response.bodyToFlux(ProductItem::class.java)
}
}
}

RxJava - Retrofit dont making request with Rx

I'm studying about RxJava with Retrofit, and I'm trying to combine two requests. But its not making a request to getToken api. It's a simple code just for study case
This is what I have now, what am I doing wrong?
apiManager.getToken(body)
.subscribeOn(Schedulers.io())
.map { people -> saveUser(people) }
.doOnNext { car -> Log.d("car",car.toString()) }
.flatMap { car -> Observable.from(car!!.items) }
.flatMap { carId -> val header = HashMap<String, String>()
header.put("Authorization", "Bearer " + user!!.authorization)
apiManager.getCarItens(header, carId.id!!) }
.doOnCompleted { showUser(user) }
.subscribeOn(AndroidSchedulers.mainThread())
You are just defining your Observable, but you are not subscribing to it, so the stream won't get data at all:
apiManager.getToken(body)
.subscribeOn(Schedulers.io())
.map { people -> saveUser(people) }
.doOnNext { car -> Log.d("car", car.toString()) }
.flatMap { car -> Observable.from(car!!.items) }
.flatMap { carId ->
val header = HashMap<String, String>()
header.put("Authorization", "Bearer " + user!!.authorization)
apiManager.getCarItens(header, carId.id!!)
}
.doOnCompleted { showUser(user) }
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe() // This is what you were missing!
I'd recommend you cleanup some things, though:
Avoid using !!: it will cause an Exception if the objects are null, which will bubble up, given that you are not handling them (you could, as an onError parameter to subscribe
Instead of doOnCompleted, use onCompleted parameter to subscribe, which makes it be next to the error handling, and it's easier to read