Post API using Ratpack and Groovy giving 405 Error and RxJava methods not working - api

I am building an API using Ratpack and Groovy. The POST API is always giving:
405-Method not Found Error
This is a snippet from POST Endpoint Handler. In this code, promiseSingle, then, observe, map, doOnNext, doOnError, etc.
RxJAVA functions are not working. Is there any reason why RxJava methods are not working?
saveJsonAsData(context, id)
.promiseSingle()
.then { Data updateddata ->
context.response.headers
.add(HttpHeaderNames.LOCATION, "/api/save/${updateddata.id}/${updateddata.value}")
context.response.status(HttpResponseStatus.CREATED.code())
.send()
}
}
protected Observable<Data> saveJsonAsData(GroovyContext context, String id) {
context.request.body.observe()
.map { TypedData typedData -> extractData(context, typedData) }
.doOnNext { Data data ->
data.id = id
validatorWrapper.validate(data)
}
.flatMap(data.&save as Func1)
.doOnError { Throwable throwable -> log.error("Error saving data", throwable) }
}

The issue is not so much with Rx as it is with the usage of the Context.
You should try to keep the response handling logic within your Handler, that is don't pass the Context around, rather get the objects you need and pass them to your services.
As an example
path('myendpoint') { MyRxService service ->
byMethod {
get {
// do something when request is GET
}
post {
request.body.map { typedData ->
extractItem(typeData) // extract your item from the request first
}.flatMap { item ->
service.saveJsonAsItemLocation(item).promiseSingle() // then once it's extracted pass the item to your "saveJsonAsItemLocation" method
}.then { ItemLocationStore updatedItem ->
response.headers.add(HttpHeaderNames.LOCATION, "/itemloc/v1/save/${updatedItem.tcin}/${updatedItem.store}")
context.response.status(HttpResponseStatus.CREATED.code()).send()
}
}
}
}
My guess is that you have something like this:
get {
// get stuff
}
post {
// post stuff
}
The reason this doesn't work is that Ratpack doesn't use Routing Table for handling incoming requests, instead it uses chain delegation. The get {} binds to root path and GET http method and post {} binds to root path and POST http method. Because get {} matches the path, Ratpack considers the handler matched and since the handler is for GET it considers it a 405.
There are chain methods available that binds regardless of HTTP Method such as all {} and path {}. Chain#all will handle all paths and methods where as Chain#path(String) matches against specific path.
Hope this helps.

Related

Why is the value not entering the list?

At 'urichecking2' log, I can see there is value. But in 'uriChecking' the uriList is null.
why the uriList.add not work??
private fun getPhotoList() {
val fileName = intent.getStringExtra("fileName")
Log.d("fileNameChecking", "$fileName")
val listRef = FirebaseStorage.getInstance().reference.child("image").child(fileName!!)
var tmpUrl:Uri = Uri.parse(fileName)
Log.d("firstTmpUri","$tmpUrl")
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
tmpUrl = task.result
Log.d("secondTmpUri","$tmpUrl")
Log.d("urichecking2","$task.result")
uriList.add(task.result)
} else {
}
}.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
}
Log.d("thirdTmpUri","$tmpUrl")
Log.d("urichecking", "$uriList")
}
If I do this, the log is output in the order of first, third, and second, and the desired value is in second, but when third comes out, it returns to the value of first.
The listAll method (like most cloud APIs these days, including downloadUrl which you also use) is asynchronous, since it needs to make a call to the server - which may take time. This means the code executes in a different order than you may expect, which is easiest to see if you add some logging:
Log.d("Firebase","Before starting listAll")
listRef.listAll()
.addOnSuccessListener { listResult ->
Log.d("Firebase","Got listResult")
}
Log.d("Firebase","After starting listAll")
When you run this code it outputs:
Before starting listAll
After starting listAll
Got listResult
This is probably not the order you expected, but it perfectly explains why you can't see the list result. By the time your Log.d("urichecking", "$uriList") runs, none of the uriList.add(task.result) has been called yet.
The solution for this is always the same: any code that needs the list result, has to be inside the addOnCompleteListener callback, be called from there, or be otherwise synchronized.
So in its simplest way:
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
uriList.add(task.result)
Log.d("urichecking", "$uriList")
}
}
}
}
This is an incredibly common mistake to make if you're new to programming with asynchronous APIs, so I recommend checking out
Asynchronous programming techniques in the Kotlin language guide
How to get URL from Firebase Storage getDownloadURL
Can someone help me with logic of the firebase on success listener
Why does my function that calls an API or launches a coroutine return an empty or null value?

Send upstream exception in SharedFlow to collectors

I want to achieve the following flow logic in Kotlin (Android):
Collectors listen to a List<Data> across several screens of my app.
The source-of-truth is a database, that exposes data and all changes to it as a flow.
On the first initialization the data should be initialized or updated via a remote API
If any API exception occurs, the collectors must be made aware of it
In my first attempt, the flow was of the type Flow<List<Data>>, with the following logic:
val dataFlow = combine(localDataSource.dataFlow, flow {
emit(emptyList()) //do not wait for API on first combination
emit(remoteDataSource.suspendGetDataMightThrow())
}) { (local, remote) ->
remote.takeUnless { it.isEmpty() }?.let { localDataSource.updateIfChanged(it) }
local
}.shareIn(externalScope, SharingStarted.Lazily, 1)
This worked fine, except when suspendGetDataMightThrow() throws an exception. Because shareIn stops propagating the exception through the flow, and instead breaks execution of the externalScope, my collectors are not notified about the exception.
My solution was to wrap the data with a Result<>, resulting of a flow type of Flow<Result<List<Data>>>, and the code:
val dataFlow = combine(localDataSource.dataFlow, flow {
emit(Result.success(emptyList())) //do not wait for API on first combination
emit(runCatching { remoteDataSource.suspendGetDataMightThrow() })
}) { (local, remote) ->
remote.onSuccess {
data -> data.takeUnless { it.isEmpty() }?.let { localDataSource.updateIfChanged(it) }
}
if (remote.isFailure) remote else local
}.shareIn(externalScope, SharingStarted.Lazily, 1)
I can now collect it as follows, and the exception is passed to the collectors:
dataRepository.dataFlow
.map { it.getOrThrow() }
.catch {
// ...
}
.collect {
// ...
}
Is there a less verbose solution to obtain the exception, than to wrap the whole thing in a Result?
I am aware that there are other issues with the code (1 API failure is emitted forever). This is only a proof-of-concept to get the error-handling working.

Is this the correct way to process results through the Spring Integration Flow?

I am currently working on a personal project - in which I need my Spring application to take queries from an EMQX (MQTT Server) and query its data for corresponding results, and then push the results to a topic with the query UUID.
This is working - after many hours understanding how the Spring Integration framework works. But I think the way in which the handler is using "block" is incorrect - and not in keeping with the manner in which the Integration Flow should operate. Whilst this works I do want to make sure it is being done properly - out of respect for the work - and to avoid future issues.
The code snippet below should be enough to understand what it is that I'm trying to achieve - and where the potential issue lies.
#Bean
fun mqttInFlow() : Publisher<Message<String>> {
return IntegrationFlows.from(inbound())
.handle<String> { payload, headers ->
val emotionalOutput: EmotionalOutput = gson.fromJson(payload, EmotionalOutput::class.java)
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map {
MessageBuilder.withPayload(gson.toJson(it))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, "query/" + it.query_uuid).build()
}.block()
}
.channel(outgoingChannel())
.toReactivePublisher()
}
EDIT - Thanks for the advice - here is what I understood to be the potential edit for the Kotlin DSL solution - this is now producing an error - complaining that an output-channel or replyChannel was not available - nothing outside of the this function has been changed.
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
wireTap {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid)
}
}
}
}
channel("outgoingChannel")
}
Exception is :
exception is org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
Although I have many years experience with Java - this approach is new - so thank you very much for your assistance. It's appreciated. If the whole class would be useful - I can post that.
EDIT
Here is the Configuration file - which might give a better insight into what might be causing this secondary error -
021-03-28 21:59:48.008 ERROR 84492 --- [T Call: divnrin] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: error occurred in message handler [bean 'mqttOutbound'; defined in: 'class path resource [io/divnr/appserver/configuration/MQTTConfiguration.class]'; from source: 'org.springframework.core.type.classreading.SimpleMethodMetadata#4a9419d7']; nested exception is java.lang.IllegalArgumentException: This default converter can only handle 'byte[]' or 'String' payloads; consider adding a transformer to your flow definition, or provide a BytesMessageMapper, or subclass this converter for reactor.core.publisher.MonoMapFuseable payloads, failedMessage=GenericMessage [payload=MonoMapFuseable, headers={mqtt_receivedRetained=false, mqtt_id=0, mqtt_duplicate=false, id=c5a75283-c0fe-ebac-4168-dabddd989da9, mqtt_receivedTopic=source/d9e50e8f-67e0-4505-7ca2-4d05b1242207, mqtt_receivedQos=0, timestamp=1616961588004}]
at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:65)
at
The full class is provided here.
#Configuration
#EnableIntegration
#IntegrationComponentScan
class MQTTConfiguration(val emotionalPrintService: EmotionalPrintService,
val gson: Gson,
val applicationConfiguration: ApplicationConfiguration) {
#Bean
fun mqttServiceFactory() : MqttPahoClientFactory {
return DefaultMqttPahoClientFactory().apply {
connectionOptions = MqttConnectOptions().apply {
serverURIs = arrayOf<String>(applicationConfiguration.mqttServerAddress)
}
}
}
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}
channel(outgoingChannel())
}
#Bean
#ServiceActivator(requiresReply = "false", inputChannel = "outgoingChannel")
fun mqttOutbound(): MessageHandler {
val messageHandler = MqttPahoMessageHandler("divnrout", mqttServiceFactory())
messageHandler.setAsync(true)
return messageHandler
}
#Bean
fun outgoingChannel() : FluxMessageChannel {
return FluxMessageChannel()
}
#Bean
fun inbound(): MessageProducerSupport {
return MqttPahoMessageDrivenChannelAdapter("divnrin", mqttServiceFactory(),
"source/" + applicationConfiguration.sourceUuid).apply {
setConverter(DefaultPahoMessageConverter())
setQos(1)
}
}
}
You indeed don't need that block() in the end of your handle(). You just can return the Mono from that emotionalPrintService.populateEmotionalOutput() and the framework will take for you about the proper subscription and back-pressure handling.
What you would need yet is to make that outgoingChannel() as a FluxMessageChannel.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/reactive-streams.html#reactive-streams
Plus consider to move your IntegrationFlow solution to the proper Kotlin DSL: https://docs.spring.io/spring-integration/docs/current/reference/html/kotlin-dsl.html#kotlin-dsl
Also: when it is a FluxMessageChannel in the end of flow, there is no reason to worry about a toReactivePublisher() - the FluxMessageChannel is a Publisher<Message<?>> by itself.
UPDATE
The problem is here:
handle<String>( { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}) { async(true) }
See that async(true) option. Unfortunately in the current version we don't let it to process reactive reply in the reactive manner by default. You have to say that you'd like to be an async at this end-point. So, your Publisher reply and and FluxMessageChannel as an output is going to do the proper trick.

How to make several synchronuous call of rxjava Single

I have difficulties making sequential calls of RxJava Single observerable. What I mean is that I have a function that makes http request using retrofit that returns a Single.
fun loadFriends(): Single<List<Friend>> {
Log.d("msg" , "make http request")
return webService.getFriends()
}
and if I subscribe from several places at the same time:
loadFriends().subscribeOn(Schedulers.io()).subscribe()
loadFriends().subscribeOn(Schedulers.io()).subscribe()
I want that loadFriends() makes only one https request but in this case I have two http request
I know how to solve this problem in blocking way:
The solution is to make loadFriends() blocking.
private val lock = Object()
prival var inMemoryCache: List<Friends>? = null
fun loadFriends(): Single<List<Friend>> {
return Single.fromCallable {
if(inMemoryCache == null) {
synchronize(lock) {
if(inMemoryCache == null) {
inMemoryCache = webService.getFriends().blockingGet()
}
}
}
inMemoryCache
}
But I want to solve this problem in a reactive way
You can remedy this by creating one common source for all your consumers to subscribe to, and that source will have the cache() operator invoked against it. The effect of this operator is that the first subscriber's subscription will be delegated downstream (i.e. the network request will be invoked), and subsequent subscribers will see internally cached results produced as a result of that first subscription.
This might look something like this:
class Friends {
private val friendsSource by lazy { webService.getFriends().cache() }
fun someFunction() {
// 1st subscription - friends will be fetched from network
friendsSource
.subscribeOn(Schedulers.io())
.subscribe()
// 2nd subscription - friends will be fetched from internal cache
friendsSource
.subscribeOn(Schedulers.io())
.subscribe()
}
}
Note that the cache is indefinite, so if periodically refreshing the list of friends is important you'll need to come up with a way to do so.

RxJava/RxKotlin: Wait for value to be fetched async and then provide to all subscribers

I have a scenario where I have to fetch some string asynchronously. I would like to create a method where I can listen to when this value is fetched successfully and then provided to the listener.
Now, this can be done easily via many ways including a callback listener or a lambda.
But what do I use so that all subsequent calls to this method, also provide the string back - without having to fetch it again, as it has already been fetched once. So a solution where the listener is still attached but is provided the value right away since it is available.
I know how to do this via old fashioned callback listeners, where the value is stored and then for subsequent calls it can be returned right away via the callback.
But is there a more compact/sophisticated way to do it, let's say via Rx?
Thanks.
I think you can just use cache() operator. It will be something like this:
val value: Single<String> by lazy {
// emulation of your callback
Single.create<String> { emitter ->
Thread.sleep(1000)
println("do some work")
emitter.onSuccess("test")
}.cache()
}
fun main() {
value.subscribe { str -> println(str) }
value.subscribe { str -> println(str) }
value.subscribe { str -> println(str) }
Thread.sleep(2000)
// output:
// do some work
// test
// test
// test
}