How to return Flux<String> in ServerResponse in WebFlux - mono

I have a Jdbc Layer which is returning Flux. While returning the data, the fromPublisher method, it's accepting other Serializable classes, but the method is not accepting Flux.
Approach 1
public Mono<ServerResponse> getNames(final ServerRequest request) {
Flux<String> strings = Flux.just("a", "b", "c");
return ServerResponse.ok().contentType(APPLICATION_JSON)
.body(fromPublisher(response), String.class);
}
Above approach is returning abc combined as a Single String.
I tried this,
return ServerResponse.ok()
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromValue(response), List.class);
I tried this aswell.
Mono<List<String>> mono = response.collectList();
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.body(fromPublisher(mono, String.class));
but this is also giving a Runtime error of
> body' should be an object, for reactive types use a variant specifying
> a publisher/producer and its related element type

Below is an example of how to send back a Flux<String> in the body of a response
Flux<String> strings = Flux.just("a", "b", "c");
ServerResponse.ok().body(strings, String.class);

This is working.
Mono<List<String>> strings = Flux.just("a", "b", "c").collectList();
return strings.flatMap(string -> ServerResponse.ok()
.contentType(APPLICATION_JSON)
.bodyValue(string));

Related

Spring Cloud Gateway: Post Filter Web Client Request

We are using Spring Cloud Gateway in order to route requests to multiple underlying services. The calls to these underlying services will be sequential and potentially feed into one another (response from one being used in the request for the next). We have a working solution for when we need to make those requests sequentially BEFORE the main request, but after the main request we are having problems with feeding the response of one proxy request into the request of the next.
The way we have planned on feeding the response from one request to the next is by making the request using a WebClient in the GatewayFilter and storing the response string in the exchange's attribute store. Then during the next proxy request we supply an attribute name to optionally pull the request body from. This works well when using "pre" filters, because the first proxy request is built, executed and response cached before the second request is built and executed, so the chain of attributes works as expected. The problem comes when working with "post" filters. In the post proxy, the web client requests are all built before the subsequent request has finished. So the attribute store never has the response from the previous request, meaning the next request doesn't work as intended because it doesn't have a valid request body.
My understanding was that calling chain.filter(exchange).then(Mono.fromRunnable{ ... }) would cause the .then logic to execute only after the prior filters had fully completed. This does not seem to be the case. In other filter types like logging, response manipulation, etc the post filters execute in the correct order, but when creating a WebClient they don't seem to.
Does anyone have any ideas on how this desired behavior might be achievable?
Pre-Proxy Filter Code(Working):
class PreProxyGatewayFilterFactory: AbstractGatewayFilterFactory<PreProxyGatewayFilterFactory.Params>(Params::class.java) {
override fun apply(params: Params): GatewayFilter {
return OrderedGatewayFilter(
{ exchange, chain ->
ServerWebExchangeUtils.cacheRequestBody(exchange){
val cachedExchange = exchange.mutate().request(it).build()
executeRequest(cachedExchange, params)
.map { response ->
val body = response.body.toString()
cacheResponse(
response.body.toString(),
params.cachedResponseBodyAttributeName,
cachedExchange
)
}
.flatMap(chain::filter)
}
}, params.order)
}
private fun cacheResponse(response: String, attributeName: String?, exchange: ServerWebExchange): ServerWebExchange{
if(!attributeName.isNullOrBlank()){
exchange.attributes[attributeName] = response
}
return exchange
}
private fun executeRequest(exchange: ServerWebExchange, params: Params): Mono<ResponseEntity<String>>{
val request = when(exchange.request.method){
HttpMethod.PUT -> WebClient.create().put().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.POST -> WebClient.create().post().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.GET -> WebClient.create().get().uri(params.proxyPath)
HttpMethod.DELETE -> WebClient.create().delete().uri(params.proxyPath)
else -> throw Exception("Invalid request method passed in to the proxy filter")
}
return request.headers { headers ->
headers.addAll(exchange.request.headers)
headers.remove(CONTENT_LENGTH)
}
.exchange()
.flatMap{ response ->
response.toEntity(String::class.java)
}
}
private fun createProxyRequestBody(exchange: ServerWebExchange, attributeName: String?): BodyInserter<out Flux<out Any>, ReactiveHttpOutputMessage> {
val cachedBody = attributeName?.let { attrName ->
exchange.getAttributeOrDefault<String>(attrName, "null")
} ?: "null"
return if(cachedBody != "null"){
BodyInserters.fromPublisher(Flux.just(cachedBody), String::class.java)
} else {
BodyInserters.fromDataBuffers(exchange.request.body)
}
}
data class Params(
val proxyPath: String = "",
val cachedRequestBodyAttributeName: String? = null,
val cachedResponseBodyAttributeName: String? = null,
val order: Int = 0
)
}
Post-Proxy Filter Code (Not Working)
class PostProxyGatewayFilterFactory: AbstractGatewayFilterFactory<PostProxyGatewayFilterFactory.Params>(Params::class.java) {
override fun apply(params: Params): GatewayFilter {
return OrderedGatewayFilter(
{ exchange, chain ->
ServerWebExchangeUtils.cacheRequestBody(exchange){
val cachedExchange = exchange.mutate().request(it).build()
//Currently using a cached body does not work in post proxy
chain.filter(cachedExchange).then( Mono.fromRunnable{
executeRequest(cachedExchange, params)
.map { response ->
cacheResponse(
response.body.toString(),
params.cachedResponseBodyAttributeName,
cachedExchange
)
}
.flatMap {
Mono.empty<Void>()
}
})
}
}, params.order)
}
private fun cacheResponse(response: String, attributeName: String?, exchange: ServerWebExchange): ServerWebExchange{
if(!attributeName.isNullOrBlank()){
exchange.attributes[attributeName] = response
}
return exchange
}
private fun executeRequest(exchange: ServerWebExchange, params: Params): Mono<ResponseEntity<String>>{
val request = when(exchange.request.method){
HttpMethod.PUT -> WebClient.create().put().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.POST -> WebClient.create().post().uri(params.proxyPath).body(createProxyRequestBody(exchange, params.cachedRequestBodyAttributeName))
HttpMethod.GET -> WebClient.create().get().uri(params.proxyPath)
HttpMethod.DELETE -> WebClient.create().delete().uri(params.proxyPath)
else -> throw Exception("Invalid request method passed in to the proxy filter")
}
return request.headers { headers ->
headers.addAll(exchange.request.headers)
headers.remove(CONTENT_LENGTH)
}
.exchange()
.flatMap{ response ->
response.toEntity(String::class.java)
}
}
private fun createProxyRequestBody(exchange: ServerWebExchange, attributeName: String?): BodyInserter<out Flux<out Any>, ReactiveHttpOutputMessage> {
val cachedBody = attributeName?.let { attrName ->
exchange.getAttributeOrDefault<String>(attrName, "null")
} ?: "null"
return if(cachedBody != "null"){
BodyInserters.fromPublisher(Flux.just(cachedBody), String::class.java)
} else {
BodyInserters.fromDataBuffers(exchange.request.body)
}
}
data class Params(
val proxyPath: String = "",
val cachedRequestBodyAttributeName: String? = null,
val cachedResponseBodyAttributeName: String? = null,
val order: Int = 0
)
}
Was finally able to get to a working solution for the post filter proxy pulling it's request body from the attributes. It was a relatively straightforward fix that I just couldn't find the answer to. Instead of using chain.filter(exchange).then(Mono.fromRunnable { ...execute proxy request...}) I just needed to use chain.filter(exchange).then(Mono.defer { ...execute proxy request...}).

Kotlin - chain of scope functions

I'm new to Kotlin language and I would like to know if it's a good practice have a chain of scope functions. As example, I'm writing a function that calls some API (an utilitary function), parse the string response to specific object, do a small verification and return an object.
Is a good practice have a chain of scope functions like this code above?
fun execRequest(endpoint: String, method: String = "GET", body: String? = ""): String =
defaultHttpRequestBuilder()
.uri(URI.create(endpoint))
.method(method, HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
.run { httpClient.send(this, HttpResponse.BodyHandlers.ofString()) }
.let { it.body() }
fun processLoginRequest(challenge: String) =
execRequest(buildEndpoint("login", challenge))
.let {
mapper.readValue<LoginResponse>(it)
}
.let {
val authSituation = Auth(it.skip, it.challenge)
if (it.skip) {
val acceptResponse = acceptLoginRequest(challenge, it.subject)
authSituation.redirectTo = acceptResponse.redirectTo
}
authSituation
}
This code looks awful in my opinion. Is there another way to write it in a "Kotlin way"?
I think this question is likely to be closed due to answers being a matter of opinion, but since you asked about my comment, here is how you could break up the first function.
fun execRequest(endpoint: String, method: String = "GET", body: String? = ""): String {
val request = defaultHttpRequestBuilder()
.uri(URI.create(endpoint))
.method(method, HttpRequest.BodyPublishers.ofString(body))
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
return response.body()
}
I might break up the second function like this
fun processLoginRequest(challenge: String): Auth {
val httpResponse = execRequest(buildEndpoint("login", challenge))
val loginResponse: LoginResponse = mapper.readValue(httpResponse)
return Auth(loginResponse.skip, loginResponse.challenge)
.apply {
if (loginResponse.skip)
redirectTo = acceptLoginRequest(challenge, it.subject).redirectTo
}
}

Can you get the response data from Volley outside of the stringRequest variable in Kotlin?

I want to get the Volley stringRequest response from my website outside of the variable.
val queue = Volley.newRequestQueue(this)
val url = ""
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
var obj = JSONObject(response) <-- cant access this variable outside of stringRequest
},
Response.ErrorListener { textView3.text = "That didn't work!" })
stringRequest.body.toString() <-- cant covert null to string
stringRequest.headers.toString() <-- output is {}
//here i want to do something like
if (response == "True") {
//do something
}
On the website that I'm accessing there is nothing more than {"check":"True"}
This implementation is asynchronous in the way its built in, What you actually can do to look it more like synchronous is if you use coroutines in your project, You can use suspendCoroutine, see
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/suspend-coroutine.html
Example:
suspend fun getData(url: String) = suspendCoroutine<String?> { cont ->
val queue = Volley.newRequestQueue(this)
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
cont.resume(response)
},
Response.ErrorListener { cont.resume(null) })
queue.add(stringRequest)
}
Now you can access the response string out of Response.Listener()
Edit: Additionally you can do cont.resumeWithException(e) instead if you dont want to return nullable expression and check nullability every time you use this function.

RxJava Filter on Error

This question is loosely related to this question, but there were no answers. The answer from Bob Dalgleish is close, but doesn't support the potential error coming from a Single (which I think that OP actually wanted as well).
I'm basically looking for a way to "filter on error" - but don't think this exists when the lookup is RX based. I am trying to take a list of values, run them through a lookup, and skip any result that returns a lookup failure (throwable). I'm having trouble figuring out how to accomplish this in a reactive fashion.
I've tried various forms of error handling operators combined with mapping. Filter only works for raw values - or at least I couldn't figure out how to use it to support what I'd like to do.
In my use case, I iterate a list of IDs, requesting data for each from a remote service. If the service returns 404, then the item doesn't exist anymore. I should remove non-existing items from the local database and continue processing IDs. The stream should return the list of looked up values.
Here is a loose example. How do I write getStream() so that canFilterOnError passes?
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import org.junit.Test
class SkipExceptionTest {
private val data: Map<Int, String> = mapOf(
Pair(1, "one"),
Pair(2, "two"),
Pair(4, "four"),
Pair(5, "five")
)
#Test
fun canFilterOnError() {
getStream(listOf(1, 2, 3, 4, 5))
.subscribeOn(Schedulers.trampoline())
.observeOn(Schedulers.trampoline())
.test()
.assertComplete()
.assertNoErrors()
.assertValueCount(1)
.assertValue {
it == listOf(
"one", "two", "four", "five"
)
}
}
fun getStream(list: List<Int>): Single<List<String>> {
// for each item in the list
// get it's value via getValue()
// if a call to getValue() results in a NotFoundException, skip that value and continue
// mutate the results using mutate()
TODO("not implemented")
}
fun getValue(id: Int): Single<String> {
return Single.fromCallable {
val value: String? = data[id]
if (value != null) {
data[id]
} else {
throw NotFoundException("dat with id $id does not exist")
}
}
}
class NotFoundException(message: String) : Exception(message)
}
First .materialize(), then .filter() on non-error events, then .dematerialize():
getStream(/* ... */)
.materialize()
.filter(notification -> { return !notification.isOnError(); })
.dematerialize()
I ended up mapping getValue() to Optional<String>, then calling onErrorResumeNext() on that and either returning Single.error() or Single.just(Optional.empty()). From there, the main stream could filter out the empty Optional.
private fun getStream(list: List<Int>): Single<List<String>> {
return Observable.fromIterable(list)
.flatMapSingle {
getValue(it)
.map {
Optional.of(it)
}
.onErrorResumeNext {
when (it) {
is NotFoundException -> Single.just(Optional.empty())
else -> Single.error(it)
}
}
}
.filter { it.isPresent }
.map { it.get() }
.toList()
}

Transform an array of value with async function and Reactive-Cocoa

I have an array of String and I want to modify it by calling an asynchronous function on each value.
I tried something but it's just creating a RACSequence of RACSignal and not an array of modified String
func modifyValueAsynchronously(inputValue: String, completion: (String -> Void)) -> RACSignal {
return RACSignal.createSignal() { subscriber in
doAServerCall(inputValue) { outputValue in
subscriber.sendNext(outputValue)
}
}
}
let value: NSArray = ["a", "b"]
let result = value.rac_sequence.map({ value in
return self.doRequest(value as! String)
})
As you said, you want to perform an asynchronous function on each of your string values in array. Because of the fact that you do it async, the final result will also arrive asynchronous. After getting RACSequence of RACSignal, you want to wait until each signal provides you with value and then gather it all into array.
let transformation = { (input: String) -> RACSignal in
return RACSignal.createSignal { subscriber in
subscriber.sendNext(input + "a")
return nil
}
}
let array: NSArray = ["a", "b", "c", "d"]
let sequence = array.rac_sequence.map({ (element) in
return transformation(element as! String)
})
RACSignal.combineLatest(sequence).subscribeNext { (element) in
let array = (element as! RACTuple).allObjects()
print("Elements from array \(array) \n")
}
Lets take this example. Transformation closure will asynchronously add an 'a' letter to each string. After creating a sequence, I add combineLatest operator which will give me result as soon as each element from sequence provides me with it's result. As soon as I get all results, I get a RACTuple inside subscribeNext method and I can make an array out of it.