RestTemplate execute() method cannot send JSON Payload - spring

In my application, I need to take data from another request and chain into a new one
I must use the exchange() method of RestTemplate because I have issue with jacksons lib and I cannot add/change the libs.
this is my code:
final RequestCallback requestCallback = new RequestCallback() {
#Override
public void doWithRequest(final ClientHttpRequest request) throws IOException {
request.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
// Add basic auth header
String auth = username + ":" + password;
byte[] encodedAuth = Base64Utils.encode(auth.getBytes(StandardCharsets.US_ASCII));
String authHeader = "Basic " + new String(encodedAuth);
request.getHeaders().add("Authorization", authHeader);
// Add Headers Request
Enumeration headerNamesReq = servletRequest.getHeaderNames();
while (headerNamesReq.hasMoreElements()) {
String headerName = (String) headerNamesReq.nextElement();
if (whiteListedHeaders.contains(headerName.toLowerCase())) {
String headerValue = servletRequest.getHeader(headerName);
request.getHeaders().add(headerName, headerValue);
}
}
request.getHeaders().forEach((name, value) -> {
log.info("RestExecutorMiddleware", "HEADERS ---\t" + name + ":" + value);
});
IOUtils.copy(new ByteArrayInputStream(payload.getBytes()), request.getBody());
}
};
// Factory for restTemplate
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
ClientHttpResponse responsePost = restTemplate.execute(url, method, requestCallback, new ResponseFromHeadersExtractor());
But at the end, the endpoint cannot receive my JSON (receive data, but not JSON.)
Where I wrong?
Thanks

Very inaccuracy code. Make all steps one-to-one and it will work, you make optimization later ...
Basic Auth. Don't do unnecessary actions
var headers = new HttpHeaders();
headers.setBasicAuth(username, password);
That's all, Spring will take care of everything else - to apply Base64, add Basic: and set properly a header.
Set all required headers including headers.setContentType(MediaType.APPLICATION_JSON)
Get an entity/object which you need to send (set as a body) with a request.
Serialize your object. The most popular, proper and simple way is using fasterxml json framework, you can make serialization with mapper.writeBalueAsString(<your object>). If you really cannot use external libraries, HttpEntity should make it: var request = new HttpEntity<>(<object>, headers);
Make restTemplate request. In almost all cases more convenient methods are restTemplate.postForObject(), restTemplate.getForObject(), restTemplate.postForEntity(), etc.: restTemplate.postForObject(uri, request, ResponseObject.class)

Related

Spring RestTemplate seems to not be thread-safe wrt headers

I have a Spring web client which posts to a Spring web server (the same URL) using two different basic-auth users. Is it a known issue that I can not use a single RestTemplate for both?
When I use a single RestTemplate, and the requests are nearly simultaneous (in different threads), though I specify different users in the header, the receiving server thinks they're from the same user! Note that the request and the headers (and the body of the post) are newly allocated for each request.
It works fine, when I use a single RestTemplate and put a synchronized() around the call to
response = RestTemplate.exchange(url, method, requestParams, MyResponse.class)
I've also tried creating two RestTemplate instances, one for each user - (each built with a RestTemplateBuilder) that works, too. I'll keep this solution, but it surprises me that it's needed.
Is this a known issue?
(I see stackOverflow answers that a RestTemplate is thread-safe after constructed, but the headers are passed in with the request, not as a setting on the already-constructed RestTemplate...)
====
Here's an example of 2 different calls, using 2 different RestTemplates because there were sometimes problems in using the same:
public OperationStatus getOpStatus(String gufi) {
HttpEntity<String> requestParams = new HttpEntity<>(Utils.createBasicHeader(cfg.getManager(), cfg.getManPass()));
ResponseEntity<OperationStatus> restResponse = null;
try {
restResponse = managerRestTemplate.exchange(
cfg.getNussOpApiPath(), HttpMethod.GET, requestParams, OperationStatus.class);
} catch (RestClientException e) {
...
}
OperationStatus opState = restResponse.getBody();
opState.setHttpStatusCode(String.valueOf(restResponse.getStatusCodeValue()));
return opState;
}
Here was a method to do a post, using the priority to switch rest templates (at the time, the target server recognized the priority by the privileges of the user)
UTMRestResponse doPost(Object objToSend, String url, String msg) throws IOException {
String user = cfg.getOpUser();
String pass = cfg.getOpPass();
RestTemplate restTemplate = opUserRestTemplate;
boolean isPriorityOp = false;
if ( objToSend instanceof OpPost) {
OpPost post = (OpPost) objToSend;
String flightNum = post.getFlightNumber();
isPriorityOp = Boolean.TRUE.equals(post.getPriorityOp()); // null is false
} else if ( objToSend instanceof PositionPost) {
PositionPost post = (PositionPost) objToSend;
isPriorityOp = Boolean.TRUE.equals(post.getPriorityOp()); // null is false
}
if (isPriorityOp) {
user = cfg.getUserEmergency();
pass = cfg.getPassEmergency();
restTemplate = emergRestTemplate;
}
String jsonToSend = CommonsObjectMapper.get().writeValueAsString(objToSend);
HttpEntity<String> requestParams = new HttpEntity<>(jsonToSend, Utils.createBasicHeader(user, pass));
UTMRestResponse restResponse = restTemplate.exchange(
url, HttpMethod.POST, requestParams, UTMRestResponse.class).getBody();
if (restResponse.getHttpStatusCode().startsWith("4")) {
String fmt = "Status:{}, url:{}, jsonSent:{}, response:{}";
logger.error(fmt, restResponse.getHttpStatusCode(), url, jsonToSend, restResponse.getMsg());
}
return restResponse;
}

spring boot rest client connection Exception:: org.springframework.web.client.HttpClientErrorException: 400 null

while i am executing below code i am getting error like
"org.springframework.web.client.HttpClientErrorException: 400 null".
but when i use postman to call this "http://localhost:2018/test" it is working.
static final String URL_EMPLOYEES = "http://localhost:2018/test";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(new MediaType[] {
MediaType.APPLICATION_JSON}));
// Request to return XML format
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("replyMsg", "str");
// HttpEntity<Employee[]>: To get result as Employee[].
HttpEntity<String> entity = new HttpEntity<String>(headers);
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// Send request with GET method, and Headers.
ResponseEntity<String> response =
restTemplate.exchange(URL_EMPLOYEES,
HttpMethod.POST, entity,String.class);
HttpStatus statusCode = response.getStatusCode();
// Status Code: 200
if (statusCode == HttpStatus.OK) {
// Response Body Data
msg=response.getBody();
if (msg != null) {
System.out.println(msg);
}
}
//my clint controller class
#RestController
public class TextController {
#RequestMapping(value="/test",method = RequestMethod.POST)
public String myData2(#RequestBody String payload) {
return "done";
}
}
any suggetions?
If you're using Jackson as your JSON parser, you can simply declare your parameter with the type TextNode. This is the Jackson type representing JSON strings.
public String updateName(#PathVariable(MY_ID) String myId, #RequestBody TextNode name) {
You can then use its asText method to retrieve its text value.
Here you are setting headers Content-Type with type JSON and passing the body of type text/String.
headers.setContentType(MediaType.APPLICATION_JSON); //setting your Content type as JSON.
So, First you need to change this to
headers.setContentType(MediaType.TEXT_PLAIN); //setting your Content type as Pure Text String.
and add some code after this line
// HttpEntity<Employee[]>: To get result as Employee[].
HttpEntity<String> entity = new HttpEntity<String>(headers);
add this code
// HttpEntity<Employee[]>: To get result as Employee[].
HttpEntity<String> entity = new HttpEntity<String>(headers);
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// Send request with GET method, and Headers.
String entity_Str = new ObjectMapper().writeValueAsString(entity);
ResponseEntity<String> response =
restTemplate.exchange(URL_EMPLOYEES,
HttpMethod.POST, entity_Str, String.class);
This might work for you.. Thanks :)

RestTemplate gives 400 Bad Request Error on a Get Request

When I try to make a get request with Spring's RestTemplate, it gives 400 BAD Request. I can call the same url from javascript successfully with the headers below :
But the code below does not work. What might be the cause?
public Entity getEntityByUri(String uri) {
String req = "http://live.dbpedia.org/sparql?query=DESCRIBE%20%3Chttp://dbpedia.org/resource/Concept_learning%3E&format=application%2Fjson-ld";
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.ALL));
HttpEntity<String> httpEntity = new HttpEntity<String>(headers);
new RestTemplate().exchange(req, HttpMethod.GET, httpEntity, Map.class);
Entity entity = new Entity();
return entity;
}
Your url is already encoded. Popular browsers such as Chrome are capable of understanding and responding appropriately. However, it's not the same case with RestTemplate.
I had to decode your uri here and the decoded uri is DESCRIBE <http://dbpedia.org/resource/Concept_learning>
Having checked the browser console, I got to know you have two query strings passed in the url, they are query and format holding values DESCRIBE <http://dbpedia.org/resource/Concept_learning> and application/json-ld respectively.
I assume Entity class is the pojo class of json response.
Have created Entity as from your json response:
public class Entity {
private String value;
private String type;
// getters and setters omitted for brevity
}
Finally in your getEntityByUri method have got the instance of UriComponentsBuilder which handles uri encoding and query params.
To sum up, your getEntityByUri looks below.
public HttpEntity<Entity> getEntityByUri() {
String req = "http://live.dbpedia.org/sparql";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(req)
.queryParam("query",
"DESCRIBE <http://dbpedia.org/resource/Concept_learning>")
.queryParam("format", "application/json-ld");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.ALL));
HttpEntity<String> httpEntity = new HttpEntity<String>(headers);
return new RestTemplate().exchange(builder.build().encode().toUri(), HttpMethod.GET, httpEntity, Entity.class);
}
The above method didn't throw HTTP400 as the required query params have been passed in builder object.
Hope this helps and good luck!
Anyone getting same error make sure your URL is decoded means no percent symbols in url (if space in param values).
This worked for me
try {
requestURL = URLDecoder.decode("http://api.com?p=1&groups=3212&affected-since=2019-06-06T14%3A11%3A14.880&detail=full&after-id=43536", "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Maybe
headers.setAccept(Arrays.asList(MediaType.ALL));
generates a malformed "Accept" header field? (FWIW, why do you send it at all???)

How to send GET request with headers by Spring

It will call another REST API with a GET request.
#RequestMapping(value = "xxxx/{id}", method = RequestMethod.GET)
public #ResponseBody GetObjet GET( #PathVariable("id") String id,
#RequestHeader(value="X-Auth-Token") String Token) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("X-Auth-Token", Token);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<GetObjet> response = restTemplate.exchange(url, HttpMethod.GET, entity, GetObjet.class);
return response.getBody();
}
Always 400 Error. It means that bad request or some errors in the request body. But this is GET so the resquest bodys is always empty. So this way to add header may be not right. Any ideas?
You can obtain the headers including the notation #RequestHeader in your method
public void displayHeaderInfo(#RequestHeader("Accept-Encoding") String encoding,
#RequestHeader("Keep-Alive") long keepAlive) {
}
o
You can read more about the request here
And the other way to abtain the URL is:
#RequestMapping(value = "/restURL")
public String serveRest(#RequestBody String body, #RequestHeader HttpHeaders headers){
//Use headers to get the information about all the request headers
long contentLength = headers.getContentLength();
...
StreamSource source = new StreamSource(new StringReader(body));
YourObject obj = (YourObject) jaxb2Mashaller.unmarshal(source);
...
}
Try using:
RestTemplate.getForEntity(url, GetObject.class);
You have some methods to request data from a rest API, such as getForEntity and getForObject, use the one you needed.

Spring Rest Template to send JsonArray

I am using spring rest template to send json array as request. Source code to send request is as follow:
JSONArray jsonArray = new JSONArray();
for (Iterator iterator = itemlist.iterator(); iterator.hasNext();) {
Item item = (Item)iterator.next();
JSONObject formDetailsJson = new JSONObject();
formDetailsJson.put("id", item.getItemConfId());
formDetailsJson.put("name", item.getItems().getItemName());
formDetailsJson.put("price", item.getPrice());
formDetailsJson.put("Cost",item.getCost());
jsonArray.put(formDetailsJson);
}
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
// Prepare header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
// Pass the new person and header
HttpEntity<JSONArray> entity = new HttpEntity<JSONArray>(jsonArray, headers);
System.out.println("Json Object : "+entity);
// Send the request as POST
try {
ResponseEntity<String> result = restTemplate.exchange("my url", HttpMethod.POST, entity, String.class);
} catch (Exception e) {
logger.error(e);
return "Connection not avilable please try again";
}
And to accept request:
#RequestMapping(value = "/testStock", method = RequestMethod.POST,headers="Accept=application/xml, application/json")
public #ResponseBody int testStock(#RequestBody List<ItemList> jsonArray) {
logger.debug("Received request to connect ms access : "+jsonArray.size());
//int returnSizecount = stockList.getStocklst().size();
return 1;
}
The problem is that it giving me following error:
Could not write request: no suitable HttpMessageConverter found for request type [org.json.JSONArray].Any suggestion is greatly acceptable.
There are no MessageConverter for JSONArray, so I suggest do the following.
HttpEntity<JSONArray> entity = new HttpEntity<JSONArray>(jsonArray, headers);
Convert Class JSONArray to String, and add that to HttpEntity, you know use toString
java.lang.String toString()
Make a JSON text of this JSONArray.
HttpEntity entity = new HttpEntity(jsonArray.toString(), headers);
Or change to Jackson implementation Spring have support to that. XD
If you dont want to do the above, consider create your own implementation of messageConverter, that will work but is harder
update
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
headers.setContentType(MediaType.APPLICATION_JSON);
update 2 Change endpoint to.
#RequestMapping(value = "/testStock", method = RequestMethod.POST)
public #ResponseBody int testStock(#RequestBody String jsonArray) {
you need to have httpmessageconverter configured for your resttemplate, please read my post for configuring http message conveter for you webservice
http://stackoverflow.com/questions/19963127/new-to-spring-and-jackson-2-what-does-this-bean-declaration-allow-for-in-a-spri/19973636#19973636.
and for you problem to convert your http request to json you might add this entry in your restemplate configuration
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
The error is quite straightforward. You do not have a converter for the JSONArray. Converting the array to a String (using toString) did help you here, but there is a better way:
Just add a converter for the json.org objects:
Add this to your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
</dependency>
And then on your ObjectMapper add the JsonOrgModule:
mapper.registerModule(new JsonOrgModule());

Resources