Spring throws HttpMediaTypeNotAcceptableException inexplicably - spring

I have my Spring app configured to use a GET parameter for content negotiation. Code is Kotlin but would work the same in Java.
Config:
override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) {
configurer.favorParameter(true)
.parameterName("format")
.ignoreAcceptHeader(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("text/plain", MediaType.TEXT_PLAIN)
.mediaType("application/json", MediaType.APPLICATION_JSON)
.mediaType("application/rdf+xml", MediaType("application", "rdf+xml"))
}
And the following controller methods:
#GetMapping("/test", produces=["text/plain"])
fun testText() : String {
return "Hello"
}
#GetMapping("/test", produces=["application/json"])
fun testJson() : Map<String, String> {
return mapOf("hello" to "world")
}
#GetMapping("/test", produces=["application/rdf+xml"])
fun testRdf(response: HttpServletResponse) {
// dummy response, to demonstrate using output stream.
response.setContentType("application/rdf+xml")
response.outputStream.write("dummy data".toByteArray())
response.outputStream.close()
}
testRdf returns void and uses an output stream to send body data back.
The following works just fine:
http://localhost:8080/test?format=text/plain gives me the plain text
http://localhost:8080/test?format=application/json gives me the JSON
But http://localhost:8080/test?format=application/rdf+xml gives me an HTTP 406 and the logs say
org.apache.tomcat.util.http.Parameters : Start processing with input [format=application/rdf+xml]
o.s.web.servlet.DispatcherServlet : GET "/test?format=application/rdf+xml", parameters={masked}
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
o.s.web.servlet.DispatcherServlet : Completed 406 NOT_ACCEPTABLE
A debugger shows that it doesn't even call my function.
(To prove that the testRdf handler does what's expected, I made the path unique and removed the produces annotation - it works fine outside content negotiation and returns the body as expected.)
As far as I can tell I have indicated that my method is the right handler for that content type, and I have registered the content type correctly.
Why does Spring not consider that my handler meets the content negotiation request?

I found the answer. The parameter characters that needed to be URL encoded, so this works fine:
http://localhost:8080/test?format=application%2Frdf%2Bxml

Related

Webclient raises exceptions on responses with non-200 status codes. How can I return the response anyway?

When using Spring Webclient, any non-200 status code immediately throws an exception. I do not want this behavior.
I have the following API Service:
fun createSomething(body: String): SomeCustomResponse {
// Get signedRequest from another service (see below for that code)
return signedRequest
.retrieve()
.bodyToMono(SomeCustomResponse::class.java)
.block()
}
My response class intentionally contains an error field:
data class SomeCustomResponse(
val message: String? = null,
val error: String? = null
)
I want to pass the errors returned from this request to the front end, so the user can see the error messages. As long as the status code is 200, that works exactly as it should, but any non-200 status code throws immediately.
I've tried using .onStatus, but you can only return Throwable Monos using that, which I do not want to do. That attempted solution looks like this:
return signedRequest
.retrieve()
.onStatus(HttpStatus::is4xxClientError) {
it.bodyToMono(SomeCustomResponse::class.java)
}
.bodyToMono(SomeCustomResponse::class.java)
.block()
But that doesn't compile, complaining about:
Type mismatch: inferred type is Mono<SomeCustomResponse!>! but Mono<out Throwable!>! was expected
Type mismatch: inferred type is SomeCustomResponse! but Throwable! was expected
The signedRequest value is of type RequestHeadersSpec<*>, which is an AWS v4 signed API request. I cannot put custom logic into that service, as it is used for any number of other APIs.
For reference though, that code looks like this:
// Generate Auth headers and then:
return WebClient.create(baseUrl)
.method(method)
.uri { builder: UriBuilder -> builder.path(resourcePath).build() }
.header("X-Amz-Date", headers["X-Amz-Date"])
.header("Authorization", headers["Authorization"])
.body(Mono.just(body), String::class.java)
How can I bypass the default behavior of throwing exceptions here? I just want to return the response exactly as it currently does, regardless of status code.
According to the ResponseSpec.onStatus docs:
To suppress the treatment of a status code as an error and process it as a normal response, return Mono.empty() from the function. The response will then propagate downstream to be processed.
return signedRequest
.retrieve()
.onStatus(HttpStatus::is4xxClientError) {
Mono.empty()
}
.bodyToMono(SomeCustomResponse::class.java)
.block()
Bear in mind though, that if the error is a "real one" (e.g. the URL indeed does not exist) and the response body is not parseable, the method will throw a pretty misleading deserialisation exception.

Access Spring WebClient response body **BEFORE** being parsed

I've got a problem with an URL call response encoding.
So, just before Spring's WebClient converts the body response into an String object, as desired, I need to access the raw body response to parse it with the proper encoding. So, just before:
<execution>.flatMap(servletResponse -> {
Mono<String> mono = servletResponse.bodyToMono(String.class);
}
I need to access the raw URL call response; I think before "flatMap".
So... I've been looking at "codecs" within Spring documentation, but... So; even for testing, I have:
myWriter is an instance of: EncoderHttpMessageWriter.
myReader is an instance of: DecoderHttpMessageReader.
myWriter handles myDecoder, an instance of Decoder.
myReader handles myEncoder, an instance of Encoder.
as per Spring Documentation about Codecs; and testing with both options for the WebClient Builder:
myWebClientBuilder = WebClient.Builder; // provided by Spring Context,
myWebClientBuilder = WebClient.builder(); // "by hand"
So, the relevant part of code looks like this (tried even with register and registerWithDefaultConfig):
WebClient.builder().codecs(consumer -> {
consumer.customCodecs().register(myWriter.getEncoder());
consumer.customCodecs().register(myWriter);
consumer.customCodecs().register(myReader.getDecoder());
consumer.customCodecs().register(myReader);
})
.build();
shows that the codecs load, and internal basic methods are called:
canDecode
canEncode
canRead
canWrite
getDecodableMimeTypes
getDecoder
getEncodableMimeTypes
getEncoder
but... No one of the read/write... Mono<T> / Flux<T> methods are used. Is there anything left for configuring a codec to properly parse the incoming response with the proper encoding?
The response is a String; a old-fashioned String, with all data-fields in a single row, that wll be parsed later, according to rules about positions and lengths of fields; nothing related with JSON or Jackson.
Is there another better way to perform this pre-action?
Many thanks in advance.

.transform / .compose duplicates Mono execution with Spring Security

While implementing an authentication solution based on Spring Security Reactive, I faced an issue where the operations in the chain get duplicated at some point. From that, everything was called twice.
The culprit was the operator .transform at one point of the chain. After editing the called method and replacing the operator by .flatMap, the issue was resolved and everything was only called once.
The question
According to the operator's documentation, the
function is applied to an original operator chain at assembly time to augment it with the encapsulated operators
and
is basically equivalent to chaining the operators directly.
Why did the operator .transform trigger a second subscription to the chain, then ?
The context
This authentication flow takes a trusted username and retrieves its details from a webservice.
The authentication method to implement the ReactiveAuthenticationManager :
#Override
public Mono<Authentication> authenticate(Authentication providedAuthentication) {
String username = (String) providedAuthentication.getPrincipal();
String token = (String) providedAuthentication.getCredentials();
return Mono.just(providedAuthentication)
.doOnNext(x -> LOGGER.debug("Starting authentication of user {}", x))
.doOnNext(AuthenticationValidator.validateProvided)
.then(ReactiveSecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.flatMap(auth -> AuthenticationValidator.validateCoherence(auth, providedAuthentication))
.switchIfEmpty(Mono.defer(() -> {
LOGGER.trace("Switch if empty before retrieving user");
return retrieveUser(username, token);
}))
.doOnNext(logAccess);
}
The duplication of the calls started from the supplier of .switchIfEmpty until the end of the chain.
The method creating the Mono used by .switchIfEmpty :
private Mono<PreAuthenticatedAuthenticationToken> retrieveUser(String username, String token) {
return Mono.just(username)
.doOnNext(x -> LOGGER.trace("Before find by username"))
.then(habileUserDetails.findByUsername(username, token))
.cast(XXXUserDetails.class)
.transform(rolesProvider::provideFor)
.map(user -> new PreAuthenticatedAuthenticationToken(user, GlobalConfiguration.NO_CREDENTIAL, user.getAuthorities()))
.doOnNext(s -> LOGGER.debug("User data retrieved from XXX"));
}
The operator .transform on line 4 has been replaced by .flatMap to resolve the issue.
The original method called by the .transform operator :
public Mono<CompleteXXXUserDetails> provideFor(Mono<XXXUserDetails> user) {
return user
.map(XXXUserDetails::getAuthorities)
.map(l -> StreamHelper.transform(l, GrantedAuthority::getAuthority))
.map(matcher::match)
.map(enricher::enrich)
.map(l -> StreamHelper.transform(l, SimpleGrantedAuthority::new))
.zipWith(user, (authorities, userDetails)
-> CompleteXXXUserDetails.from(userDetails).withAllAuthorities(authorities));
}
Here is a trace of the execution :
DEBUG 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager : Starting authentication of user [REDACTED]
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager : Switch if empty before retrieving user
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager : Before find by username
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.xxx.user.UserRetriever : Between request and call
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.u.retriever.UserRetrieverV01: Calling webservice v01
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager : Before find by username
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.xxx.user.UserRetriever : Between request and call
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.u.retriever.UserRetrieverV01: Calling webservice v01
For information, I'm using Spring Boot 2.1.2.RELEASE.
This answer doesn't address the root cause but rather explains how a transform could be applied several times when subscribed to several times, which is not the case in OP's issue. Edited the original text into a quote.
That statement is only valid when the transform is applied as a top-level operator in the chain you subscribe to. Here you are applying it within retrieveUser, which is invoked inside a Mono.defer (which goal is to execute that code for each different subscription).
(edit:) so if that defer is subscribed to x times, the transform Function will be applied x times as well.
compose is basically transform-inside-a-defer by the way.
The issue is in the fact that you do a user.whatever(...).zipWith(user, ...).
With transform, this translates to:
Mono<XXXUserDetails> user = Mono.just(username)
.doOnNext(x -> LOGGER.trace("Before find by username"))
.then(habileUserDetails.findByUsername(username, token))
.cast(XXXUserDetails.class);
return user.wathewer(...)
.zipWith(user, ...);
Whereas with flatMap I assume you did something to the effect of flatMap(u -> provideFor(Mono.just(u))? If so, that would translate to:
Mono<XXXUserDetails> user = Mono.just(username)
.doOnNext(x -> LOGGER.trace("Before find by username"))
.then(habileUserDetails.findByUsername(username, token))
.cast(XXXUserDetails.class);
return user.flatMap(u -> {
Mono<XXXUserDetails> capture = Mono.just(u);
return capture.whatever(...)
.zipWith(capture, ...);
}
You can see how both subscribe twice to a Mono<XXXUserDetails due to zipWith.
The reason is seems to subscribe once with flatMap is because it captures the output of the upstream pipeline and applies the provideFor function on that capture. The capture (Mono.just(u)) is subscribed twice but acts as a cache and doesn't bear any logic / logs / etc...
With transform, there is no capture. The provideFor function is applied directly to the upstream pipeline, which makes the fact that it subscribes twice quite visible.

SQS Listener #Headers getting body content instead of Message Attributes

I am using Spring Cloud SQS messaging for listening to a specified queue. Hence using #SqsListener annotation as below:
#SqsListener(value = "${QUEUE}", deletionPolicy = SqsMessageDeletionPolicy.ALWAYS )
public void receive(#Headers Map<String, String> header, #Payload String message) {
try {
logger.logInfo("Message payload is: "+message);
logger.logInfo("Header from SQS is: "+header);
if(<Some condition>){
//Dequeue the message once message is processed successfully
awsSQSAsync.deleteMessage(header.get(LOOKUP_DESTINATION), header.get(RECEIPT_HANDLE));
}else{
logger.logInfo("Message with header: " + header + " FAILED to process");
logger.logError(FLEX_TH_SQS001);
}
} catch (Exception e) {
logger.logError(FLEX_TH_SQS001, e);
}
}
I am able to connect the specified queue successfully and read the message as well. I am setting a message attribute as "Key1" = "Value1" along with message in aws console before sending the message. Following is the message body:
{
"service": "ecsservice"
}
I am expecting "header" to receive a Map of all the message attributes along with the one i.e. Key1 and Value1. But what I am receiving is:
{service=ecsservice} as the populated map.
That means payload/body of message is coming as part of header, although body is coming correctly.
I wonder what mistake I am doing due to which #Header header is not getting correct message attributes.
Seeking expert advice.
-PC
I faced the same issue in one of my spring projects.
The issue for me was, SQS configuration of QueueMessageHandlerFactory with Setting setArgumentResolvers.
By default, the first argument resolver in spring is PayloadArgumentResolver.
with following behavior
#Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(Payload.class) || this.useDefaultResolution);
}
Here, this.useDefaultResolution is by default set to true – which means any parameter can be converted to Payload.
And Spring tries to match your method actual parameters with one of the resolvers, (first is PayloadArgumentResolver) - Indeed it will try to convert all the parameters to Payload.
Source code from Spring:
#Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
How I solved this,
The overriding default behavior of Spring resolver
factory.setArgumentResolvers(
listOf(
new PayloadArgumentResolver(converter, null, false),
new HeaderMethodArgumentResolver(null, null)
)
)
Where I set, default flag to false and spring will try to convert to payload only if there is annotation on parameter.
Hope this will help.
Apart from #SqsListener, you need to add #MessageMapping to the method. This annotation will helps to resolve method arguments.
I had this issue working out of a rather large codebase. It turned out that a HandlerMethodArgumentResolver was being added to the list of resolvers that are used to basically parse the message into the parameters. In my case it was the PayloadArgumentResolver, which usually always resolves an argument to be the payload regardless of the annotation. It seems by default it's supposed to come last in the list but because of the code I didn't know about, it ended up being added to the front.
Anyway, if you're not sure take a look around your code and see if you're doing anything regarding spring's QueueMessageHandler or HandlerMethodArgumentResolver.
It helped me to use a debugger and look at HandlerMethodArgumentResolver.resolveArgument method to start tracing what happens.
P.S. I think your #SqsListener code looks fine except that I think #Headers is supposed to technically resolve to a Map of < String, Object >", but I'm not sure that would cause the issue you're seeing.

AWS Lex receives Invalid Response from lambda function - Can not construct instance of IntentResponse

Using Java8 in eclipse AWS SDK, I've created and uploaded a lambda function that is hooked in upon fulfillment of my lex intent.
Lambda has not problem receiving JSON request and parsing.
Then, I format a simple "Close" dialogAction response and send back to lex and receive the following error from the Test Bot page in the lex console:
An error has occurred: Received invalid response from Lambda:
Can not construct instance of IntentResponse:
no String-argument constructor/factory method to deserialize
from String value
('{"dialogAction
{"type":"Close","fulfillmentState":"Fulfilled","message":
{"contentType":"PlainText","content":"Thanks I got your info"}}}')
at [Source: "{\"dialogAction\":
{\"type\":\"Close\",\"fulfillmentState\":\"Fulfilled\",\"message\":
{\"contentType\":\"PlainText\",\"content\":\"Thanks I got your
info\"}}}";line: 1, column: 1]
It seems to have a problem right away with the format (line 1, column 1), but my JSON string looks ok to me. Before returning the output string in the handleRequest java function, I am writing the it to the Cloudwatch log and it writes as follows:
{
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "Thanks I got your info"
}
}
}
Things I've tried:
Removing the message element as it's not required
Adding in non-required properties like sessionAttributes,
responseCard, etc
removing the double quotes
replacing double quotes with single quotes
hardcoding json from sample response format message in documentation
Is there something hidden at the http headers level or is java8 doing something to the JSON that is not visible?
Not sure if this is because I'm using Java8 or not, but a return value of "String" from the RequestHandler class handleRequest method will not work.
Yes, String is an object, but the constructors on the Lex side are expecting an "Object". I was converting my lex response POJO to a String before returning it in the handleRequest method. That was my mistake.
I fixed it by changing the return type of the handleRequest method to be "Object" instead of "String".
public Object handleRequest(Object input, Context context)
instead of
public String handleRequest(Object input, Context context)
You also have to implement the
public class LambdaFunctionHandler implements RequestHandler<Object, Object>
not
public class LambdaFunctionHandler implements RequestHandler<Object, String>
This solved my issue.
In my case I was facing exactly the same issue and was able to fix it by creating specific response POJO type and using this POJO as the return type for 'handleRequest' method. E.g. BotResponse.java as follow:
public class BotResponse implements Serializable{
private static final long serialVersionUID = 1L;
public DialogAction dialogAction = new DialogAction();
public DialogAction getDialogAction() {
return dialogAction;
}
public void setDialogAction(DialogAction dialogAction) {
this.dialogAction = dialogAction;
}
}
Note, I have also added the 'implements Serializable' just to be on safer side. Probably it is an overkill.
Not sure why but for me returning a well formatted JSON String object did not worked even after changing the return type of 'handleRequest' method to 'Object'.
I know this is an old question however thought this might help some else
#Mattbob Solution dint fix my issue, However he is in the right path. Best approach is to use a Response object, a custom response object and make the lambda return the custom response object. So i went to the Documentation and created a custom object that looks Response format here
http://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html
At the time of answering question i couldnt find an object in SDK that matched the response Object so i had to recreate but if some one knows please comment below
Class xxxxx implements RequestHandler<Object, AccountResponse> {
#Override
public AccountResponse handleRequest(Object input, Context context) {
}
}
Lambda will look somewhat like this and just populate and return the object to match response structure and error goes away. Hope this helps.
Whenever we are returning the object to the bot from the backend make sure we need to pass content type along with content. But here we are passing wrong. So wE need to pass as like below. It is in Node.js
let message = {
contentType: "PlainText",
content: 'Testing bot'
};

Resources