Spring-Integration-DSL: Nested Scatter Gather hangs - spring-integration-dsl

Here is a broken, but executable example code:
#Bean
IntegrationFlow testFlow() {
return IntegrationFlows
.from(Http.inboundChannelAdapter("test")
.requestMapping(mapping -> mapping.methods(HttpMethod.GET))
.get())
.scatterGather(
scatterer -> scatterer
.applySequence(true)
.recipientFlow(flow -> flow
.scatterGather(
scatterer1 -> scatterer1
.applySequence(true)
.recipientFlow(IntegrationFlowDefinition::bridge),
gatherer -> gatherer.outputProcessor(MessageGroup::getOne))
.log(INFO, m -> "THIS HAPPENS")),
gatherer -> gatherer.outputProcessor(MessageGroup::getOne))
.log(INFO, m -> "THIS NEVER HAPPENS")
.get();
}
The expected output is:
THIS HAPPENS
THIS NEVER HAPPENS
The actual output is:
THIS HAPPENS
I found this identical looking issue on Github, but it claims it has been fixed with versions 5.1.10 and 5.2.4. I am running spring-boot-starter-integration 5.5.0 which includes spring-integration-core of the same version.
What can I do to get this nested scatter gather to work ? Is it a bug with the DSL or with my code ?

After having a similar problem using only one level of scatter-gather, I realized it was the log message that was blocking the output from being returned to the parent flow. Replace .log() with .logAndReply() or .log().bridge() and everything should work again.
Like this:
#Bean
IntegrationFlow testFlow() {
return IntegrationFlows
.from(Http.inboundChannelAdapter("test")
.requestMapping(mapping -> mapping.methods(HttpMethod.GET))
.get())
.scatterGather(
scatterer -> scatterer
.applySequence(true)
.recipientFlow(flow -> flow
.scatterGather(
scatterer1 -> scatterer1
.applySequence(true)
.recipientFlow(IntegrationFlowDefinition::bridge),
gatherer -> gatherer.outputProcessor(MessageGroup::getOne))
.logAndReply(INFO, m -> "THIS HAPPENS")), // this fixes the problem
gatherer -> gatherer.outputProcessor(MessageGroup::getOne))
.log(INFO, m -> "THIS NEVER HAPPENS")
.get();
}

Related

Quarkus RestEasy reactive InputStream response using wrong writer

I tried to optimize a reactive endpoint streaming input of an audio file based on Quarkus REST Score Console. I replaced generic Response with Reactive RestResponse. It increased the score to 100 but it is using ServerStringMessageBodyHandler instead of ServerInputStreamMessageBodyHandler now. Is there a way to tell Quarkus what MessageBodyHandler to use? Now it is calling .toString() method on inputStream object. I tried to return directly ByteArray, but the issue is the same. Any idea what is going on wrong here?
#GET
#Path("/{lectureId}/stream")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getLectureStreamById(
#RestHeader("Range") rangeParam: String?,
#RestPath lectureId: LectureId
): Uni<RestResponse<InputStream>> {
return lectureAudioService.getAudioFile(lectureId).map { lectureStream ->
downloadResponse(ByteArrayInputStream(lectureStream.data), filename = "$lectureId.mp3").build()
}
}
fun downloadResponse(
data: InputStream,
filename: String,
): ResponseBuilder<InputStream> {
return ResponseBuilder.ok(data)
.header("Content-Disposition", "attachment;filename=$filename")
}
Based on answer in github issue it should be fixed in upcoming releases but original approach was not good as well because it blocked event loop. Better approach will be:
#Path("/{filename}/async-file")
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getAsyncFile(filename: String): Uni<RestResponse<AsyncFile>> {
return Uni.createFrom().emitter { emitter: UniEmitter<in RestResponse<AsyncFile>> ->
vertx.fileSystem().open(
"$filename.mp3", OpenOptions()
) { result: AsyncResult<AsyncFile> ->
if (result.succeeded()) emitter.complete(
ResponseBuilder.ok(result.result()).header("Content-Disposition", "attachment;filename=$filename.mp3").build()
) else emitter.fail(result.cause())
}
}
}
Thanks to #geoand

What is the usecase of Flux.repeat() without any parameter?

I was looking at the following code and was confused with what the repeat() operator is doing here.
return inboundFlux
.groupBy(record -> record.receiverOffset().topicPartition())
.flatMap(partitionFlux -> partitionFlux
.concatMap(el -> Flux.just(el)
.doOnNext(receiverRecord -> {
log.info("Starting to process {}", receiverRecord);
messageProcessor.processMessage(receiverRecord);
receiverRecord.receiverOffset().acknowledge();
log.info("Message acknowledged");
})
.doOnError(e -> log.error("ERRRRRRROOORRRRRR"))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(5)).maxBackoff(Duration.ofSeconds(20)).transientErrors(true))
.onErrorResume(e -> {
// code to handle retry exhaustion
})
).repeat()
)
.subscribeOn(scheduler)
.subscribe();

How to WebFluxTest whit ReactiveSecurityContexHolder

Here a simple Class reading the Principal user from the spring security context:
public Mono<Void> getAndCheckAccessRights(Integer agencyKey) {
return ReactiveSecurityContextHolder.getContext()
.map(securityContext -> getAccessRights(agencyKey, securityContext.getAuthentication().getName()))
.switchIfEmpty(Mono.defer(() -> {
log.error("No security context found!");
throw new AuthorizationException("No security context found!");
}))
.flatMap(accessRightsDtoMono -> checkAccessRights(accessRightsDtoMono))
.then();
}
private Mono<AccessRightsDto> getAccessRights(Integer agencyKey, String bensl) {
return dataServiceWebClient.get()
.uri("/access_rights/" + agencyKey + "/" + bensl)
.retrieve()
.bodyToMono(AccessRightsDto.class)
.switchIfEmpty(Mono.defer(() -> {
log.error("No user found!");
throw new AuthorizationException("No user found!");
}));
}
While testing it does not what it is supposed to do, execution just jump code lines over without executing method streams in .map or .flatMap,
Logs are not printed and there is no debug logging on any level, test just runs as everithing is terminated correctly, i dont have any clue whatsovewer why this happen:
#WebFluxTest(AccessRightService.class)
...
#Test
#WithMockUser
void getAndCheckAccessRights_NOT_AUTHORIZED() throws JsonProcessingException {
AccessRightsDto testAccessRightsDto = AccessRightsDto
.builder(123456789, "test", "test", PUBLISH, PUBLISH, PUBLISH, PUBLISH, PUBLISH,
PUBLISH, PUBLISH, PUBLISH, NO_ACCESS)
.build();
MockResponse response = new MockResponse();
response.setResponseCode(HttpStatus.OK.value()).setBody(objectMapper.writeValueAsString(testAccessRightsDto));
mockWebServer.enqueue(response);
assertThrows(AuthorizationException.class, () -> accessRightService.getAndCheckAccessRights(123456789));
}
Off course when running the application it just work correctly as expected, testing is strange!
App running with spring boot 2.2.2 and okhttp3 mockwebserver.
My bad, I forgot to put.block() after getAndCheckAccessRights which propagate the input of getting the object, whitout that nothing will happen.
It is actually not that clear how does it works in case of Netty, because it does not accept .block()'s but on tests is ok to call it.

How to customize logging in F# Saturn framework?

I created a default SAFE app as described here.
Removing redundant stuff, the server is this:
open Giraffe
open Saturn
let webApp = scope {
get "/api/init" (fun next ctx ->
task {
let number = 42
let! counter = task { return number }
return! Successful.OK counter next ctx
})
}
let app = application {
url ("http://0.0.0.0:8085/")
router webApp
memory_cache
use_static "../Client/public"
use_gzip
}
run app
Now, when running app, I see some logging in the console, basically incoming requests:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:8085/api/init
How do I customize the logging? The docs are as scarce as possible, no examples. I need something simple, like logging "going to return 42...".
Or at least some links with cases.
You can pull the fully blown ILogger object from the context, ctx.
Open Microsoft.Extensions.Logging module and then you can do things like this:
let webApp = scope {
get "/api/init" (fun next ctx ->
task {
let logger = ctx.GetLogger();
let number = 42
logger.Log(LogLevel.Information, "Going to return " + number.ToString())
let! counter = task { return number }
return! Successful.OK counter next ctx
})
}
This will bring to your console:
info: object[0]
Going to return 42
I do not have any proper references. I found a similar thing at the Github of Giraffe server for which Saturn is basically a set of abstractions.
Logging configuration is built into v0.9 at least. I used the case below for myself to suppress most of the logging.
open Microsoft.Extensions.Logging
let app = application {
url ("http://0.0.0.0:8085/")
use_router webApp
logging (fun logger -> logger.SetMinimumLevel LogLevel.Critical |> ignore)
}

Using node modules from fable

I'm trying to import a node module in my fable code. Being new to fable I did expect so problems and understanding the import flow seems to be one of those. I have the below code which compiles fine but fails run time with Cannot read property 'request' of undefined on the line of the printfn statement
module Session =
let inline f (f: 'a->'b->'c->'d) = Func<_,_,_,_> f
[<Import("default","request")>]
type Http =
abstract request : string -> System.Func<obj,obj,obj,unit> -> unit
let http : Http = failwith "js only"
let start () =
http.request "http://dr.dk" (ff (fun error response body ->
printfn "%A" body
))
do
start()
I was able to get your example working in the Fable REPL:
open System
open Fable
open Fable.Core
open Fable.Core.JS
open Fable.Core.JsInterop
type RequestCallback = Func<obj, obj, obj, unit>
type Request = Func<string, RequestCallback, unit>
[<ImportDefault("request")>]
let request : Request = jsNative
let start () =
request.Invoke("http://dr.dk", (fun error response body ->
console.log("error", error)
console.log("response", response)
console.log("body", body)
))
start ()
And here is the JavaScript that it generated:
import request from "request";
import { some } from "fable-library/Option.js";
export function start() {
request("http://dr.dk", (error, response, body) => {
console.log(some("error"), error);
console.log(some("response"), response);
console.log(some("body"), body);
});
}
start();
Note that there are bindings for many modules already. For this particular task, I would suggest using Fable.Fetch. If you want a library that works in the browser and .NET, try Fable.SimpleHttp.

Resources