Spring ResponsEntity body contains extra json with timestamp, status and more - java

We have an REST endpoint that will add a new empty ingredient to an existing meal:
#RequestMapping(value = "/add", method = RequestMethod.PUT, consumes = "application/json;charset=UTF-8")
public ResponseEntity<Object> add(#RequestBody final Meal meal) throws URISyntaxException
{
Optional<Meal> optionalMeal = mealRepository.findById(meal.getId());
if (!optionalMeal.isPresent()) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(MessageUtil.parse(MSG_404_MEAL, meal.getId() + ""));
}
Ingredient ingredient = new Ingredient();
ingredient.setMeal(optionalMeal.get());
ingredientRepository.saveAndFlush(ingredient);
ResponseEntity re = ResponseEntity
.created(RequestUtil.getResourceURI(ingredient.getId()))
.body(ingredient);
return re;
}
Ingredient is an entity class with some fields:
public class Ingredient implements Serializable
{
#Id
private Integer id;
private Meal meal;
private Grocery grocery;
private Float amount;
...
}
RequestUtil takes care of creating the URI where the newly created resource is to be found:
public class RequestUtil
{
public static URI getResourceURI(int id) throws URISyntaxException
{
final String url = RequestUtil.getCurrentRequest().getRequestURL().toString();
final String req = RequestUtil.omitLast(url);
return new URI(req + "get/" + id);
}
public static HttpServletRequest getCurrentRequest()
{
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return ((ServletRequestAttributes) requestAttributes).getRequest();
}
public static String omitLast(final String url) {
return url.substring(0, url.lastIndexOf("/") + 1);
}
}
The http status code and resource URI end up correctly in the response headers, but the body contains two JSONs:
{
"id": 407,
"meal": {
"id": 99,
"name": "New Meal",
"active": true
},
"grocery": null,
"amount": null,
"bought": false
} {
"timestamp": "2018-08-29T19:25:31.466+0000",
"status": 201,
"error": "Created",
"message": "No message available",
"path": "/ingredient/add"
}
Our javascript code does not expect this extra data and fails with
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data at line 1 column 114 of the JSON data
Using a debugger, we can see that by the time the code reaches the return statement in add(), the ResponseEntity does not contain this extra data. Can someone explain where it comes from, and how we stop it from polluting the response?
Thanks for any help!

Related

CompletableFuture: how to combine two asynchronous requests into one response

The first request is sent asynchronously, then the id value is taken from the response to the first request and used in the second asynchronous request. And then both responses to request 1 and request 2 are combined. The example below works, but is not asynchronous because .get() is used. Is there a way to do this asynchronously?
The process in short - everything should happen asynchronously:
Send POST request 1
Use the id value from response 1 for request 2
Send POST request 2
Combine response 1 and response 2 to the final response of the REST controller
This body is sent to the REST controller endpoint "/combine" via POST method:
{
"userid": 1,
"id": 2,
"title": "3",
"body": "4" }
#RestController
public class CombinationController {
#Autowired
CombinationService combinationService;
#PostMapping("/combine")
public CompletableFuture<CombinationBothResponses> combine(#RequestBody RequestBodyOne requestBodyOne) {
return combinationService.combine(requestBodyOne);
}
}
#Service
public class CombinationService {
private final Jsonb jsonb = JsonbBuilder.create();
private final HttpClient httpClient = HttpClient.newBuilder().build();
public CompletableFuture<CombinationBothResponses> combine(RequestBodyOne requestBodyOne) {
// 1. Send POST request 1
HttpRequest httpRequestOne =
HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/posts"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyOne)))
.build();
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses combinationBothResponses =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
CompletableFuture<CombinationBothResponses> completableFuture2 =
sendSecondPostRequest(valueToBeUsedInRequestBody2);
// 4. Combine response 1 and response 2 to the final response of REST controller
try {
CombinationBothResponses responseBodyRequestTwo =
completableFuture2.get(); // Not asynchronous
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
throw new RuntimeException();
});
}
private CompletableFuture<CombinationBothResponses> sendSecondPostRequest(
int valueToBeUsedInRequestBody2) {
RequestBodyTwo requestBodyTwo = new RequestBodyTwo(valueToBeUsedInRequestBody2, "request 2");
HttpRequest httpRequest =
HttpRequest.newBuilder(URI.create("https://reqbin.com/echo/post/json"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyTwo)))
.build();
return httpClient
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses responseBodyRequestTwo =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
return responseBodyRequestTwo;
}
throw new RuntimeException();
});
}
}
#Data
#NoArgsConstructor
public class RequestBodyOne {
private int userId;
private int id;
private String title;
private String body;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public class RequestBodyTwo {
private int id;
private String key;
}
#Data
#NoArgsConstructor
public class CombinationBothResponses {
private int userId;
private int id;
private String title;
private String body;
private String success;
}
Response to request 1:
{ "userId": 0, "id": 101, "title": "3", "body": "4" }
Response to request 2:
{"success":"true"}
Combined responses; response of REST controller:
{
"userId": 0,
"id": 101,
"title": "3",
"body": "4",
"success": "true" }
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenCompose(httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
final CombinationBothResponses combinationBothResponses = jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
return sendSecondPostRequest(valueToBeUsedInRequestBody2)
.thenApply(responseBodyRequestTwo -> {
// 4. Combine response 1 and response 2 to the final response of REST controller
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
});
}
return CompletableFuture.failedFuture(new RuntimeException());
});

Get both Mapping and Pain Json text in Spring Boot Restful webService

i am trying to parse json body request coming in for a post request using Spring Boot. I would like to map the body to fields on vehicle class and also to store plain json body to some variable as well for future use. But i am always getting stream closed exception when trying to access plain json body. Can someone help me out on this. Thanks In Advance
Code
#RequestMapping(value = "/GetDriverDetails", method = RequestMethod.POST)
public ResponseEntity<Vehicle> GetVehicleDetails(#RequestBody Vehicle vehicle, HttpServletRequest request) {
System.out.println(vehicle);
String json;
if ("POST".equalsIgnoreCase(request.getMethod()))
{
try {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return new ResponseEntity<Vehicle>(HttpStatus.OK);
}
Json request Body
{
"vehicleName": "Brio",
"vehicleModel": "fisrtClass",
"drivers": [
{
"name": "rej",
"licenseNumber": "KLLicense1"
},
{
"name": "Dan",
"licenseNumber": "KLLicense2"
},
{
"name": "bala",
"licenseNumber": "KLLicense3"
},
{
"name": "vijay",
"licenseNumber": "KLLicense4"
},
{
"name": "aravind",
"licenseNumber": "KLLicense5"
},
{
"name": "sathya",
"licenseNumber": "KLLicense6"
}
]
}
Exception
java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.springframework.web.util.ContentCachingRequestWrapper$ContentCachingInputStream.read(ContentCachingRequestWrapper.java:254) ~[spring-web-5.2.9.RELEASE.jar:5.2.9.RELEASE]
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:297) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188) ~[na:na]
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:181) ~[na:na]
Can you try following code:
The solution to your main problem, since you are using #RequestBody, contents are already read and mapped to pojo class hence stream is utlized and closed in this case you do not want to use #RequestBody at all. Please find my implementation below:
#PostMapping(path = "update-vehicle-details", consumes = MediaType.ALL_VALUE)
public VehicleDriver updateVehicleDetails(HttpServletRequest request) throws IOException {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
final VehicleDriver vehicleDriver = new ObjectMapper().readValue(collect, VehicleDriver.class);
return vehicleDriver;
}
Otherwise, use a simple approach, read the value from application json content type parses in requestbody and converts that body to string and return the same result
#RestController
public static class TestController {
#PostMapping(path = "update-vehicle-details", consumes = MediaType.APPLICATION_JSON_VALUE)
public String updateVehicleDetails(#RequestBody VehicleDriver vehicleDriver) throws JsonProcessingException {
final StringBuilder stringBuilder = new StringBuilder(vehicleDriver.vehicleName);
List<String> driverDetails = Optional.ofNullable(
vehicleDriver.drivers)
.map(Collection::stream)
.orElse(Stream.empty())
.map(d -> "name=: " + d.name + ", license number:" + d.licenseNumber)
.collect(Collectors.toList());
stringBuilder.append("\n");
stringBuilder.append(driverDetails);
String stringRepresentationOfBody = new ObjectMapper().writeValueAsString(vehicleDriver);
// return stringBuilder.toString();
return stringRepresentationOfBody;
}
}
public static class VehicleDriver {
public String vehicleName;
public String vehicleModel;
public List<Driver> drivers;
}
public static class Driver {
public String name;
public String licenseNumber;
}
Try using Object Mapper for converting your vehicle object to json string and in that case you would not be needing request in method argument.
And you are using post request method then if condition is not needed.

How I get RESTful response correctly?

I am consuming a restful with spring but I do not achieve get all data successfully.
RESTful response detail:
{
"errorCode": "0",
"errorMessage": "OK",
"transactionUUID": "2e48d6e7-f1fb-4271-b282-d74d3df1ef23",
"data":{
"price": "123",
"quantity" : "1",
"productname" : "Anystuff"}
}
My classes:
public class ResponseRest{
private String errorCode;
private String errorMessage;
private String transactionUUID;
private ResponseDetail data;
//get and set methods
}
public class ResponseDetail {
private String price;
private String quantity;
private String productname;
//get and set methods
}
Piece of my method in my controller class:
HttpHeader headers;
//headers.set....
String jsonParam = "{\"transactionToken\":\""+transactionToken+"\","
+ "\"sessionToken\":\""+sessionToken+"\"}";
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<String>(jsonParam, headers);
response = restTemplate.exchange(URL_API_AUTH, HttpMethod.POST, entity, ResponseRest.class);
When I got result by console, I only get the properties from the ResponseRest class but not their detail from class ResponseDetail, console result:
System.out.println("-> Result - status ("+ response.getStatusCode() + ") has body with: " + (response.getBody().toString()));
Result - status (200) has body with: ResponseRest[errorCode="0", errorMessage="OK",transactionUUID="2e48d6e7-f1fb-4271-b282-d74d3df1ef23", data[price=null,quantity=null,productname=null]]
What I doing wrong?
Thanks a lot for their time.

Various Response Entity in Spring Controller

In my controller I am having if condition and two different response type. I will get response in JSON format from "if" condition, but I am getting response from else condition like unexpected '0 , instead I need to get my error message'.
My controller code snippet
#RequestMapping(value = "/saveuser", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST)
public ResponseEntity<?> addUser(#RequestBody TestUser user)
throws NotFoundException {
System.out.println(user.getAnswer().size());
if(questioncount == user.getAnswer().size())
{
return new ResponseEntity<TestUser>(service.addUser(user),
HttpStatus.OK);
}else {
String one="one";
String erromessage = "Only" + questioncount +" questions are allowed";
System.out.println(erromessage);
return new ResponseEntity<String>(erromessage,HttpStatus.NOT_ACCEPTABLE);
}
}

Spring parameter too long

#RequestMapping(value = "/createItem", method = RequestMethod.POST)
#Override
public void createItem(#RequestParam(value="userId") String userId, #RequestParam(value="title") String title, #RequestParam(value="subtitle") String subtitle, #RequestParam(value="description") String description, #RequestParam(value="category") String category, #RequestParam(value="datapack") String datapack) {
this.itemDAO.createItem(userId, title, subtitle, description, category, datapack);
}
I am creating RESTful application with Spring. The method above works just fine, but when the datapack is longer than a certain length it will result in error. The error says...
Error parsing HTTP request header Note: further occurrences of HTTP
header parsing errors will be logged at DEBUG level.
I need to pass the datapack as a parameter and the datapack itself will be a json file which I convert it to string.
The datapack file might be very complex and big. How do I solve this?
Here's the example of the request:
http://localhost:8090/createItem?userId=test&title=test&subtitle=test&description=test&category=test&datapack=
{
"CLASS": "com.mincom.ellipse.edoi.ejb.menu_item.MENU_ITEMRec",
"INSTANCE": {
"m_creationDate": "20150824",
"m_creationTime": "001616",
"m_creationUser": "SR4187",
"m_lastModDate": "20150824",
"m_lastModTime": "001616",
"m_lastModUser": "SR4187",
"m_menuType": "",
"m_invokationString": "",
"primaryKey": {
"m_uuid": "3b4d95fe3dd3432fb00cde0cc25f903f"
}
}
},
{
"CLASS": "com.mincom.ellipse.edoi.ejb.i18n_descriptions.I18N_DESCRIPTIONSRec",
"INSTANCE": {
"m_creationDate": "20150824",
"m_creationTime": "001616",
"m_creationUser": "SR4187",
"m_lastModDate": "20150824",
"m_lastModTime": "001616",
"m_lastModUser": "SR4187",
"m_description": "CUSTOM_MENU",
"primaryKey": {
"m_locale": "en",
"m_uuid": "3b4d95fe3dd3432fb00cde0cc25f903f"
}
}
},
{
"CLASS": "com.mincom.ellipse.edoi.ejb.top_level_menus.TOP_LEVEL_MENUSRec",
"INSTANCE": {
"m_creationDate": "20150824",
"m_creationUser": "SR4187",
"m_lastModDate": "20150824",
"m_lastModTime": "001620",
"m_creationTime": "001620",
"m_lastModUser": "SR4187",
"m_uuid": "3b4d95fe3dd3432fb00cde0cc25f903f",
"primaryKey": {
"m_name": "SX"
}
}
}
Examples above works, but if I test with longer JSON file, it won't work.
Create a controller method that shall be receiving the JSON data posted by $http service using XHR (AJAX)
#RequestMapping(value = "/savecompany_json", method = RequestMethod.POST)
public #ResponseBody String saveCompany_JSON( #RequestBody Company company ) {
//
// Code processing the input parameters
//
return "JSON: The company name: " + company.getName() + ", Employees count: " + company.getEmployees() + ", Headoffice: " + company.getHeadoffice();
}
Create a POJO which maps to JSON object
public class Company {
private String name;
private long employees;
private String headoffice;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getEmployees() {
return employees;
}
public void setEmployees(Long employees) {
this.employees = employees;
}
public String getHeadoffice() {
return headoffice;
}
public void setHeadoffice(String headoffice) {
this.headoffice = headoffice;
}
}
Source
If the request is really big (like when sending files for instance), you might want to change your content-type in your http header. For instance (although might look differently depending on what you're actually sending):
$http({
method: 'POST',
url: url,
headers: {
'Content-Type': 'multipart/form-data'
},
data: {
data: model,
file: file
}
});
Then if the request is too big for the server to accept it, you might need to extend the request size limit. If you're using spring boot, you may go to your application.properties file (should be located under src/main/resources, but if it doesn't exist yet, create it manually) and add the following properties:
multipart.maxFileSize=3MB
multipart.maxRequestSize=3180KB
You are using a POST request anyhow, so simply send the parameters in the body with content type application/x-www-form-urlencoded like a normal form submission instead of in the URL.

Categories

Resources