Using BodyInserters to pass parameter with webClient - java

There is my code.
public Mono<RespDto> function(TransReqDto1 reqDto1, TransReqDto2 reqDto2, String token) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("TransReqDto1", reqDto1);
builder.part("TransReqDto2", reqDto2);
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
LinkedMultiValueMap map = new LinkedMultiValueMap();
map.add("TransReqDto1", reqDto1);
map.add("TransReqDto2", reqDto2);
return
client.post()
.uri("/api")
.body(BodyInserters.fromValue(reqDto1))
.headers(h -> h.setBearerAuth(token.split(" ")[1]))
.retrieve()
.bodyToMono(RespDto.class);
}
My probelm is that I need to send both reqDto1 & reqDto2. I've successfully sent reqDto1 with the code above but I can't figure out a way to send two objects.
Tried MultipartBodybuild and MultiValueMap but both are returning error from the target API. Please give me some hints!! Thank you
Here is the API I am trying to call!
#PostMapping("")
#ApiOperation(value = "test", notes = "test")
public Mono<?> transPost(#Valid #RequestBody TransReqDto1 reqDto1,
#Valid #RequestBody TransReqDto2 reqDto2) {
return testService.function(reqDto1, reqDto2);
}

You cannot use two #RequestBody. It can bind to a single object only. The expected way to do that is to create a wrapper DTO containing all the relevant data:
public class TransReqDto {
private TransReqDto1 transReqDto1;
private TransReqDto2 transReqDto2;
//...
}

Related

Passing a BODY of a POST request to the server

I have built a Restful-API Java(SpringBoot) and created the needed requests.
The following request is a POST Request to add new Category.
I have tested the POST request by POSTMAN, and it working as expected.
I am building the client-side in ASP.NET 5.x.x.
Now the problem appear when I am calling the post request, it seems the API doesn't receive the data (#RequestBody category) that has been send from the client.
Here is a code simple of how I have created them
Server Side:
#ResponseStatus(HttpStatus.CREATED)
#PostMapping(value = "/add", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public CategoryDTO create(#RequestBody CategoryDTO category) {
log.info("Adding new Category Name: " + category.getName());
return categoryMapper.asCategoryDTO(categoryService.save(categoryMapper.asCategory(category)));
}
Client Side
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Category category)
{
Category newCategory = new Category();
// Serialize the concrete class into a JSON String
var stringPayload = JsonConvert.SerializeObject(category);
// Wrap the JSON inside a StringContent which then can be used by the HttpClient class
StringContent content = new StringContent(stringPayload);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.PostAsync("http://localhost:8080/category/add", content))
{
string apiResponse = await response.Content.ReadAsStringAsync();
newCategory = JsonConvert.DeserializeObject<Category>(apiResponse);
}
}
return RedirectToAction("Index");
}
I don't know what is wrong there, could anybody help!
EDIT--
Here is the request via postman
EDIT
I have created another POST request but as a RequestParam instead of RequestBody
#ResponseStatus(HttpStatus.CREATED)
#PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
public CategoryDTO addCategory(#RequestParam(name = "categoryName") String categoryName){
return categoryMapper.asCategoryDTO(categoryService.addCategory(categoryName));
}
and created in the client side the request as following
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Category category)
{
Category newCategory = new Category();
var parameters = new Dictionary<string, string> { { "categoryName", category.Name } };
var encodedContent = new FormUrlEncodedContent(parameters);
using (var httpClient = new HttpClient())
{
using (var response = await httpClient.PostAsync("http://localhost:8080/category/add", encodedContent))
{
string apiResponse = await response.Content.ReadAsStringAsync();
newCategory = JsonConvert.DeserializeObject<Category>(apiResponse);
}
}
return RedirectToAction("Index");
}
And It's works fine!
So the problem is how to pass the data via the httpClient, which need to be of type RequestBody (the data in the body not in the header!) also as a application/json.
So how to pass the data?
I suppose that your spring boot application just blocks POST request because you didn't provide instruction how to handle requests. Try to disable csrf protection like it did here: https://stackoverflow.com/a/48935484/13314717
It might be a problem in the naming convention. Starting with capital letters in properties in C#, and starting with lowercase in Java.
If your class looks like this in C#
class Category {
int Id;
string Name;
...
}
And like this in Java
class Category {
int id;
string name;
...
}
It is not going to work so you have to match the property names. So make either both id or both Id.

Axios post request to springboot backend

I'm trying to send a formData post request (using axios) to my backend (springboot) but I'm not sure of the proper way to do it. My plan is to pass the data through the controller to a service that will utilize it.
Axios call -
startStreamLocation() {
const location = new FormData();
location.set("accuracy", this.accuracy)
location.set("lat", this.lat)
location.set("lng", this.lng)
location.set("timeStamp", this.timeStamp)
axios.post("http://localhost:8080/api/v1/location/request-location", location,
{headers: {'Content-Type': 'application/json'}})
},
Controller -
#PostMapping(value = "request-location")
public ResponseEntity<?> requestLocation() {
connectionRequestService.addDataToStream();
return new ResponseEntity<Authenticator.Success>(HttpStatus.OK);
}
Service -
public void addDataToStream() {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);
AmazonKinesis kinesisClient = AmazonKinesisClient.builder()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion(awsRegion)
.build();
PutRecordsRequest putRecordsRequest = new PutRecordsRequest();
putRecordsRequest.setStreamName("location-stream");
List <PutRecordsRequestEntry> putRecordsRequestEntryList = new ArrayList<>();
PutRecordsRequestEntry putRecordsRequestEntry = new PutRecordsRequestEntry();
putRecordsRequestEntry.setData(ByteBuffer.wrap(( INJECT DATA HERE ).getBytes()));
putRecordsRequestEntry.setPartitionKey(String.format("partitionKey-%d"));
putRecordsRequestEntryList.add(putRecordsRequestEntry);
putRecordsRequest.setRecords(putRecordsRequestEntryList);
PutRecordsResult putRecordsResult = kinesisClient.putRecords(putRecordsRequest);
System.out.println("\nData sent successfully... \n" + putRecordsResult);
try {
Thread.sleep(1000);
}
catch (InterruptedException exception) {
throw new RuntimeException(exception);
}
}
Since you want to send form data to the server, you would need to change the Content-Type header in your Axios call to multipart/form-data. This helps the server understand the resource type being sent by the client.
On the server end, you'll want to read this form data. I can think of the following two ways to do that
Use #RequestParam to read individual form keys. For example, if my form data contains a key named Foo, I'd read it on the server end as this
#PostMapping(value = "/form-data")
public void readFormData( #RequestParam(value = "Foo") String foo )
Use #RequestBody to map the form data to a MultiValueMap which can be then read from like a normal map. Here's the code snippet for the same
#PostMapping(value = "/form-data")
public void readFormData( #RequestBody MultiValueMap<String,String> formData )

In a WebFlux application, form data is accessed via ServerWebExchange.getFormData()

I'm trying to access the request body of a request in a Spring Webflux application but when I try to do so I get the following:
java.lang.IllegalStateException: In a WebFlux application, form data is accessed via ServerWebExchange.getFormData().
Using this code:
#PostMapping(value = "/in")
public ResponseEntity<?> receiveSms(#RequestBody MultiValueMap<String, String> params) {
return ResponseEntity.ok().build();
}
If i use the ServerWebExchange.getFormData() I get an empty list of params.
i believe you can do something like:
#PostMapping(value = "/in")
public ResponseEntity<?> receiveSms(ServerWebExchange serverWebExchange) {
return serverWebExchange.getFormData()
.flatMap(multiValueMap -> {
// process multiValueMap here
}).thenReturn(ResponseEntity.ok().build())
}

PostMapping with Parameters from URI

I am trying to build a spring boot #PostMapping method that gets it's parameters from the uri, something like this
http://localhost:8091/url/log?param1=asdf&param2=asd&param3=test
or like this
http://localhost:8091/url/log/msg/msg1/msg2
Is there a way to expand the code model from below to 3 parameters?
headers.setLocation(builder.path("/article/{param1}/{param2}/{param3}")
#PostMapping("article")
public ResponseEntity<Void> addArticle(#RequestBody ArticleInfo articleInfo, UriComponentsBuilder builder) {
Article article = new Article();
BeanUtils.copyProperties(articleInfo, article);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(builder.path("/article/{id}").buildAndExpand(article.getArticleId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
If you simply want your API call to create a new Article, how about simply having a PostMapping to /article/new (for example) and then simply pass the new Article's parameters as RequestBody?
#PostMapping("article/new")
public ResponseEntity<Void> addArticle(#RequestBody Article article) {
// ...
}
Then as RequestBody you would have something like:
{ "param1": "value1", "param2": "value2", "param3": "value3" }
If you're just looking to include more PathVariable's to your api call, refer to #sovannarith cheav's answer
I hope this helps
Is there a way to expand the code model from below to 3 parameters? headers.setLocation(builder.path("/article/{param1}/{param2}/{param3}")
You can use #PathVariable, like below
#PostMapping("article/{param1}/{param2}/{param3}")
public ResponseEntity<Void> addArticle(#PathVariable("param1") String param1, #PathVariable("param3") String param3, #PathVariable("param3") String param3) {
//enter code here
}

Return value from asynchonous running method to callback URL

I want to trigger a method to create a ArrayList and return this list to a callback-URL. This method can take a while until the data has been generated so it should be an asynchronous running method.
So I got a few questions:
What's a callback-URL? I assume it's the URL the method is returning the value to?
How to returna value to a callback-URL?
How to access the value after it has been returned?
#Async
#GetMapping("/createcustomer/{start}/{end}"){
public ArrayList<Customer> createCustomer(#PathVariable int start,
#PathVariable int end) {
//code to generate random data
return arrayList;
}
In order to call your handler method you can use javascript code on your front-end side. Here is an example how you can do it with axios library:
axios.get('createcustomer/something/something')
.then(function (response) {
console.log(response);
// response.data will contain your json with returned list content
})
.catch(function (error) {
console.log(error);
});
You will also need to modify your hanlder method like this:
#ReponseBody
#GetMapping("createcustomer/{start}/{end}"){
public ArrayList<Customer> createCustomer(#PathVariable int start,
#PathVariable int end) {
//code to generate random data
return arrayList;
}
The #ResponseBody annotation will cause the ArrayList to be returned as json in the body of the response. Alternatively, you can change #Controller class annotation to #RestController. It will make all your handler methods in this class to return back to the client.
This way, the request will be totally asynchronous and you can access it in your front-end code as soon as it's available.
Edit:
If you want to send this list somewhere else using callback-url, you can use RestTemplate to send post request with list as body. You can use something similiar to this code inside your handler method:
String callback-URL = "http://my.server.com/customerListEndpoint";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<?> entity = new HttpEntity<>(headers);
restTemplate.exchange(callback-URL,HttpMethod.POST,entity,ArrayList.class);
You need an endpoint that can be mapped with post requests to callback-URL. Something like:
#PostMapping("/customerListEndpoint"){
public void createCustomer(#RequestBody ArrayList<Customer> arrayList) {
// do what you want with arrayList here
}
Also, be sure to configure the RestTemplate bean in some class anottated with #Configuration:
#Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(requestFactory));
restTemplate.getMessageConverters()
.add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}

Categories

Resources