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
Related
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()
}
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()
}
}
}
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
I'm working with an API that only displays an event's venue id in the response when performing an event search API call. I'm looking to see if there's a way to make a Spring WebClient request to fetch the venue information as Flux items are being emitted.
val events = eventService.fetchEventsByLocation(lat,lon,radius)
.flatMapIterable { eventResponse -> EventTransformer.map(eventResponse)}
.doOnNext { transformedEvent -> this.repository.save(transformedEvent) }
fun fetchEventsByLocation(lat:Double?,lon:Double?,radius:Double?): Mono<EventResponse> {
val builder = UriComponentsBuilder.fromPath(SEARCH_EVENTS)
.queryParam("categories", "103")
.queryParam("location.within", 20.toString() + "mi")
.queryParam("location.latitude", lat)
.queryParam("location.longitude", lon)
.queryParam("token",apiKey)
return this.webClient.get()
.uri(builder.toUriString())
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.flatMap { response -> response.bodyToMono(String::class.java) }
.map { response -> transform(response) }
}
fun fetchEventVenue(id:String?): Mono<Venue> {
val builder = UriComponentsBuilder.fromPath(VENUES + id)
.queryParam("token",apiKey)
return this.webClient.get()
.uri(builder.toUriString())
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.flatMap { response -> response.bodyToMono(Venue::class.java) }
}
doOnNext is an intermediate operation. Without a terminal operation (such as subscribe), the stream isn't consumed.
I'm trying to find a way to execute requests in parallel and handle them when every observable finishes. Despite everything is working when all observables gives a response, I not seeing a way to handle each all errors when everything is finished.
This is a sample of zip operator, which basically executes 2 requests in parallel:
Observable.zip(
getObservable1()
.onErrorResumeNext { errorThrowable: Throwable ->
Observable.error(ErrorEntity(Type.ONE, errorThrowable))
}.subscribeOn(Schedulers.io()),
getObservable2()
.onErrorResumeNext { errorThrowable: Throwable ->
Observable.error(ErrorEntity(Type.TWO, errorThrowable))
}.subscribeOn(Schedulers.io()),
BiFunction { value1: String, value2: String ->
return#BiFunction value1 + value2
})
//execute requests should be on io() thread
.subscribeOn(Schedulers.io())
//there are other tasks inside subscriber that need io() thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
Snackbar.make(view, "Replace with your own action " + result, Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
},
{ error ->
Log.d("TAG", "Error is : " + (error as ErrorEntity).error.message)
}
)
private fun getObservable1(): Observable<String> {
return Observable.defer {
throw Throwable("Error 1")
}
}
private fun getObservable2(): Observable<String> {
return Observable.defer {
throw Throwable("Error 2")
}
}
Problem with this approach is that there is no mechanism to join each error like BiFunction do for the success case. Therefore, the zip operator will only trigger the first error and will ignore the others.
Output:
D/TAG: Error is : Error 1
Is there any way to retrieve all errors only after every observable inside zip completed or gave an error?
My main goal is to see which requests gave an error and execute only those after a dialog appears to the user asking him if he wants to retry the failed requests.
You can model your observables using data classes. E.g.
sealed class Response {
data class Success(val data: String) : Response()
data class Error(val t: Throwable) : Response()
}
then you can map your observables to Response like this:
val first: Observable<Response> = observable1
.map<Response> { Response.Success(it) }
.onErrorReturn { Response.Error(it) }
val second: Observable<Response> = observable2
.map<Response> { Response.Success(it) }
.onErrorReturn { Response.Error(it) }
and you can combine them:
Observable.zip(
first,
second,
BiFunction { t1: Response, t2: Response -> Pair(t1, t2) }
).subscribe({println(it)})
this prints:
(Error(t=java.lang.Exception: Error 1), Error(t=java.lang.Exception:
Error 2))
Also take a look at this article.