Spring Boot Produces text/csv, Could not find acceptable representation - spring

I am trying to get my Spring Boot application to return accept=text/csv, I continue to get:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not
find acceptable representation
I've added:
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson_version}"
compile "com.fasterxml.jackson.dataformat:jackson-dataformat-csv:${jackson_version}"
To my build.gradle, I am NOT using SpringMVC
and the handler looks like:
#RequestMapping(value = "/{id}/csv", method = RequestMethod.GET, produces = "text/csv")
public List<RegistrationCode> exportToCsv(#ApiParam(name = "id", required = true, value = "string") #PathVariable String id, HttpServletResponse response) {
...
String headerKey = "Content-Disposition";
String headerValue = String.format("attachment; filename=\"%s\"",
csvFileName);
response.setHeader(headerKey, headerValue);
response.setContentType("text/csv;charset=utf-8");
return registrationCodes;
}
Curl example:
curl -X GET --header 'Accept: text/csv' --header 'Authorization: Bearer ...' 'http://localhost:8080/api/1001/csv'
Message Converter:
public class CsvMessageConverter extends AbstractHttpMessageConverter<List<RegistrationCode>> {
}
Example adding message converts (in traditional applicationContext.xml):
<util:list id="messageConverters">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"
p:objectMapper-ref="jsonObjectMapperFactory"/>
<!--bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"
p:objectMapper-ref="xmlObjectMapperFactory"/-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="com.binding.CsvMessageConverter"/>
</util:list>

//Adding the message converter
#Configuration
public class MyApplicationConfiguration {
...
#Bean
public HttpMessageConverters customConverters() {
return new HttpMessageConverters(new CsvMessageConverter());
}
}
I was able to find some instructions for registering a convert here:
In Spring Boot, adding a custom converter by extending MappingJackson2HttpMessageConverter seems to overwrite the existing converter

Related

x-www-form-urlencoded Array inconsistently populated in Spring REST call

I am attempting to send a PUT request to a Rest API using x-www-form-urlencoded content. My aim is to send a list of strings in the request similar to this article. I have the following REST controller defined in a Spring Boot application to allow for this:
#RestController
#RequestMapping(value = "/rest/api", produces = MediaType.APPLICATION_JSON_VALUE)
public class RestApiController {
#PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
private ReturnType putRestApiTypeJson(
#PathVariable("id") String id,
#ModelAttribute PutDataRequest request) {
System.out.println();
return null;
}
#PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
private ReturnType putRestApiTypeUrlEncoded(
#PathVariable("id") String id,
#ModelAttribute PutDataRequest request) {
System.out.println();
return null;
}
}
which leverages PutDataRequest defined by:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class PutDataRequest {
Set<String> characters = new HashMap<>();
Set<String> movies = new HashMap<>();
}
I try hitting the rest api controller via curl to perform testing. The Application JSON PUT request receives characters and movies no problem, however the form-urlencoded endpoint does so inconsistently:
// No data populated in PutDataRequest at debug time:
curl -X PUT 'http://localhost:some-port/rest/api' -d 'characters=Some%20Name%26movies=Some%20Title' -H 'Content-Type: application/x-www-form-urlencoded'
// Data populated in PutDataRequest at debug time:
curl -X PUT 'http://localhost:some-port/rest/api?characters=Some%20Name%26movies=Some%20Title' -H 'Content-Type: application/x-www-form-urlencoded'
Can anyone give an insight on why providing the key-value pairs via -d prevents the data from being forwarded to the form-urlencoded PUT endpoint? For context, I run this coded using spring version 5.2.3.RELEASE and spring boot version 2.2.4.RELEASE.
I decided to sidestep Spring in this situation. Instead of relying on Spring to figure out how to marshal the data I wanted, I added a HttpServletRequest to the form-urlencoded method signature and pulled the data out of the request:
#PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
private ReturnType putRestApiTypeUrlEncoded(
#PathVariable("id") String id,
#ModelAttribute PutDataRequest data,
HttpServletRequest request) {
String body = request.getRequest().lines()
.map(line -> URLDecoder.decode(line, Charset.defaultCharset()))
.collect(Collectors.joining(System.lineSeparator()));
// manipulate body content to extract desired data
}
I was inspired to do the above by this answer.
Also found another way to get around this error. Turns out PUT and DELETE requests aren't enabled by default, and you need to add an implementation for the formContentFilter method in your Application.java (or wherever you call SpringApplication.run(...) )
Once I added the following to Application.java, I ran again and it worked like magic:
#Bean
#ConditionalOnMissingBean(org.springframework.web.filter.FormContentFilter.class)
#ConditionalOnProperty(prefix="spring.mvc.formcontent.filter", name="enabled", matchIfMissing=true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}

Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported for #RequestBody MultiValueMap

Based on the answer for problem with x-www-form-urlencoded with Spring #Controller
I have written the below #Controller method
#RequestMapping(value = "/{email}/authenticate", method = RequestMethod.POST
, produces = {"application/json", "application/xml"}
, consumes = {"application/x-www-form-urlencoded"}
)
public
#ResponseBody
Representation authenticate(#PathVariable("email") String anEmailAddress,
#RequestBody MultiValueMap paramMap)
throws Exception {
if(paramMap == null || paramMap.get("password") == null) {
throw new IllegalArgumentException("Password not provided");
}
}
the request to which fails with the below error
{
"timestamp": 1447911866786,
"status": 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
"path": "/users/usermail%40gmail.com/authenticate"
}
[PS: Jersey was far more friendly, but couldn't use it now given the practical restrictions here]
The problem is that when we use application/x-www-form-urlencoded, Spring doesn't understand it as a RequestBody. So, if we want to use this
we must remove the #RequestBody annotation.
Then try the following:
#RequestMapping(
path = "/{email}/authenticate",
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = {
MediaType.APPLICATION_ATOM_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE
})
public #ResponseBody Representation authenticate(
#PathVariable("email") String anEmailAddress,
MultiValueMap paramMap) throws Exception {
if (paramMap == null &&
paramMap.get("password") == null) {
throw new IllegalArgumentException("Password not provided");
}
return null;
}
Note that removed the annotation #RequestBody
answer: Http Post request with content type application/x-www-form-urlencoded not working in Spring
It seems that now you can just mark the method parameter with #RequestParam and it will do the job for you.
#PostMapping( "some/request/path" )
public void someControllerMethod( #RequestParam Map<String, String> body ) {
//work with Map
}
Add a header to your request to set content type to application/json
curl -H 'Content-Type: application/json' -s -XPOST http://your.domain.com/ -d YOUR_JSON_BODY
this way spring knows how to parse the content.
In Spring 5
#PostMapping( "some/request/path" )
public void someControllerMethod( #RequestParam MultiValueMap body ) {
// import org.springframework.util.MultiValueMap;
String datax = (String) body .getFirst("datax");
}
#RequestBody MultiValueMap paramMap
in here Remove the #RequestBody Annotaion
#RequestMapping(value = "/signin",method = RequestMethod.POST)
public String createAccount(#RequestBody LogingData user){
logingService.save(user);
return "login";
}
#RequestMapping(value = "/signin",method = RequestMethod.POST)
public String createAccount( LogingData user){
logingService.save(user);
return "login";
}
like that
Simply removing #RequestBody annotation solves the problem (tested on Spring Boot 2):
#RestController
public class MyController {
#PostMapping
public void method(#Valid RequestDto dto) {
// method body ...
}
}
I met the same problem when I want to process my simple HTML form submission (without using thymeleaf or Spring's form tag) in Spring MVC.
The answer of Douglas Ribeiro will work very well. But just in case, for anyone, like me, who really want to use "#RequestBody" in Spring MVC.
Here is the cause of the problem:
Spring need to ① recognize the "Content-Type", and ② convert the
content to the parameter type we declared in the method's signature.
The 'application/x-www-form-urlencoded' is not supported, because, by
default, the Spring cannot find a proper HttpMessageConverter to do
the converting job, which is step ②.
Solution:
We manually add a proper HttpMessageConverter into the Spring's
configuration of our application.
Steps:
Choose the HttpMessageConverter's class we want to use. For
'application/x-www-form-urlencoded', we can choose
"org.springframework.http.converter.FormHttpMessageConverter".
Add the FormHttpMessageConverter object to Spring's configuration,
by calling the "public void
configureMessageConverters(List<HttpMessageConverter<?>>
converters)" method of the "WebMvcConfigurer" implementation class
in our application. Inside the method, we can add any
HttpMessageConverter object as needed, by using "converters.add()".
By the way, the reason why we can access the value by using "#RequestParam" is:
According to Servlet Specification (Section 3.1.1):
The following are the conditions that must be met before post form
data will be populated to the parameter set: The request is an HTTP
or HTTPS request. 2. The HTTP method is POST. 3. The content type is
application/x-www-form-urlencoded. 4. The servlet has made an initial
call of any of the getParameter family of methods on the request
object.
So, the value in request body will be populated to parameters. But in Spring, you can still access RequestBody, even you can use #RequstBody and #RequestParam at the same method's signature.
Like:
#RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public String processForm(#RequestParam Map<String, String> inputValue, #RequestBody MultiValueMap<String, List<String>> formInfo) {
......
......
}
The inputValue and formInfo contains the same data, excpet for the type for "#RequestParam" is Map, while for "#RequestBody" is MultiValueMap.
I wrote about an alternative in this StackOverflow answer.
There I wrote step by step, explaining with code. The short way:
First: write an object
Second: create a converter to mapping the model extending the AbstractHttpMessageConverter
Third: tell to spring use this converter implementing a WebMvcConfigurer.class overriding the configureMessageConverters method
Fourth and final: using this implementation setting in the mapping inside your controller the consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE and #RequestBody in front of your object.
I'm using spring boot 2.
#PostMapping(path = "/my/endpoint", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ResponseEntity<Void> handleBrowserSubmissions(MyDTO dto) throws Exception {
...
}
That way works for me
You can try to turn support on in spring's converter
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// add converter suport Content-Type: 'application/x-www-form-urlencoded'
converters.stream()
.filter(AllEncompassingFormHttpMessageConverter.class::isInstance)
.map(AllEncompassingFormHttpMessageConverter.class::cast)
.findFirst()
.ifPresent(converter -> converter.addSupportedMediaTypes(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
}
}
Just add an HTTP Header Manager if you are testing using JMeter :

Spring Controller sending image to client results in 406

I'm trying to send an image to the front end upon request, it works if i put it in the request body as part JSON, but i want to use image/png, makes more sense, but i get a 406 when i try that.
Controller:
#RequestMapping(value = RESTPaths.EQUIPMENT_FILE_GET_IMAGE + "/{equipmentId}", method = RequestMethod.GET,
produces = MediaType.IMAGE_PNG_VALUE)
public #ResponseBody byte[] insertDataFile(#PathVariable("equipmentId") final Long equipmentId)
throws InternalServerError {
return equipmentFileService.getImage(equipmentId);
}
Test (client):
mockMvc.perform(
get(RESTPaths.EQUIPMENT_FILE_CONTROLLER + RESTPaths.EQUIPMENT_FILE_GET_IMAGE + "/" + equipment.getId())
.with(httpBasic("user", "password")).accept(MediaType.IMAGE_PNG)
.contentType(TestUtil.APPLICATION_JSON_UTF8)).andDo(MockMvcResultHandlers.print()).andExpect(status().isOk());
}
What am i missing?
Try adding the mvc annotation in servlet-context.xml file which registers a ByteArrayHttpMessageConverter
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>image/jpeg</value> <value>image/png</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
Solved:
#RequestMapping(value = RESTPaths.EQUIPMENT_FILE_GET_IMAGE + "/{equipmentId}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<byte[]> getImage(#PathVariable("equipmentId") final Long equipmentId)
throws InternalServerError {
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
return new ResponseEntity<byte[]>(equipmentFileService.getImage(equipmentId), headers, HttpStatus.OK);
}

Apache HttpClient making multipart POST to Spring #Controller class

It seems like there are several posts such as here asking how to use Apache Commons HTTPClient libraries in Java to do a POST to a Servlet. However, it seems like I'm having some problems doing the same thing with a annotated Spring controller method. I've tried a few things but gotten HTTP 401 Bad Request responses from the server. Any examples of doing this would be greatly appreciated.
EDIT: Code I am trying to use:
//Server Side (Java)
#RequestMapping(value = "/create", method = RequestMethod.POST)
public void createDocument(#RequestParam("userId") String userId,
#RequestParam("file") MultipartFile file, HttpServletResponse response) {
// Do some stuff
}
//Client Side (Groovy)
void processJob(InputStream stream, String remoteAddress) {
HttpClient httpclient = new DefaultHttpClient()
httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1)
HttpPost httppost = new HttpPost("http://someurl/rest/create")
MultipartEntity mpEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE)
InputStreamBody uploadFilePart = new InputStreamBody(stream, 'application/octet-stream', 'test.file')
mpEntity.addPart('file', uploadFilePart)
mpEntity.addPart('userId', new StringBody('testUser'))
httppost.setEntity(mpEntity)
HttpResponse response = httpclient.execute(httppost);
println(response.statusLine)
}
Still getting 400 Bad Request in the response from the server.
I hate to answer my own question when it shows incompetence, but it turns out the code was fine, this particular controller did not have a CommonsMultipartResolver defined in its servlet-context.xml file (multiple DispatcherServlets...long story :()
Here's what I added to make it work:
<!-- ========================= Resolver DEFINITIONS ========================= -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="50000000"/>
</bean>
Here is an example from the Spring Reference:
#Controller
public class FileUpoadController {
#RequestMapping(value = "/form", method = RequestMethod.POST)
public String handleFormUpload(#RequestParam("name") String name,
#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
} else {
return "redirect:uploadFailure";
}
}
}

spring restTemplate POST parameters from complex object

I'm attempting to test our REST service using restTemplate using the postForObject(...) method.
unit test:
#Test
public void testPostOrder() {
String url = BASE_URL + "/orders/";
OrderDto orderDtoInput = new OrderDto();
orderDtoInput.setCustomerId(34);
UpdateReportDto updateReport = restTemplate.postForObject(url,
orderDtoInput, UpdateReportDto.class, new Object[] {});
}
the interesting piece of my configuration:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<ref bean="formHttpMessageConverter" />
<ref bean="marshallingHttpMessageConverter" />
</list>
</property>
</bean>
<bean id="formHttpMessageConverter" class="org.springframework.http.converter.FormHttpMessageConverter">
</bean>
I understand that the FormHttpMessageConverter will convert to and from MultiValueMap and media type
application/x-www-form-urlencoded.
Is there any magic, or tools I can use or wire in to convert my Dto to a MultiValueMap ??? or do I need to cycle over the object properties and build my own MultiValueMap in my test?
my server is expecting to get POST parameters that look something like this:
id=11752&firstName=Joe&active=true&address1=1122&address2=2233&c
ellPhone=123-321-1234&childrensName1=bobby1&childrensName2=bobby2&childrensName3=bobby3&childrensName4=bobby4&city=someCity&
customHobbies=loves To Fly Planes&distributorId=407&email=doc#surgeon.com&fax=321-123-1234&fellowship=good fellows&fishing=false&golf=true&hunting=false&
insuranceCompany1=ins1&insuranceCompany2=ins2&insuranceCompany3=ins3&insuranceCompany4=ins4&lastName=Brownie&
mailMerge=true&medicalSchool=Granada U&officeDays=4&officeManager=manager&officeManagerPhone=456.654.4567&other=true&
paNurse=nurse 1&paNursePhone=345-543-3456&
phone=234-432-2345&
salesRepresentativeId=1935&specialty=meatball surgery&spouseName=Betty&state=AL&
surgeryDays=22&title=doc&version=2&zip=47474
promptValues[0].id=12&promptValues[0].miscPromptId=882&promptValues[0].value=meFirst&
promptValues[1].id=13&promptValues[1].miscPromptId=881&promptValues[1].value=youToo&residency=Jamaica General&
surgeonClinics[0].address1=newAddress&surgeonClinics[0].address2=newAddress2&surgeonClinics[0].city=clinic City&
surgeonClinics[0].email=email#clinic1.com&surgeonClinics[0].fax=123.456.7890&surgeonClinics[0].id=33273&
surgeonClinics[0].name=clinic name&surgeonClinics[0].phone=890-098-4567&
surgeonClinics[0].zip=34567&surgeonClinics[0].surgeryCenter1=MySurgeryCenter1&
surgeonClinics[0].surgeryCenter2=MySurgeryCenter2&
surgeonClinics[1].address1=newAddress11&surgeonClinics[1].address2=newAddress22&surgeonClinics[1].city=clinic2 City&
surgeonClinics[1].email=email#clinic2.com&surgeonClinics[1].fax=123.456.7890&surgeonClinics[1].id=33274&
surgeonClinics[1].name=clinic2 name&surgeonClinics[1].phone=890-098-4567&
surgeonClinics[1].zip=34567&
surgeonClinics[1].surgeryCenter1=MySurgeryCenter21&surgeonClinics[1].surgeryCenter2=MySurgeryCenter22&
Here's what I don't get: our RestServiceController method knows how to take this crazy parameter list and re-create our Dto object. We can successfully call it using curl. It seems that some reciprocal magic should exist on the client side to turn the Dto into the parameter list.
Here's the signature of the server side controller method:
// createOrder
#RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT }, value = "/orders/")
#ResponseBody
public UpdateReportDto createOrder(OrderDto orderDto,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
You can use RestTemplate with message converters. I have tested it and it works
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
restTemplate.postForEntity(url, requestBodyObject, returnTypeClass);

Resources