Spring WebFlux - WebGraphQlInterceptor has empty request data - spring

I would need to retrieve data from query headers for use in the GraphQl query controller. I searched around a bit and found the WebGraphQlInterceptor, but realized that when the WebGraphQlRequest argument is invoked it is completely devoid of information.
For example, this code:
#Component
class WebRequestInterceptor : WebGraphQlInterceptor {
val logger: Logger by lazy { LoggerFactory.getLogger(WebRequestInterceptor::class.java) }
override fun intercept(request: WebGraphQlRequest, chain: WebGraphQlInterceptor.Chain): Mono<WebGraphQlResponse> {
logger.info("URI", request.uri.toString())
logger.info("HEADERS", request.headers.toString())
logger.info("DOCUMENT", request.document)
return chain.next(request)
}
}
Returns:
2022-09-12 10:57:11.747 INFO 1 --- [or-http-epoll-1] b.s.b.gateway.auth.WebRequestInterceptor : URI *empty string*
2022-09-12 10:57:11.748 INFO 1 --- [or-http-epoll-1] b.s.b.gateway.auth.WebRequestInterceptor : HEADERS *empty string*
2022-09-12 10:57:11.748 INFO 1 --- [or-http-epoll-1] b.s.b.gateway.auth.WebRequestInterceptor : DOCUMENT *empty string*
P.S. The same thing happens if I try to log a single headers element using the request.headers.getFirst(..) function.

Related

how to pass a parameter in the Header with multipartFormData

#PostMapping(value = "/uploadCV" , consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<?> uploadCV(#RequestHeader("token") String token,
#RequestParam("file") MultipartFile cvFile) throws Exception {
log.info(token + cvFile.getOriginalFilename()));
return curriculumService.addCurriculum(token, pdfFile);
}
If i use MediaType.MULTIPART_FORM_DATA_VALUE don't get the value "token" :
2022-11-11 16:13:28.040 WARN 7283 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Required request header 'token' for method parameter type String is not present]
If i use MediaType.MULTIPART_MIXED_VALUE don't get file:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
how can i do?
The code that you have written with MediaType.MULTIPART_FORM_DATA_VALUE is absolutely correct. It is very clear from error message what is the issue.
2022-11-11 16:13:28.040 WARN 7283 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Required request header 'token' for method parameter type String is not present]
Issue:
Code is expecting header name with key token should present in the request but it is not present.
Solution:
Please add header name with key token like this and try again.

Spring Boot : Cannot replace Jackson with Gson , jackson still appears in logs

Gson dependency
implementation("com.google.code.gson:gson:2.9.0")
application-dev.properties
spring.http.converters.preferred-json-mapper=gson
# Format to use when serializing Date objects.
spring.gson.date-format="yyyy-MM-dd HH:mm:ss"
Function to handle incoming post request with data payload
#RequestMapping(path = [ControllerEndPoints.AddCheckingPoint], method = [RequestMethod.POST])
fun addCheckingPoint(#RequestBody reqData: ChartServerVo): ResponseEntity<Ack> {
var ok = true
val data = CheckingPointEntity()
val cpDto = reqData.checkingPointDto
val gson = GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create()
val payload = gson.toJson(reqData)
mLog("json: \n json $payload")
//val gDto = reqData.gDto
data.apply {
name = cpDto.name
description = cpDto.description
cpTypeId = cpDto.cpTypeId
isCumulative = cpDto.isCumulative
cpCategoryGroupId = cpDto.cpCategoryGroupId
isTemplate = cpDto.isTemplate
isTopTemplate = cpDto.isTopTemplate
}
cpr.save(data)
pcRepo.save(reqData.purusharthChartDto.toEntity())
pcMappingrepo.save(reqData.purusharthChartCpMappingDto.toEntity())
return ResponseEntity(Ack(ok), HttpStatus.ACCEPTED)
}
JSON Payload
{"checkingPointDto":{"cpCategoryGroupId":1641785600780,"cpTypeId":1,"description":"","isCumulative":false,"isTemplate":false,"isTopTemplate":false,"name":"asdf","dbCreateDts":"2022-03-05 11:54:01","dbCreateSource":"","dbUpdateDts":"2022-03-05 11:54:01","dbUpdateSource":"","id":0},"purusharthChartCpMappingDto":{"cpId":0,"id":0,"purusharthChartId":1647652877927},"purusharthChartDto":{"adminID":0,"description":"","endDate":"2022-12-30 11:54:01","id":1647652877927,"isSelfChart":false,"name":"asdf","startDate":"2022-03-05 11:54:01","userId":8}}
Error log
POST "/api/v1/cp-add", parameters={}
2022-03-05 16:52:32.118 DEBUG 1360 --- [nio-9000-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to in_.co.innerpeacetech.bkp.controller.CheckingPointController#addCheckingPoint(ChartServerVo)
2022-03-05 16:52:32.358 DEBUG 1360 --- [nio-9000-exec-2] o.s.web.method.HandlerMethod : Could not resolve parameter [0] in public org.springframework.http.ResponseEntity<in_.co.innerpeacetech.bkp.dto.Ack> in_.co.innerpeacetech.bkp.controller.CheckingPointController.addCheckingPoint(in_.co.innerpeacetech.bkp.dto.ChartServerVo): JSON parse error: Cannot construct instance of `in_.co.innerpeacetech.bkp.dto.ChartServerVo`, problem: `java.lang.IllegalArgumentException`; nested exception is com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `in_.co.innerpeacetech.bkp.dto.ChartServerVo`, problem: `java.lang.IllegalArgumentException`
at [Source: (PushbackInputStream); line: 1, column: 2]
2022-03-05 16:52:32.365 WARN 1360 --- [nio-9000-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `in_.co.innerpeacetech.bkp.dto.ChartServerVo`,
My data class variable from the error log
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
var dbCreateDts: Date = Date(),
Why is com.fasterxml.jackson.databind still present in the logs and why is the params empty in the logs POST "/api/v1/cp-add", parameters={}, I am testing the api from postman and the headers and everything is fine and the json is also properly formatted. I am new to spring, what am I missing.

How do I get a byte[] (image) from a webservice using micronaunt HttpClient

I am porting a Grails 3.1 library for using some internal webservices to Grails 4.0. One of the services provides an image of a requested employee upon request. I am having difficulty implementing the (micronaut) HttpClient code to process the request - specifically to get a proper byte[] that is the returned image.
A simple curl command on the command line works with the service:
curl -D headers.txt -H 'Authorization:Basic <encodedKeyHere>' https:<serviceUrl> >> image.jpg
and the image is correct. The header.txt is:
HTTP/1.1 200
content-type: image/jpeg;charset=UTF-8
date: Tue, 27 Aug 2019 20:05:43 GMT
x-ratelimit-limit: 100
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
x-ratelimit-remaining: 99
X-RateLimit-Reset: 38089
x-ratelimit-reset: 15719
Content-Length: 11918
Connection: keep-alive
The old library uses the groovyx.net.http.HTTPBuilder and simply does:
http.request(Method.GET, ContentType.BINARY) {
uri.path = photoUrlPath
uri.query = queryString
headers.'Authorization' = "Basic $encoded".toString()
response.success = { resp, inputstream ->
log.info "response status: ${resp.statusLine}"
return ['status':resp.status, 'body':inputstream.getBytes()]
}
response.failure = { resp ->
return ['status':resp.status,
'error':resp.statusLine.reasonPhrase,
body:resp.getEntity().getContent().getText()]
}
}
so returning the bytes from an inputStream. This works.
I've tried several things using the micronaut HttpClient, both with the low level API and with the declarative API.
A simple example with the declarative API:
#Get(value='${photo.ws.pathurl}', produces = MediaType.IMAGE_JPEG)
HttpResponse<byte[]> getPhoto(#Header ('Authorization') String authValue,
#QueryValue("emplId") String emplId)
And than in the Service:
HttpResponse<byte[]> resp = photoClient.getPhoto(getBasicAuth(),emplId)
def status = resp.status() // code == 200 --> worked
def bodyStrOne = resp.getBody() // nope: get Optional.empty
// Tried different getBody(class) -> Can't figure out where the byte[]s are
// For example can do:
def buf = resp.getBody(io.netty.buffer.ByteBuf).value // Why need .value?
def bytes = buf.readableBytes() // Returns 11918 --> the expected value
byte[] ans = new byte[buf.readableBytes()]
buf.readBytes(ans) // Throws exception: io.netty.util.IllegalReferenceCountException: refCnt: 0
This "works" but the returned String looses some encoding that I can't reverse:
// Client - use HttpResponse<String>
#Get(value='${photo.ws.pathurl}', produces = MediaType.IMAGE_JPEG)
HttpResponse<String> getPhoto(#Header ('Authorization') String authValue,
#QueryValue("emplId") String emplId)
// Service
HttpResponse<String> respOne = photoClient.getPhoto(getBasicAuth(),emplId)
def status = respOne.status() // code == 200 --> worked
def bodyStrOne = respOne.getBody(String.class) // <-- RETURNS DATA..just NOT an Image..or encoded or something
String str = bodyStrOne.value // get the String data
// But these bytes aren't correct
byte[] ans = str.getBytes() // NOT an image..close but not.
// str.getBytes(StandardCharsets.UTF_8) or any other charset doesn't work
Everything I've tried with the ByteBuf classes throws the io.netty.util.IllegalReferenceCountException: refCnt: 0 exception.
Any direction/help would be greatly appreciated.
Running:
Grails 4.0
JDK 1.8.0_221
Groovy 2.4.7
Windows 10
IntellJ 2019.2
It must be Grails bug.
Add this line into logback.groovy:
logger("io.micronaut.http", TRACE)
Then you should see that the body was not empty but finally it ends with error Unable to convert response body to target type class [B. See the trace:
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Status Code: 200 OK
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Content-Type: image/jpeg
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Content-Length: 11112
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Accept-Ranges: bytes
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Response Body
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : ----
2019-09-11 11:19:16.238 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : ���� C
...
2019-09-11 11:19:16.241 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : ----
2019-09-11 11:19:16.243 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Unable to convert response body to target type class [B
But when you try the same in standalone Microunaut application (add <logger name="io.micronaut.http" level="trace"/> into logback.xml) the result is different:
09:02:48.583 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Status Code: 200 OK
09:02:48.583 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Content-Type: image/jpeg
09:02:48.589 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - content-length: 23195
09:02:48.590 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Response Body
09:02:48.590 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ----
09:02:48.612 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ���� C���
...
09:02:48.620 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ----
Micronaut trace has no error.
Here is an example of declarative HTTP client which downloads random image from https://picsum.photos web site:
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
#Client('https://picsum.photos')
interface LoremPicsumClient {
#Get(value = '{width}/{height}', produces = MediaType.IMAGE_JPEG)
HttpResponse<byte[]> getImage(Integer width, Integer height)
}
And Spock unit test for it:
import io.micronaut.http.HttpStatus
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification
import javax.inject.Inject
import java.nio.file.Files
import java.nio.file.Paths
#MicronautTest
class LoremPicsumClientSpec extends Specification {
#Inject
LoremPicsumClient client
void 'image is downloaded'() {
given:
def output = Paths.get('test')
when:
def response = client.getImage(300, 200)
then:
response.status == HttpStatus.OK
response.getBody().isPresent()
when:
Files.write(output, response.getBody().get())
then:
Files.probeContentType(output) == 'image/jpeg'
}
}
In Micronaut the test passes and an image is saved into the test file. But in Grails the test fails because HttpClient is not able to convert the response bytes into byte array or better into anything else then String.
We are currently using this implementation:
#Client(value = "\${image-endpoint}")
interface ImageClient {
#Get("/img")
fun getImageForAddress(
#QueryValue("a") a: String
): CompletableFuture<ByteArray>
}
works fine for us.
When I use the HttpResponse I get an error as well, couldn't make it work with that.
Documentation propose to send bytes via input stream but I didn't manage to make it work. The most brittle thing that HttpClient should Consume bytes but Server should Produce.
#Get(value = "/write", produces = MediaType.TEXT_PLAIN)
HttpResponse<byte[]> write() {
byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
return HttpResonse.ok(bytes); //
}

Flux.subscribe finishes before the last element in processed

Strange behavior of Spring + Flux. I have Python server code (using Flask, but that's not important, treat it as pseudo-code) which is streaming response:
def generate():
for row in range(0,10):
time.sleep(1)
yield json.dumps({"count": row}) + '\n'
return Response(generate(), mimetype='application/json')
With that, I simulate processing some tasks from the list and sending me results as soon as they are ready, instead of waiting for everything to be done, mostly to avoid keeping that everything in memory first of the server and then of the client. Now I want to consume that with Spring WebClient:
Flux<Count> alerts = webClient
.post()
.uri("/testStream")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux( Count.class )
.log();
alerts.subscribe(a -> log.debug("Received count: " + a.count));
Mono<Void> mono = Mono.when(alerts);
mono.block();
log.debug("All done in method");
Here is what I'm getting in log:
2019-07-03 18:45:08.330 DEBUG 16256 --- [ctor-http-nio-4] c.k.c.restapi.rest.Controller : Received count: 8
2019-07-03 18:45:09.323 INFO 16256 --- [ctor-http-nio-2] reactor.Flux.MonoFlatMapMany.4 : onNext(com.ksftech.chainfacts.restapi.rest.Controller$Count#55d09f83)
2019-07-03 18:45:09.324 INFO 16256 --- [ctor-http-nio-2] reactor.Flux.MonoFlatMapMany.4 : onComplete()
2019-07-03 18:45:09.325 DEBUG 16256 --- [io-28088-exec-4] c.k.c.restapi.rest.Controller : All done in method
2019-07-03 18:45:09.331 INFO 16256 --- [ctor-http-nio-4] reactor.Flux.MonoFlatMapMany.4 : onNext(com.ksftech.chainfacts.restapi.rest.Controller$Count#da447dd)
2019-07-03 18:45:09.332 DEBUG 16256 --- [ctor-http-nio-4] c.k.c.restapi.rest.Controller : Received count: 9
2019-07-03 18:45:09.333 INFO 16256 --- [ctor-http-nio-4] reactor.Flux.MonoFlatMapMany.4 : onComplete()
Notice how last object is processed by subscribe after mono.block returns. I understand that Reactor is asynchronous, and once it sees no more objects, it releases Mono and calls my code in subscribe in parallel. Then it is a mercy of scheduler to see what runs first.
I came up with quite ugly kludge of having subscribe with completeConsumer, and using good old wait/notify. Then it works fine. But is there more elegant way of making sure my method waits until all elements of Flux are processed?
OK, I have studied this area and realized that Reactor is for asynchronous execution. If I need it synchronously, I have to use synchronization. And to have a code which executes after everything has been fed to subscribe, I need to use doOnComplete:
public class FluxResult {
public boolean success = true;
public Exception ex = null;
public void error() {success = false;}
public void error(Exception e) {success = false; ex = e;}
public synchronized void waitForFluxCompletion() throws InterruptedException {
wait();
}
public synchronized void notifyAboutFluxCompletion() {
notify();
}
}
.... // do something which returns Flux
myflux
.doFirst(() -> {
// initialization
})
.doOnError(e -> {
log.error("Exception", e);
})
.doOnComplete(() -> {
try {
// finalization. If we were accumulating objects, now flush them
}
catch (Exception e) {
log.error("Exception", e);
flux_res.error(e);
}
finally {
flux_res.notifyAboutFluxCompletion();
}
})
.subscribe(str -> {
// something which must be executed for each item
});
And then wait for object to be signaled:
flux_res.waitForFluxCompletion();
if (!flux_res.success) {
if (flux_res.ex != null) {

keeping connection alive to websocket when using ServerWebSocketContainer

I was trying to create a websocket based application where the server needs to keep the connection alive with the clients using heartbeat.
I checked the server ServerWebSocketContainer.SockJsServiceOptions class for the same, but could not use it. I am using the code from the spring-integration sample
#Bean
ServerWebSocketContainer serverWebSocketContainer() {
return new ServerWebSocketContainer("/messages").withSockJs();
}
#Bean
MessageHandler webSocketOutboundAdapter() {
return new WebSocketOutboundMessageHandler(serverWebSocketContainer());
}
#Bean(name = "webSocketFlow.input")
MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
IntegrationFlow webSocketFlow() {
return f -> {
Function<Message , Object> splitter = m -> serverWebSocketContainer()
.getSessions()
.keySet()
.stream()
.map(s -> MessageBuilder.fromMessage(m)
.setHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER, s)
.build())
.collect(Collectors.toList());
f.split( Message.class, splitter)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.handle(webSocketOutboundAdapter());
};
}
#RequestMapping("/hi/{name}")
public void send(#PathVariable String name) {
requestChannel().send(MessageBuilder.withPayload(name).build());
}
Please let me know how can I set the heartbeat options ensure the connection is kept alive unless the client de-registers itself.
Thanks
Actually you got it right, but missed a bit of convenience :-).
You can configure it like this:
#Bean
ServerWebSocketContainer serverWebSocketContainer() {
return new ServerWebSocketContainer("/messages")
.withSockJs(new ServerWebSocketContainer.SockJsServiceOptions()
.setHeartbeatTime(60_000));
}
Although it isn't clear for me why you need to configure it at all because of this:
/**
* The amount of time in milliseconds when the server has not sent any
* messages and after which the server should send a heartbeat frame to the
* client in order to keep the connection from breaking.
* <p>The default value is 25,000 (25 seconds).
*/
public SockJsServiceRegistration setHeartbeatTime(long heartbeatTime) {
this.heartbeatTime = heartbeatTime;
return this;
}
UPDATE
In the Spring Integration Samples we have something like stomp-chat application.
I have done there something like this to the stomp-server.xml:
<int-websocket:server-container id="serverWebSocketContainer" path="/chat">
<int-websocket:sockjs heartbeat-time="10000"/>
</int-websocket:server-container>
Added this to the application.properties:
logging.level.org.springframework.web.socket.sockjs.transport.session=trace
And this to the index.html:
sock.onheartbeat = function() {
console.log('heartbeat');
};
After connecting the client I see this in the server log:
2015-10-13 19:03:06.574 TRACE 7960 --- [ SockJS-3] s.w.s.s.t.s.WebSocketServerSockJsSession : Writing SockJsFrame content='h'
2015-10-13 19:03:06.574 TRACE 7960 --- [ SockJS-3] s.w.s.s.t.s.WebSocketServerSockJsSession : Cancelling heartbeat in session sogfe2dn
2015-10-13 19:03:06.574 TRACE 7960 --- [ SockJS-3] s.w.s.s.t.s.WebSocketServerSockJsSession : Scheduled heartbeat in session sogfe2dn
2015-10-13 19:03:16.576 TRACE 7960 --- [ SockJS-8] s.w.s.s.t.s.WebSocketServerSockJsSession : Preparing to write SockJsFrame content='h'
2015-10-13 19:03:16.576 TRACE 7960 --- [ SockJS-8] s.w.s.s.t.s.WebSocketServerSockJsSession : Writing SockJsFrame content='h'
2015-10-13 19:03:16.576 TRACE 7960 --- [ SockJS-8] s.w.s.s.t.s.WebSocketServerSockJsSession : Cancelling heartbeat in session sogfe2dn
2015-10-13 19:03:16.576 TRACE 7960 --- [ SockJS-8] s.w.s.s.t.s.WebSocketServerSockJsSession : Scheduled heartbeat in session sogfe2dn
In the browser's console I see this after:
So, looks like heart-beat feature works well...

Resources