My Spring controller method looks something like this:
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody byte[] data) {
// code that saves item
}
This works fine except when a try to put a zero-length item, then I get an HTTP error: 400 Bad Request. In this case my method is never invoked. I was expecting that the method should be invoked with the "data" parameter set to a zero-length array.
Can I make request mapping work even when Content-Length is 0?
I am using Spring framework version 4.1.5.RELEASE.
Setting a new byte[0] will not send any content on the request body. If you set spring MVC logs to TRACE you should see a message saying Required request body content is missing as a root cause of your 400 Bad Request
To support your case you should make your #RequestBody optional
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody(required = false) byte[] data) {
// code that saves item
}
Related
I'm trying to call an endpoint that accepts PUT requests and expects to be passed 3 different MultipartFile paramters. Let's call them A, B and C.
When I make a request to the same enpoint from Postman it works as intended. When I do it via the the reactor-netty lib I get back Error 400 Bad Request:
"Required request part 'A' is not present"
HttpClient
.create()
// skipping baseUrl and headers headers
.put()
.uri(ENDPOINT_URI)
.sendForm((req, form) -> form
.multipart(true)
.file("A", FILE_A, "application/json)
.file("B", FILE_B, "application/json)
.file("C", FILE_C, "application/json))
.response()
I could not find much info online to establish if this is the best way to achieve what I need. Can you please point me to where I'm going wrong or perhaps towards an alternative solution?
Thanks
After looking throught the source of the HttpClientForm (the class in which .file is called) I found this:
default HttpClientForm file(String name, InputStream stream, #Nullable String contentType) {
return file(name, "", stream, contentType);
}
as well as this:
default HttpClientForm file(String name, File file, #Nullable String contentType) {
return file(name, file.getName(), file, contentType);
}
Somehow I thought that the first paramter 'name' is the one that is matched with the #RequestParam value. By the looks of it its actually the second.
Also if using an input stream instead of a File I had to call the the file method with 4 paramters and pass the name explicitly as the second parameter like so:
file(name, "A", stream, contentType)
I am currently testing one of my services with Spring boot test.The service exports all user data and produces a CSV or PDF after successful completion. A file is downloade in browser.
Below is the code i have wrote in my test class
MvcResult result = MockMvc.perform(post("/api/user-accounts/export").param("query","id=='123'")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_PDF_VALUE)
.content(TestUtil.convertObjectToJsonBytes(userObjectDTO)))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_PDF_VALUE))
.andReturn();
String content = result.getResponse().getContentAsString(); // verify the response string.
Below is my resource class code (call comes to this place)-
#PostMapping("/user-accounts/export")
#Timed
public ResponseEntity<byte[]> exportAllUsers(#RequestParam Optional<String> query, #ApiParam Pageable pageable,
#RequestBody UserObjectDTO userObjectDTO) {
HttpHeaders headers = new HttpHeaders();
.
.
.
return new ResponseEntity<>(outputContents, headers, HttpStatus.OK);
}
While I debug my service, and place debug just before the exit, I get content Type as 'application/pdf' and status as 200.I have tried to replicate the same content type in my test case. Somehow it always throws below error during execution -
java.lang.AssertionError: Status
Expected :200
Actual :406
I would like to know, how should i inspect my response (ResponseEntity). Also what should be the desired content-type for response.
You have problem some where else. It appears that an exception/error occurred as noted by application/problem+json content type. This is probably set in the exception handler. As your client is only expecting application/pdf 406 is returned.
You can add a test case to read the error details to know what exactly the error is.
Something like
MvcResult result = MockMvc.perform(post("/api/user-accounts/export").param("query","id=='123'")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.content(TestUtil.convertObjectToJsonBytes(userObjectDTO)))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE))
.andReturn();
String content = result.getResponse().getContentAsString(); // This should show you what the error is and you can adjust your code accordingly.
Going forward if you are expecting the error you can change the accept type to include both pdf and problem json type.
Note - This behaviors is dependent on the spring web mvc version you have.
The latest spring mvc version takes into account the content type header set in the response entity and ignores what is provided in the accept header and parses the response to format possible. So the same test you have will not return 406 code instead would return the content with application json problem content type.
I found the answer with help of #veeram and came to understand that my configuration for MappingJackson2HttpMessageConverter were lacking as per my requirement. I override its default supported Mediatype and it resolved the issue.
Default Supported -
implication/json
application*/json
Code change done to fix this case -
#Autowired
private MappingJackson2HttpMessageConverter jacksonMessageConverter;
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.ALL);
jacksonMessageConverter.setSupportedMediaTypes(mediaTypes);
406 means your client is asking for a contentType (probably pdf) that the server doesn't think it can provide.
I'm guessing the reason your code is working when you debug is that your rest client is not adding the ACCEPT header that asks for a pdf like the test code is.
To fix the issue, add to your #PostMapping annotation produces = MediaType.APPLICATION_PDF_VALUE see https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PostMapping.html#produces--
Introduction
I have a question about a RestController and a Test .
I have the following PostMapping:
#PostMapping(path = "/download/as/zip/{zipFileName}" )
#ResponseBody
public ResponseEntity<InputStreamResource> downloadDocumentZip(#RequestHeader(required=false,name="X-Application") String appName, #RequestBody ZipFileModel zipFileModel, #PathVariable("zipFileName") String zipFileName)
And I have the following Test:
Response response = given(this.requestSpecification).port(port)
.filter(document("downloadAsZip",
preprocessRequest(prettyPrint()),
requestHeaders(headerWithName("X-Application").description("Owner application")),
pathParameters(parameterWithName("zipFileName").description("The name of the resulting zip file. Mostly not needed/optional.")))
)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.header(new Header(HEADER, "themis"))
.body(jsonContent)
.when()
.post("/download/as/zip/{zipFileName}", "resultFile.zip");
This works and 200 is returned.
First Question
Now I am a bit confused about the meaning of .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE) within the Test.
Content-type is the header on the returned response. But in this test it is being included while making the test request ? Or is it signifying in this case that we are sending JSON in the request body?
Second Question
I know that my controller method should consume JSON, and returns Bytes.
So hence, I make the following change:
#PostMapping(path = "/download/as/zip/{zipFileName}", consumes = MediaType.APPLICATION_JSON_VALUE)
This works so far.
So then I add the following:
#PostMapping(path = "/download/as/zip/{zipFileName}", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
And it fails:
java.lang.AssertionError:
Expected :200
Actual :406
<Click to see difference>
So I changed my test to be the following:
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.accept(MediaType.APPLICATION_OCTET_STREAM_VALUE)
This fails once again.
Expected :200
Actual :406
So even though the client is signalling the same accept header as what the controller produces, we have an error.
Questions:
So should we or should we not have the produces= on the request mapping?
Why is it failing now? Is there a conflict in consuming JSON and Producing Bytes? Or the ContentType within the test?
The problem is that spring changes the return content-type if the end of a URL has an extension.
So seeing .zip at the end, was causing spring to over-ride the type to application/zip .
I have a controller with #ResponseBody annotation. What I want to do is if this user doesn't exists process user's Id and return a json object. If exists redirect to user page with userInfo. Below code gives ajax error. Is there any way to redirect to user page with userInfo?
#RequestMapping(value = "/user/userInfo", method = {RequestMethod.GET})
#ResponseBody
public String getUserInfo(HttpServletRequest request, HttpServletResponse response, ModelMap modelMap) {
if(...){
.....//ajax return
}else{
modelMap.addAttribute("userInfo", userInfoFromDB);
return "user/user.jsp";
}
}
Well, this method is annotated with #ResponseBody. That means that the String return value will be the body of the response. So here you are just returning "user/user.jsp" to caller.
As you have full access to the response, you can always explicitely do a redirect with response.sendRedirect(...);. It is even possible to explicitely ask Spring to pass userInfoFromDB as a RedirectAttribute through the flash. You can see more details on that in this other answer from me (this latter is for an interceptor, but can be used the same from a controller). You would have to return null to tell spring that the controller code have fully processed the response. Here it will be:
...
}else{
Map<String, Object> flash = RequestContextUtils.getOutputFlashMap(request);
flash.put("userInfo", userInfoFromDB);
response.redirect(request.getContextPath() + "/user/user.jsp");
return null;
}
...
The problem is that the client side expects a string response that will not arrive and must be prepared to that. If it is not, you will get an error client side. The alternative would then be not to redirect but pass a special string containing the next URL:
...
}else{
Map<String, Object> flash = RequestContextUtils.getOutputFlashMap(request);
flash.put("userInfo", userInfoFromDB); // prepare the redirect attribute
return "SPECIAL_STRING_FOR_REDIRECT:" + request.getContextPath() + "/user/user.jsp");
}
and let the javascript client code to process that response and ask for the next page.
I want to view the response for the below method in SOAP UI. The url would be as below to call the accountDetails(..) method in SOAP UI to check the response.
http://localhost:8080/AccountInfo/rest/account/0003942390
#RequestMapping(value = /accountDetails/{accNum}, method = RequestMethod.GET)
public void accountDetails(#PathVariable final String accNum)
{
final boolean accountValue = service.isAccountExists(accNum);
if (!accountValue )
{
throw new Exception();
}
}
The method is executed correctly but the response i'm getting in SOAP UI is 404.
accountDetails(..) method return type is void, so do i need to set any extra parameters when i have to check the response for the method in SOAP UI with void return type to show success message.
Below is the message shown in SOAP UI:
HTTP/1.1 404 /AccountInfo/WEB-INF/jsp/account/0003942390/accountInfo.jsp
Server: Apache-Coyote/1.1
Is the exception thrown? If yes, how does the framework handle the exception?
Your method doesn't return anything - see here. Based on the RESTful nature of the URL it seems the method should return something like an AccountDetail. However, if you really just want to see the 200 then just return something like a non-null String.