Spring REST consumption results in HTTP Status 406 - Not Acceptable - spring

I get this error when i try to consume a REST API:
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 406 Not Acceptable
Here's the client code that gets executed:
public static void main(String[] args) {
Car c = getCarById(4);
System.out.println(c);
}
public static #ResponseBody Car getCarById(int id){
return new RestTemplate().getForObject("http://localhost:8080/rest/cars/{id}", Car.class, id);
}
Here's the code of the Controller which maps the request:
#RequestMapping(value="/cars/{id}", method=RequestMethod.GET, headers = {"Accept=text/html,application/xhtml+xml,application/xml"}, produces="application/xml")
public #ResponseBody Car getCarById(#PathVariable("id") int id){
return carService.getCarById(id);
}
Why is this error (406-Not Acceptable) happening although the mappers should take care of mapping to the correct types?

You're sending an Accept= header instead of an Accept: header.

I got this answer when I had a wrong Accept: header in my request. I was trying to request an image/jpeg, but my request contained "Accept: application/json".
The solution was to use the correct entity class to query for (I was querying for Object just to see what would come), in my case Resource.class.

add this to spring mvc dispatcher:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- JSON format support for Exception -->
<bean id="methodHandlerExceptionResolver"
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
</list>
</property>
</bean>

In my case, I fixed this not on the server side but in the client side. I was using Postman and was getting the 406 error. But using a browser was processing the request just fine. So I looked at the request headers in the browser and added the Accept header in Postman, like this: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

In addition, you can fix add "Accept", "/"
val headers = HttpHeaders()
headers.add("Accept", "*/*")
val httpEntity = HttpEntity("parameters", headers)
restTemplate.exchange(....)
restTemplate.exchange("http://localhost:" + serverPort + "/product/1",
HttpMethod.GET,
httpEntity,
String.javaClass)
Sorry, it's Kotlin

I got same type of error due to mismatch of the value of ContentType & Accept. Solved the issue by setting the same string value for both of them.
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.valueOf(CONTENT_TYPE));
httpHeaders.setAccept(Arrays.asList(MediaType.valueOf(CONTENT_TYPE)));

I have had the same problem and finally it was a library problem. If you don't use maven you have to be sure you have include json core library and all its dependencies.
If your method has input parameters loaded in json format and you don't have this libraries you will have a 415 error.
I think the two error has the same origin: incomplete libraries.

Related

Spring Integration | HTTP outbound gateway | Multipart

Service A receives a file
Service A performs business logic.
Service B exposed an HTTP inbound to receive the file and post it
to S3.
Service A calling Service B using http outbound gateway.
I am getting an error, unable to find suitable message converter when service A calls Service B using http outbound gateway.
<int:header-enricher input-channel="addHeader" output-channel="s3publishWithHeader">`
<int:header name="Content-Type" value="multipart/form-data" overwrite="true"/> </int:header-enricher>`
<util:list id="converters">
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
<bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
</util:list>`
<http:outbound-gateway request-channel="s3publishWithHeader"
http-method="POST" url="http://localhost:8090/com/api/upload"
extract-request-payload="true" message-converters="converters"
></http:outbound-gateway>
Version of Spring Integration: 4.3.12
When using a Resource multipart it seems crucial that this resource has a file name, otherwise I found that the receiving server complains that the part is not present.
The MultiValueMap which will become the multipart request body can contain HttpEntity parts where you can specify the parts more precisely, e.g. you can specify a file name.
Assuming you have two parts, request and file, and file is an InputStreamResource (which never has a file name), you must define the filename by means of the Content-Disposition header. Use the resulting map as outgoing message payload, like so:
MultiValueMap payload = new LinkedMultiValueMap();
// some json part named "request"
HttpHeaders requestPartHeaders = new HttpHeaders();
requestPartHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ConversionRequest> requestEntity =
new HttpEntity<>(requestBean, requestPartHeaders);
// InputStreamResource part named "file"
HttpHeaders fileRequestHeaders = new HttpHeaders();
fileRequestHeaders.setContentDisposition(ContentDisposition.builder("form-data")
.name("file") // name of the part in the map
.filename("my-file-name.pdf") // <-- mandatory file name
.build());
fileRequestHeaders.setContentType(MediaType.parseMediaType(fileMimeType));
HttpEntity<InputStreamResource> fileEntity =
new HttpEntity<>(new InputStreamResource(fileOriginalStream),
fileRequestHeaders);
payload.add("request", requestEntity);
payload.add("file", fileEntity);
You don't need to specify that Content-Type header and you don't need to configure custom converters.
Only what you need is a payload as Map<String, Object>:
else if (content instanceof Map) {
// We need to check separately for MULTIPART as well as URLENCODED simply because
// MultiValueMap<Object, Object> is actually valid content for serialization
if (this.isFormData((Map<Object, ?>) content)) {
if (this.isMultipart((Map<String, ?>) content)) {
contentType = MediaType.MULTIPART_FORM_DATA;
}
else {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
}
}
Nonetheless you have to show the StackTrace for error because I think there is something with response parsing.

How to enable Spring Reactive Web MVC to handle Multipart-file?

I'm trying to use the new reactive web-mvc implementation in a spring boot 2.0 application. I'm trying to define a method which consume multipart file but do not succeed at making it working :( - I always get a 415 error.
On one hand I have a controller containing the following request mapping :
#RequestMapping(method = RequestMethod.POST, path = "/myPath/{param}/{param2}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public Mono<Void> postFile(
#RequestBody MultipartFile data,
#PathVariable("param") String param,
#PathVariable("param2") String param2,
#RequestHeader(name = HEADER_DATE, required = false) #DateTimeFormat(pattern = DATE_FORMAT) Instant instant
){
return fileService.handleData(Mono.just(data), param, param2, instant);
}
On the other hand I had to add a server on the top of the basic dependencies as it seems netty do not handle multipart files. I so added the spring-boot-starter-tomcatdependency which enabled the MultipartAutoConfiguration to be matched and satisfied on application auto configuration.
When posting something using a curl call :
curl 'Meta-Date: 20170101104532' --form "file=#file.bin" http://localhost:8082/myPath/foo/bar
while debug logs are activated (logging.level.org.springframework.web=DEBUG) I got this exception :
org.springframework.web.server.UnsupportedMediaTypeStatusException: Request failure [status: 415, reason: "Content type 'multipart/form-data;boundary=------------------------58fa43b8f1a26de4' not supported"]
This error is thrown by the RequestBodyArgumentResolver which has the the following supported media types : [*/*, text/xml, application/*+json;charset=UTF-8, application/xml, text/plain;charset=UTF-8, application/x-www-form-urlencoded, application/json;charset=UTF-8] provided by 9 DecoderHttpMessageReader.
Before posting I also took a look at :
Spring MultiPart MediaType Unsupported which seems to not be relevant here as my autoconf report contains the following entry : MultipartAutoConfiguration#multipartResolver matched
set content-type to utf-8 with angularjs $http Adding a header setting Content-Transfer-Encoding: binary didn't changed anything.
My understanding is that Spring web 5.0 uses a new request decoder system as I don't find these classes on a spring 4 spring boot application, and there is not yet any DecoderHttpMessageReader dealing with multipart file
Did I miss something ? Or should I wait one to be implemented ?
Okay, It seems this is just not implemented for now as it currently exists a pull request for this feature : Add reactive multipart request support #1201
Should have check this earlier...
[EDIT] : The issue has been solved and merged into Spring master branch. Should no longer be an issue.
#PutMapping(value="/{..}",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> save(#RequestPart("file") FilePart multipartFormData,#RequestParam("fileName") String fileName,#PathVariable("..") String ..) throws IOException {
List<ByteBuffer> bytesList = new LinkedList<>();
multipartFormData.content().
subscribe(item->bytesList.add(item.asByteBuffer()));
int totalBytes = bytesList.stream().mapToInt(item->item.capacity()).sum();
ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
bytesList.stream().forEach(byteBuff->buffer.put(byteBuff));
baseImageHandler.saveImage(buffer, fileName, baseItemId);
return Mono.empty();
}
Please note that it is a dev verison, but this is how I have managed to do it.

Spring MVC with Spring Integration HTTP: how to pass the queryString avoiding the "?" encoding with "%3F" ?

I have two Spring MVC applications interconneted between each others with Spring Integration HTTP.
I have an "application1" and an "application2".
The application1 receive this HTTP GET request:
http://localhost:8080/application1/api/persons/search/findByName?name=Name
The application1 manage the request with this #Controller:
#Controller
public class ApplicationController {
#RequestMapping(value = "/api/{repository}/search/{methodName}", method = RequestMethod.GET)
public void search(#PathVariable(value="repository") String repository,
#PathVariable(value="methodName") String methodName,
ModelMap model,
HttpServletRequest request,
HttpServletResponse response) {
// handling the request ...
}
}
Here is what I can see in the request properties:
getRequestURI=/application1/api/persons/search/findByName
getRequestedSessionId=null
getContextPath=/application1
getPathTranslated=null
getAuthType=null
getMethod=GET
getQueryString=name=Name
getServletPath=/api/persons/search/findByName
getPathInfo=null
getRemoteUser=null
I want to "transfer" this request to the application2 using Spring Integration HTTP with an <int-http:outbound-gateway>.
The message for the channel used by the outbound-gateway is originated in the #Controller in this way:
MessagingChannel messagingChannel = (MessagingChannel)appContext.getBean("requestChannelBean");
String payload = repository+"/search/"+methodName;
Message<String> message = MessageBuilder.withPayload(payload).build();
MessageChannel requestChannel = messagingChannel.getRequestChannel();
MessagingTemplate messagingTemplate = new MessagingTemplate();
Message<?> response = messagingTemplate.sendAndReceive(requestChannel, message);
This is the <int-http:outbound-gateway> configuration:
<int-http:outbound-gateway id="gateway" rest-template="restTemplate"
url="http://localhost:8080/application2/api/service/{pathToCall}"
http-method="POST" header-mapper="headerMapper" extract-request-payload="true"
expected-response-type="java.lang.String">
<int-http:uri-variable name="pathToCall" expression="payload"/>
</int-http:outbound-gateway>
This gateway produces an HTTP POST request towards the url:
http://localhost:8080/application2/api/service/persons/search/findByName
But in this request I lose the original QueryString received by the application1.
I have tried to add the queryString directly to the payload as follows:
String queryString = "";
if (request.getQueryString()!=null)
queryString = request.getQueryString();
String payload = repository+"/search/"+methodName+"?"+queryString;
But this doesn't work: the produced url is:
http://localhost:8080/application2/api/service/persons/search/findByName%3Fname=Name
The "?" symbol is replaced by "%3F", so the called method is "/service/persons/search/findByName%3Fname=Name", instead of "/service/persons/search/findByName"
I suppose that it depends on the http-method="POST"; I want to use the POST method in any case, because I want to use this "service" for general requests.
So what I have to do in order to transfer the queryString of the original request to the other side in the simplest way as possible?
Thanks in advance.
I have found the answer.
The encoding of the "?" into "%3F" was made by the <int-http:outbound-gateway> beacuse the encode-uri="false" attribute was missing.
The outbound gateway encodes the URI by default, because the encode-uri attribute is setted to true by default.

Camel & CXF & REST: ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json

In my Spring configuration file:
<bean id="jacksonJsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />
<bean id="restJacksonProviderList" class="java.util.ArrayList">
<constructor-arg>
<list>
<ref bean="jacksonJsonProvider"/>
</list>
</constructor-arg>
</bean>
//......
<route id="RestMyRoute">
<from id="RestRequest" uri="cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList" />
<to uri="direct:doRoute" />
</route>
The Service interface:
#GET
#Path("/my/something/{id}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#WebMethod
#WebResult(name = "getSomethingResponse")
public List<MySomething> getSomething(
#PathParam("id") #WebParam(name = "id") String id);
The code above works! I can send the get request to the URl and I get a JSON response.
Now, I do a small change: Instead of defining the web service's URL (and the route) by XML configuration, I define them by Java code:
public class MyRoute extends RouteBuilder {
private String uriRest = "cxfrs:/rest/MyService?resourceClasses=com.myself.services.MyService&bindingStyle=SimpleConsumer&providers=#restJacksonProviderList";
#Override
public void configure() throws Exception {
from(uriRest).
to("log:input").
to("direct:doRoute").
to("log:output");
}
}
When I hit the web service URL, I am getting 500 Internal Server Error and in the logs (Tomcat) I see JAXRSUtils ERROR No message body writer has been found for class java.util.ArrayList, ContentType: application/json
Actually the debugger tells me that defining the URI by Java code is recognized, since I do hit the code inside the route.
I saw this error in many answers here, basically they say to add a Json provider and assign it to the CXF endpoint.
Seems to me like it is what I have done. But it does not work.
Any idea what I am doing wrong here?
As peeskillet said, it's because there isn't a list of providers registered under the name restJacksonProviderList. You can get the JndiContext like this and bind a list to it in the configure method of your routebuilder:
JndiContext registry = (JndiRegistry) context.getRegistry();
registry.bind("restJacksonProviderList", Arrays.asList(new JacksonJsonProvider()));
Edit after comments:
Change & for & in your cxfrs uri definition, & is only needed in xml.

Usage of ResponseEntity<T>

I have following code (it's a method in a controller) to download a attachment/Document:
#RequestMapping(value="downloadattachment.htm",method=RequestMethod.GET)
public ResponseEntity<Blob> downloadAttachment(#RequestParam("attachmentid")
int attachmentId){
//Attachment is a POJO.
Attachment attachment= commonDao.getAttachment(attachmentId);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(new MediaType(attachment.getContentType()));
responseHeaders.set("Content-Disposition",
"attachment; filename=\"" + attachment.getFileName() +"\"");
return new ResponseEntity<Blob>(attachment.getFileData(), responseHeaders, HttpStatus.CREATED);
}
When I run this it gives following error:
The request sent by the client was syntactically incorrect ().
I'm using tiles so my view resolver is:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>
Is it related to tiles in some way as all view resolving is happening through tiles definition?
But I think (as according to spring reference), when using #ResponseBody or ResponseEntity<T>, return type of a handler method is written straight to the HTTP response body(and not placed in a Model, or interpreted as a view name).
The problem is with the request and not the response.
#RequestParam("attachmentid") int attachmentId
I think you are not sending parameter attachmentId from client or the request is going to some other controller method.

Resources