Spring Webclient Post Method `BadRequest` error for a request that works in Postman (and curl) - spring-boot

I'm calling an external API via WebClient to create a resource. But getting WebClientResponseException$BadRequest while testing from Postman. Here are few things I've tried without success:
Building URI path from uribuilder
Coverting entity to MultiValueMap before inserting into the request
Controller:
#PostMapping("/createMessage")
public String createMessage(#RequestBody Message request) {
return service.createMessage(request);
}
Service:
public String createMessage(Message requestBody) {
ObjectMapper mapper = new ObjectMapper();
MultiValueMap requestBodyForm = new LinkedMultiValueMap<String, Object>();
Map<String, Object> requestBodyMap = mapper.convertValue(requestBody, new TypeReference<Map<String, Object>>() {});
requestBodyForm.setAll(requestBodyMap);
String id = webClient.post()
.uri(properties.getMessageUrl())
.contentType(MediaType.APPLICATION_JSON)
.headers( headers ->
headers.setBasicAuth(user,pass))
.body(BodyInserters.fromValue(requestBodyForm))
.retrieve()
.bodyToMono(String.class)
.block();
return id;
}
Any direction where might be the problem would help a lot.
The external service is an IoT device controller API, with predefined message format:
$ curl --location --request POST 'http://external-service/message' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic xxxxxxxxxxxx' \
--data-raw '[{
"message": "Testing service reachability",
"defaulttext": "This is Message Zone",
"startDate": "2021-05-25 15:20:00",
"duration":10,
...
}]'
198

Related

415 Error when authenticating - Spring boot and RestTemplate

I am working on a third-party REST API and I am going to authenticate with their API by making a POST request to the provided end point. I tested this end point using Postman and it works fine, and now I wanted to do it programmatically.
I am using Spring boot 3.0 for development and RestTemplate as the http client. The code I have implemented is below, it gives me a 415 UNSUPPORTED_MEDIA_TYPE error.
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==");
headers.add("Accept", "application/json");
headers.add("Content-Type","application/x-www-form-urlencoded");
MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<String, String>();
requestBody.add("grant_type", "client_credentials");
HttpEntity formEntity = new HttpEntity<MultiValueMap<String, String>>(requestBody, headers);
String serverEndPoint = "https://xxxxxxx.xxx/auth/oauth2/xx/token";
ResponseEntity<String> response = restTemplate.exchange(serverEndPoint, HttpMethod.POST,formEntity, String.class);
System.out.println(response.getStatusCode().value());
Build RestTemplate
#Bean
public RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// don't throw
}
});
return restTemplate;
}
But same logic I have implemented using Jersey http client as mentioned below, it gives me successful response with 200 response code.
JacksonJsonProvider jacksonJsonProvider = new JacksonJaxbJsonProvider()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Client client = ClientBuilder.newClient(new ClientConfig(jacksonJsonProvider));
Form form = new Form();
form.param("grant_type", "client_credentials");
String serverEndPoint = "https://xxxxxxx.xxx/auth/oauth2/xx/token";
Response response = client.target(serverEndPoint)
.request("application/json")
.header("Authorization", "Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==")
.post(Entity.entity(form, APPLICATION_FORM_URLENCODED));
System.out.println(response.getStatus());
Kindly help me to resolve this since I need to move forward with the RestTemplate client.
cUrl From Postman
curl --location --request POST 'https://xxxxxxxxxxxxx.xxx/auth/oauth2/xx/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==' \
--header 'Cookie: coolie data bla bla' \
--data-urlencode 'grant_type=client_credentials'
If I inspect what the successful calls (curl and the Jersey HttpClient) send to the server vs what RestTemplate.exchange() sends, the only significant difference is that RestTempate.exchange() appends the charset=utf-8 to the Content-Type header.
I suspect that the sever you are calling is too strict on Content-Type and throws a 415 if it does not match "application/x-www-form-urlencoded" exactly.
To prevent this you need to use RestTempate.postForObject() instead. Try changing your RestTemplate call code as follows:
String serverEndPoint = "https://httpbin.org/post";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> entity = new HttpEntity<>("grant_type=client_credentials", headers);
String result = restTemplate.postForObject( serverEndPoint, entity, String.class);
The MediaType is org.springframework.http.MediaType.
Note that I call httpbin.org in the code fragment. I find it extremely useful in researching an issue like this.

FormData is missing when using Content-Type: 'application/x-www-form-urlencoded' in Spring 2.5.3

Based on the answer from: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported for #RequestBody MultiValueMap
I'm have write post method:
#PostMapping (value = "test_3d_gmo", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseData<String> returnUrlCall(#RequestParam Map<String, String> param, MultiValueMap<String, String> body) {
LOGGER.info("request param {}", param.toString());
LOGGER.info("request body {}", body.toSingleValueMap());
ResponseData<String> res = new ResponseData<>();
res.setAppData("Test");
return res;
}
I'm send request to this API using postman:
curl --location --request POST 'localhost:8080/api/cc/test_3d_gmo?message=a' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'param1=g1' \
--data-urlencode 'param2=g2' \
--data-urlencode 'param3=g3'
But my body always missing, param and body only have query string message. How i can fix this ?

springframework rest template: Method Not Allowed: [no body]

I need to recover a token from amazon cognito service.
I tried to use Spring RestTemplate, but when I request the URL in Java I got the following error:
org.springframework.web.client.HttpClientErrorException$MethodNotAllowed: 405 Method Not Allowed: [no body]
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:117)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:186)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:125)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:819)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:602)
Below is my code (okay compagny and aHash were removed for this ticket):
Map<String, String> params = new LinkedHashMap<>();
params.put("grant_type", "client_credentials");
// build the url with params
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("https://company.auth.eu-west-1.amazoncognito.com/oauth2/token");
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
Map<String, String> headers = new LinkedHashMap<>();
headers.put(AUTHORIZATION, "Basic aHash==");
headers.put("Content-Type", "application/x-www-form-urlencoded");
log.error("GenerateRemoteTokenURI requesting: {}", builder.toUriString());
HttpEntity<String> tokenResponseEntity = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, new HttpEntity<>(headers),
String.class);
If I use a curl with that:
curl --location --request POST 'https://company.eu-west-1.amazoncognito.com/oauth2/token?grant_type=client_credentials' \ [14:28:53]
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic aHash=='
I got my access_token without any problem.
Do you know why I got this error from Spring, and how I can resolve the problem, please?
Thank you a lot and best regards

POST request with form data using Spring RestTemplate

I have to make a HTTP POST request with form data to an external API. Currently, I am invoking the API using the below curl command
curl https://books.zoho.com/api/v3/contacts?organization_id=10234695
-X POST
-H "Authorization: Zoho-oauthtoken 216793f385b9dd6e125f"
-H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8"
-F 'JSONString="{
"contact_name": "Bowman and Co",
"company_name": "Bowman and Co",
"website": "www.bowmanfurniture.com",
"contact_type": "customer"
}
I have implemented ClientHttpRequestInterceptor interface and registered the interceptor with the RestTemplate. I am not sure, how to post the HTTP request with form data. Kindly let me know, how to submit the form data or point me to any reference.
Inside my Controller class:
#RestController
#RequestMapping("/api/v1/contacts")
public class ContactController {
#Autowired
private RestTemplate restTemplate;
#PostMapping("/")
public Invoice createContact(#RequestBody Contact contact){
//TODO
return null;
}
}
You could use a rest template to make a post request with form data.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("data", "data");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<String> response = restTemplate.postForEntity( url, request , String.class );
Spring will automatically take care of the message converters used in this case.(The message converter used will be FormHttpMessageConverter).
Read Doc

How to mimic curl REST call with Spring's RestTemplate?

Have an external Restful Web Service which takes in JSON payloads if its is more than one input but if its a single input it just needs the value.
For example, for multiple inputs, this works:
curl -H "Content-Type: application/json" -X POST -d '{ "amount": 10000, "interestRate": ".28", "term": "12", "state": "Georgia"}' http://localhost:8080/webservices/REST/sample/loan
Returns:
Approved
For single inputs:
curl -H "Content-Type: application/json" -X POST -d "18" http://localhost:8080/webservices/REST/sample/age
Returns:
Approved
Using Spring Boot, tried to create a JUnit test, in order, to see if I can post to this external service using Spring's RestTemplate API.
public void RestWebServiceTest {
private RestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void setup() {
restTemplate = new RestTemplate();
headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
}
#Test
public void validLoan() {
final String uri = "http://localhost:8080/webservices/REST/sample/Loan";
Map<String, String> input = new HashMap<>();
input.put("amount", "10000");
input.put("interestRate", ".28");
input.put("term", "12");
input.put("state", "Georgia");
String result = restTemplate.postForObject(uri, input, String.class);
assertEquals("Approved", result);
}
#Test
public void validAge() {
final String uri = "http://localhost:8080/webservices/REST/sample/age";
Integer input = 18;
String result = restTemplate.postForObject(uri, input, String.class);
assertEquals("Approved", result);
}
#Test
public void validCountry() {
final String uri = "http://localhost:8080/webservices/REST/sample/country
String input = "US";
String result = restTemplate.postForObject(uri, input, String.class);
assertEquals("Approved", result);
}
}
All of these work except for the validCountry() test method:
org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:641)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:597)
This is strange because this curl command works for the same call:
curl -H "Content-Type: application/json" -X POST -d 'US' http://localhost:8080/webservices/REST/sample/country
Returns:
Approved
Question(s):
How can one mimic the rest call for the country (see above curl command) inside the validCountry() test method?
Do I need to add or change a different value for HTTP Headers (inside setup() method)?
Don't understand that validAge works by using the Integer wrapper class but the String doesn't?
Is there a better way to do this using Spring's RestTemplate API?
Thank you for taking the time to read this...
You need to set the Content-Type to application/json. Content-Type has to be set in request. Below is the modified code to set the Content-Type
#Test
public void validCountry() {
final String uri = "http://localhost:8080/webservices/REST/sample/country";
String input = "US";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<String>(input, headers);
String result = restTemplate.postForObject(uri, request, String.class);
assertEquals("Approved", result);
}
Here, HttpEntity is constructed with your input i.e "US" and with headers.
I think this answers you questions 1,2 and 4. But not 3.

Resources