SpringBoot RestController generic POST type - java

I'm experimenting with building microservices using Spring Boot.
I have a back-end API that receives ResponseEntity POST requests and processes it (saving to database etc). Where Data is an Object of a self-created class.
Now I have a top-level API (that handles authentication,..). The end-users will communicate with the back-end services through this top-level API. So this API basically just has to forward all the requests to the right back-end api's.
In this top API I don't want to need to include all my classes (e.g. the Data class in this case) and I would rather just send it as String json data or something. So I tried this:
#RequestMapping(method = RequestMethod.POST, value="/data")
ResponseEntity<String> createUnit(#RequestBody String data) {
URI uri = util.getServiceUrl("dataservice");
String url = uri.toString() + "/data";
ResponseEntity<String> result = restTemplate.postForEntity(url, data, String.class);
return new ResponseEntity<String>(result.getBody(), HttpStatus.OK);
}
But this results in an org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type.
So my question is, is there a way to forward these requests to my back-end without the need to include all my Object classes in my API? I figured this should be able since this is the same as when a web-browser sends requests in json format without knowing what kind of Object the data actually is.
The back-end handling looks like this:
#RequestMapping(method = RequestMethod.POST, value="/data")
ResponseEntity<Data> saveData(#RequestBody Data data) {
//Some code that processes the data
return new ResponseEntity<Data>(dataProcessed, HttpStatus.OK);
}

When posting the String to the backend service you have to specify the Content-Type header so Spring knows which HttpMessageConverter to use to deserialize the Data object.
With RestTemplate you can specify the header like this:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(data, headers);
restTemplate.postForEntity(url, entity, responseType);

Even though the question was already answered, I'll show another way I managed to solve the problem.
I used type Object instead of String:
#RequestMapping(method = RequestMethod.POST, value="/data")
ResponseEntity<Object> createUnit(#RequestBody Object data) {
URI uri = util.getServiceUrl("dataservice");
String url = uri.toString() + "/data";
ResponseEntity<Object> result = restTemplate.postForEntity(url, data, Object.class);
return new ResponseEntity<Object>(result.getBody(), HttpStatus.OK);
}

Related

How do I upload a file and pass it to another service without writing it to the file system in Spring Boot?

Suppose I have a front end. On said front end, I want to upload a file. The controller in the Spring Boot (Java) app receives said file as a MultipartFile (Service A). I want to take the input stream from that, and send it to a different service (Service B) without writing said stream to the file system. Service B should return something to Service A which sends said response to the client to let me know it has handled said file after completion of the streaming. I am unsure which libraries/classes to use to achieve this in a Spring Boot app. I assume Java or Spring Boot has all kinds of helper classes to make this trivial, I'm just unsure which and how to string them together in the correct order.
Client --> Service A --> Service B
Any help would be appreciated, as the current approach of writing it to a file system is a horrible approach that I want refactored ASAP.
#Autowired
RestTemplate restTemplate
public customResponse myFileUpload(#RequestParam("foo") MultipartFile myFile) {
//myFile comes in fine, I can pull input stream via myFile.getInputStream();
//Should pull stream from myFile and return response from Service A here.
//Not sure if I need to map the input to an outputStream or something?
return restTemplate.postForObject(serviceA.url, ???, customResponse.class);
}
Using RestTemplate you can send Multipart Form Data requests. Below you can find a code snippet on how to prepare such requests.
Also RestTemplate allows you to upload Resource, which you can obtain from the MultipartFile by calling MultipartFile#getResource()
public String myFileUpload(#RequestParam("foo") MultipartFile myFile) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("bar", myFile.getResource());
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(
"http://localhost:8080/serviceB", // <-- put real endpoint here
requestEntity,
String.class
);
return responseEntity.getBody();
}

Adding another MediaType to REST api endpoint that is used by other clients

In my app I am sharing REST api using Spring MVC, which users may use in their custom apps. Let's say I have an endpoint in Controller class:
#GET
#Path("/getNumber")
#Produces({ MediaType.TEXT_PLAIN })
public String getNumber(Long id) {
return service.getNumber(id);
}
which response is available only as TEXT_PLAIN. What would happen, if for example one of the client say that his app will not work when he gets response as plain text and that the endpoint should return json/or should have possibility to return response in json? So when I add another MediaType to annotation #Produces, may that cause problems with other users custom apps using this endpoint? Because for ex. in their apps, client may be expecting response as plain text, and by getting response as json, response will not be handled correctly?
If adding this MediaType may cause problems, what can I do to take into account users custom apps using this endpoint? Should I create similar endpoint, but this one will have MediaType APPLICATION_JSON, or both, with added for ex. "/v2" in endpoint path, or is there some better solution for that?
#GET
#Path("/v2/getNumber")
#Produces({ MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
public String getNumber(Long id) {
return service.getNumber(id);
}
No need to create multiple endpoints. You can define multiple mediaTypes in #Produces.
Inorder to decide which MediaType should be sent as response, Client has to set the type of MediaType they are expecting in the "Accept" header when making the request.
Based on this "Accept" header you can decide what content-type to return.
you can use "consume" keyword for Multiple media type
#GET(value = "/configuration", consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.MULTIPART_FORM_DATA_VALUE
})
public String setConfiguration(#RequestPart MultipartFile file,
#RequestBody Configuration configuration)

RestTemplate & multipart/form-data response

I need to write a REST Client using RestTemplate that will call the following endpoint:
#RequestMapping(value = "/{documentID}", method = RequestMethod.GET, produces = "multipart/form-data")
#ResponseBody
ResponseEntity<MultiValueMap<String, Object>> getDocument(#PathVariable("documentID") long documentID);
This endpoint builds multipart/form-data response including a document (InputStreamResource) and the document's info (JSON) parts.
However, I receive the following exception:
org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [interface org.springframework.util.MultiValueMap] and content type [multipart/form-data;boundary=f9yLuCpxZoS4W5lu5iYivlD8fIo28BBMr5PXzu;charset=UTF-8]
I have FormHttpMessageConverter (that is supposed to process form data to/from a MultiValueMap) in my RestTemplate, but it still doesn't work because according to the official docs this converter can't read multipart/form-data (only write):
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/FormHttpMessageConverter.html
This endpoint works fine via Postman, returning both JSON and File parts, so I'm wondering which kind of magic I'm missing to make it work using RestTemplate.
Is it possible to write a REST client to process multipart/form-data response and if yes, which converter should be used for such messages, do I have to write a custom HttpMessageConverter?
I collided with the same case and wrote a simple example(MultipartMessageConverter). This example allows to convert a request(resource and JSON) to one DTO model as you can se in test. A model can consist Resource
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MultiPartMessageConverter(objectMapper));
final ResponseEntity<ResultModel> responseEntity = restTemplate.getForEntity("http://localhost:" + randomServerPort + "/test", ResultModel.class);
final ResultModel resultModel = responseEntity.getBody();
Assertions.assertNotNull(resultModel);
Assertions.assertEquals(2, resultModel.secondModel.size());
Assertions.assertEquals("Param1.Value", resultModel.firstModel.param1);
Assertions.assertEquals("Param2.Value", resultModel.firstModel.param2);

How to set value to #RequestAttribute in Spring boot using postman or feign client

I have method like this:
#PostMapping(path = "/workflow-services/{service_id}/tickets",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TicketIdResponse> createTicket(#PathVariable("service_id") String serviceId,
#RequestBody #Validated CreateTicketRequest request, #RequestAttribute Payload payload) {
log.info("Start create ticket [{}]", request);
TicketIdResponse response = ticketService.createTicket(serviceId, request, payload);
log.info("Create ticket response: {}", response);
return ResponseFactory.success(response);
}
so how to set value to #RequestAttribute Payload in postman or feign client
Thank you very much!
The #RequestAttribute annotation is usually used to retrieve data that is populated on the server-side but during the same HTTP request. For example, if you have used an interceptor, filter or possibly an aspect to populate the "payload" attribute then you should be able to access this using the #RequestAttribute annotation.
If you are looking to pass something from an external client (i.e via postman, curl or any other simple client) - #RequestAttribute is not the way forward.
Good references;
https://www.baeldung.com/whats-new-in-spring-4-3
https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/request-attribute.html
This SO post may also help.

Spring Boot Content type 'multipart/form-data;boundary=--------------------------#;charset=UTF-8' not supported

I'm new to Spring...I have a Spring Boot API application and all of my methods (POST, GET, etc) work great with Postman when the Content-Type is set to application/json, I can send and receive JSON.
However, I'd really like for my methods to also accept a GET or POST in the browser. When I use the browser to do a GET, the API returns an INTERNAL_SERVER_ERROR. I created a small form and try to POST to my API, but then I get the UNSUPPORTED_MEDIA_TYPE: Content type 'multipart/form-data;boundary=--------------------------802438220043016845671644;charset=UTF-8' not supported
These are 2 of the methods in my #RestController:
#RequestMapping(method = RequestMethod.POST, value = {"","/"})
public ResponseEntity<MyModel> createModel(#Valid #RequestBody MyModelDto modelDto) {
MyModel model = modelService.createModel(modelDto);
URI createdModelUrl = ServletUriComponentsBuilder.fromCurrentRequest().path("/{identifier}")
.buildAndExpand(model.getIdentifier()).normalize().toUri();
return ResponseEntity.created(createdModelUrl).build();
#RequestMapping(method = RequestMethod.GET, value = "/{identifier}")
public Resource<MyModel> getByIdentifier(#PathVariable("identifier") String identifier) {
MyModel model = modelService.getByIdentifier(identifier);
Resource<MyModel> resource = new Resource<>(model);
return resource;
}
If there's any other code that would be helpful to show, let me know and I'll update the thread.
In createModel method, instead of #RequestBody, please use #ModelAttribute for MyModelDto parameter.
You can use can try following ways,
Set consume block in "#RequestMapping".
like , #RequestMapping(value="/abc", consume = "multipart/form-data", method=HTTPMethod.POST")
Use #Multipart annotation and file object as #Part annotation
Instead of use #RequestBody use #RequestPart.

Categories