I' m trying to send JSON request using Jackson library from my Android app to the web server but response is always null. I tested it just with the HttpRequest API and all works fine - I've got a response. But now I try to use Spring RestTemplate and I can't receive a result. Here is my code:
protected Void doInBackground(String... params) {
LinkedHashMap<String, Object> _map = new LinkedHashMap<String, Object>();
_map.put("login", "Fred");
_map.put("password", "pass");
ObjectMapper _mapper = new ObjectMapper ();
StringWriter _writer = new StringWriter();
try {
_mapper.writeValue(_writer,_map);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String _baseURL = "https...."//Address of the server;
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> _entity = new HttpEntity<String>(_writer.toString(),requestHeaders);
RestTemplate templ = new RestTemplate();
templ.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
templ.getMessageConverters().add(new StringHttpMessageConverter());
ResponseEntity<String> _response = templ.postForEntity(_baseURL, _entity,String.class);
String _Test = _response.getBody();
So I always have null in _Test.
I suspect this is because of https protocol. Can RestTemplate work with https?
So what's wrong with that code. How to fix this?
Thanks in advance. I really need a help!
You have to set the responseType, otherwise the RestTemplate will throw away the body of your response. It needs the responseType to find the correct message converter. With a null responseType, the delegate below will be null...
if (delegate != null) {
T body = delegate.extractData(response);
return new ResponseEntity<T>(body, response.getHeaders(), response.getStatusCode());
}
else {
return new ResponseEntity<T>(response.getHeaders(), response.getStatusCode());
}
With the RestTemplate default constructor, Spring includes just about every converter except for RSS, XML and JSON, which depends on if Rome, JAXB or Jackson is on the classpath. I would set the responseType as String and run it with the debugger to see why it's not finding the correct converter. It's hard for me to say why without seeing the response and headers from the server.
Typo or are you connecting to an https port?
String _baseURL = "https...."//Address of the server;
I think you should monitor the port you are trying to connect to and see if there is a connection even established. One easy way I do that is to make a laptop an ad-hoc network and have an Android device connect to it and then, you should be able to monitor all traffic from your android device with a packet sniffer like wireshark.
Related
I have created a custom extension (Connector), which sends an HttpRequest (using org.mule.runtime.http.api.client.HttpClient and the related classes).
The extension's unit tests file contains the following test, to which I've added a simple Mockito mock to throw a TimeoutException when the HTTP request is being sent:
public class DemoOperationsTestCase extends MuleArtifactFunctionalTestCase {
/**
* Specifies the mule config xml with the flows that are going to be executed in the tests, this file lives in the test resources.
*/
#Override
protected String getConfigFile() {
return "test-mule-config.xml";
}
#Test
public void executeSayHiOperation() throws Exception {
HttpClient httpClient = mock(HttpClient.class);
HttpRequest httpRequest = mock(HttpRequest.class);
when(httpClient.send(any(HttpRequest.class), anyInt(), anyBoolean(), any(HttpAuthentication.class))).thenThrow(new TimeoutException());
String payloadValue = ((String) flowRunner("sayHiFlow").run()
.getMessage()
.getPayload()
.getValue());
assertThat(payloadValue, is("Hello Mariano Gonzalez!!!"));
}
}
The test should fail because the function should throw a TimeoutException, it is what I want for now.
The code that is being tested is as follows (redacted for convenience):
HttpClient client = connection.getHttpClient();
HttpResponse httpResponse = null;
String response = "N/A";
HttpRequestBuilder builder = HttpRequest.builder();
try {
httpResponse = client
.send(builder
.addHeader("Authorization", authorization)
.method("POST")
.entity(new ByteArrayHttpEntity("Hello from Mule Connector!".getBytes()))
.uri(destinationUrl)
.build(),
0, false, null);
response = IOUtils.toString(httpResponse.getEntity().getContent());
} catch (IOException e) {
e.printStackTrace();
throw new ModuleException(DemoError.NO_RESPONSE, new Exception("Failed to get response"));
} catch (TimeoutException e) {
e.printStackTrace();
throw new ModuleException(DemoError.NO_RESPONSE, new Exception("Connection timed out"));
}
But I always get the "Failed to get response" error message, which is what I get when I run the Connector with a nonexistent server, therefore the mock isn't working (it actually tries to send an HTTP request).
I am new to Java unit testing, so it might be a general mocking issue and not specific to MuleSoft - though I came across other questions (such as this one and this one), I tried the suggestions in the answers and the comments, but I get the same error. I even tried to use thenReturn instead of thenThrow, and I get the same error - so the mock isn't working.
Any idea why this is happening?
I am using a method where it calls another REST API to retrieve an ID from the DB. When I run the veracode scan for the class I am getting Security flaw "Server-side Request Forgery" at below line.
response = resttemplate.getForEntity(resturl, String.class);
Not sure How to fix this issue. Any help is appreciated. Below is my full code for that method.
public static String getIDFromDB(String resturl) {
String id = null;
RestTemplate resttemplate = new RestTemplate();
ResponseEntity<String> response = new ResponseEntity<>(HTTPStatus.OK)
try {
response = resttemplate.getForEntity(resturl, String.class);
if (response.getStatusCode == HTTPStatus.OK && response.getBody.trim() != null) {
id = response.getBody.trim() ;
}
} Catch(Exception e) {
log.error("failed to get msgID: {}", e);
}
}
This is because you are allowing in your code to pass the resturl completely in your code, so it enables the attacker to bypass and route the URL to their intended destination.
To avoid this, so should externalise and refer the URL having domain and the application contexts with operation name in config files or dB
I am trying to send a json file over REST Template. When I send it via POST man as MULTIPART_FORM_DATA, it works fine. The name I am supposed to give is specific (lets say aaa). Attached screenshot of POSTMAN. But when I try same in code as specified in another stackoverflow post, I get 415 Unsupported Media Type error as
org.springframework.web.client.HttpClientErrorException: 415 Unsupported Media Type
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:616) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:572) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:532) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:332) ~[spring-web-4.1.9.RELEASE.jar:4.1.9.RELEASE]
at
Please do not mark it as duplicate as the specified answer did not work for me. Not sharing code as my code is exactly same as this except
requestParamerterMap.add("attachment", resource);
where as my code is
requestParamerterMap.add("aaa", resource);
After debugging it from the server side, looks like request is reaching out to server. I was able to see below error in the server side:
[{error=Unsupported Media Type, exception=org.springframework.web.HttpMediaTypeNotSupportedException, message=Content type 'application/octet-stream' not supported, status=415, timestamp=1532557180124}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#74d4827a]
So, from the server side logs, I am not sure where the content type is getting added as application/octet-stream as I have set the content type as
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
Below is the code from server controller. Server side code uses Spring boot.
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,consumes = {"multipart/form-data"})
#ResponseBody
public MyResponse uploadPhoto(#RequestPart(value = "aaa", required = false) Optional<MyRequest> myRequest,
#RequestPart(value = "file", required = false) Optional<MultipartFile> file,
HttpServletRequest request) {
//some logic
return myResponse;
}
The server code has an interceptor where I can see my request has content type as multipart/form-data. It does not reach to RestController
When I debugged the server side code in 2 cases:
POSTMAN request
client code request
One thing I figured out that file iteam has content type as application/json when I post from POSTMAN and the content type was application/octet-stream when the request goes from client side code.
In my client side code, I am creating JSONObject as
JSONObject json = new JSONObject();
json.append("myKey", "myValue");
and convert it to byte array as
json.toString().getBytes("UTF-8")
then I have followed this . The difference in my code is, I am sending my JSONObject as byte stream as I can not create file (performance issues).
And I cant not send JSONObject as string as server is expecting multipart-form-data for both file and aaa
I have created the restTemplate as
public RestTemplate myRestTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(HTTP_CLIENT_TIMEOUT);
requestFactory.setConnectTimeout(HTTP_CLIENT_TIMEOUT);
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
Here is the client side code which calls the service:
public Optional<JSONObject> callService(byte[] multipartFile) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
InputStream stream = new ByteArrayInputStream(multipartFile);
MultipartByteArrayResource resource = new MultipartByteArrayResource(multipartFile,fileName);
body.add("aaa", resource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
try {
response = restTemplate.postForObject(url, requestEntity , String.class);
} catch (Exception exception) {
LOG.error("Error", exception);
return Optional.empty();
}
}
public class MultipartInputStreamFileResource extends InputStreamResource {
private final String filename;
MultipartInputStreamFileResource(InputStream inputStream, String filename) {
super(inputStream);
this.filename = filename;
}
#Override
public String getFilename() {
return this.filename;
}
#Override
public long contentLength() throws IOException {
return -1; // we do not want to generally read the whole stream into memory ...
}
}
And same code works when I send file (note file and aaa are two different things though both are multipart/form-data in server side. file is just a file of any time (image/text/pdf) but aaa is json data file)
After debugging little bit more, what I observed is server side controller is expecting the file content to be json as Jackson try to deserialize that json to MyRequest object. When I send post from POSTMAN, it has the json content so working as expected but from the client side code, the content is byteArray, and its not getting deserialize to MyRequest object. Not sure how to fix this
Finally I solved this issue. As mentioned in question, having different content type of multipart file while sending request from POSTMAN vs code is where I began with. I will explain in details if anyone has any questions.
public Optional<JSONObject> save(byte[] multipartFile, String fileName) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
Resource content = new MultipartByteArrayResource(multipartFile , fileName);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Resource> requestEntityBody = new HttpEntity<Resource>(content, headers);
body.add("aaa", requestEntityBody);
String result = "";
JSONParser parser = new JSONParser();
JSONObject json = null;
HttpHeaders requestHeaders = new HttpHeaders();
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, requestHeaders);
ResponseEntity<String> response = null;
try {
RestTemplate restTemplate = customizeRestTemplate(); //I have defined this in different config file in my actual code
response = restTemplate.exchange(url , HttpMethod.POST , requestEntity , String.class);
result = (response != null && response.getBody() != null) ? response.getBody().toString() : result;
json = (JSONObject) parser.parse(result);
LOG.info( "Response:", response );
} catch (Exception exception) {
LOG.error("Error , exception);
return Optional.empty();
}
return Optional.ofNullable(json);
}
public class MultipartByteArrayResource extends ByteArrayResource{
private String fileName;
public MultipartByteArrayResource(byte[] byteArray , String filename) {
super(byteArray);
this.fileName = filename;
}
public String getFilename() {
return fileName;
}
public void setFilename(String fileName) {
this.fileName= fileName;
}
}
public RestTemplate customizeRestTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(10000);
requestFactory.setConnectTimeout(10000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
The server-side exception is produced by org.springframework.http.converter.json.MappingJackson2HttpMessageConverter. Jackson is a JSON library and MessageConverter are used by Spring to format requests and responses.
Can it be that the client sends an "Accept: application/octet-stream" while the server has a #Produces(APPLICATION_JSON) annotation? That would mean that the server processes the request and only has problems sending the response. You could add some log.info() statements in the server to verify this.
I am trying to do DELETE with request body but I keep getting 400 (bad request) error. When I do it in the swagger/postman, it is successfully deleting the record. But from the Java code I can't do that
The external API is designed in a way that it needs body along with URL. It can't be changed. please let me know how can I delete that entry with request body
public Person delete(Person person, String url, Map<String, String> uriVariables) throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
CustomObjectMapper mapper = new CustomObjectMapper();
HttpEntity<Person> requestEntity = new HttpEntity<Person>(person);
try {
ResponseEntity<Person> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, requestEntity, Person.class, uriVariables);
return responseEntity.getBody();
} catch (RestClientException e) {
System.out.println(mapper.writeValueAsString(person));
throw e;
}
}
when it goes to exception, I will get the JSON request in JSON format and the same works fine in Swagger/postman
I did some googling and found that restTemplate have problem deleting when there is request body. this article wasn't helpful https://jira.spring.io/browse/SPR-12361 is there any way to get it work
Another way to fix this is to use restTemplate.exchange, here's an example:
try {
String jsonPayload = GSON.toJson(request);
HttpEntity<String> entity = new HttpEntity<String>(jsonPayload.toString(),headers());
ResponseEntity resp = restTemplate.exchange(url, HttpMethod.DELETE, entity, String.class);
} catch (HttpClientErrorException e) {
/* Handle error */
}
The nice thing about this solution is that you can use it with all HttpMethod types.
Issue exists for Spring version 4.0.x or earlier.
In later version it has been fixed.
This might be a late answer, but in one of my project I solved this issue via a custom ClientHttpRequestFactory to RestTemplate
If no factory is provided to RestTemplate, it uses default implementation SimpleClientHttpRequestFactory
In SimpleClientHttpRequestFactory class, DELETE method is not allowed with request body.
if ("PUT".equals(httpMethod) || "POST".equals(httpMethod) ||
"PATCH".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
Just write your own implementation as
import java.io.IOException;
import java.net.HttpURLConnection;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
public class CustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection,
String httpMethod) throws IOException {
super.prepareConnection(connection, httpMethod);
if("DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
}
}
After that create your RestTemplate object as
RestTemplate template = new RestTemplate(
new CustomClientHttpRequestFactory());
Fix in later versions of (4.1.x or above) SimpleClientHttpRequestFactory class
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
I am making a HTTPPost call using Apache HTTP Client and then I am trying to create an object from the response using Jackson.
Here is my code:
private static final Logger log = Logger.getLogger(ReportingAPICall.class);
ObjectMapper mapper = new ObjectMapper();
public void makePublisherApiCall(String jsonRequest)
{
String url = ReaderUtility.readPropertyFile().getProperty("hosturl");
DefaultHttpClient client = new DefaultHttpClient();
try {
HttpPost postRequest = new HttpPost(url);
StringEntity entity = new StringEntity(jsonRequest);
postRequest.addHeader("content-type", "application/json");
log.info("pub id :"+ExcelReader.publisherId);
postRequest.addHeader("accountId", ExcelReader.publisherId);
postRequest.setEntity(entity);
HttpResponse postResponse = client.execute(postRequest);
log.info(EntityUtils.toString(postResponse.getEntity()));
// Response<PublisherReportResponse> response = mapper.readValue(postResponse.getEntity().getContent(), Response.class);
// log.info("Reponse "+response.toString());
} catch (UnsupportedEncodingException ex) {
log.error(ex.getMessage());
log.error(ex);
Assert.assertTrue(false, "Exception : UnsupportedEncodingException");
} catch (ClientProtocolException ex) {
log.error(ex.getMessage());
log.error(ex);
Assert.assertTrue(false, "Exception : ClientProtocolException");
} catch (IOException ex) {
log.error(ex.getMessage());
log.error(ex);
Assert.assertTrue(false, "Exception : IOException");
}
Method makePublisherApiCall() will be called in a loop which runs for say 100 times.
Basically problem occurs when I uncomment the line:
// Response<PublisherReportResponse> response = mapper.readValue(postResponse.getEntity().getContent(), Response.class);
// log.info("Reponse "+response.toString());
After uncommenting I am getting exception:
Attempted read from closed stream.
17:26:59,384 ERROR com.inmobi.reporting.automation.reportingmanager.ReportingAPICall - java.io.IOException: Attempted read from closed stream.
Otherwise it works fine.
Could someone please let me know what I am doing wrong.
What does EntityUtils.toString(postResponse.getEntity()) do with the response entity? I would suspect, that it is consuming the entity's content stream. The HttpClient javadoc states, that only entities, which are repeatable can be consumed more than once. Therefore if the entity is not repeatable you cannot feed the content stream to the mapper again. To avoid this you should only let the mapper consume the stream - if logging of content is required, log the parsed Response object.
I had the same problem. Make sure you aren't consuming the entity's content stream in the "watch" or "inspect" section of your IDE. It's closed after it's consumed (read).
And sorry for my english.
I found an answer for similar issue with Spring RestTemplate here : https://www.baeldung.com/spring-rest-template-interceptor
if we want our interceptor to function as a request/response logger, then we need to read it twice – the first time by the interceptor and the second time by the client.
The default implementation allows us to read the response stream only once. To cater such specific scenarios, Spring provides a special class called BufferingClientHttpRequestFactory. As the name suggests, this class will buffer the request/response in JVM memory for multiple usage.
Here's how the RestTemplate object is initialized using BufferingClientHttpRequestFactory to enable the request/response stream caching:
RestTemplate restTemplate = new RestTemplate( new BufferingClientHttpRequestFactory( new SimpleClientHttpRequestFactory() ) );
I had the same problem.
The idea is that if you consume the postResponse, then you should put it in a variable in order to use it again in different places.
Else, the connection is closed and you can no longer consume the same response again.
I used to log it (for debug purposes) and always fails.
In my case this issue was related to another reason,
I am getting this issue because I haven't use
closeableresponce=client.Getmethod(FinalUrl);
In my first Test1, it was mention but when I missed out in Test2, I forget to put this code that's why stream closed message shows...
public void getapiwithheader() throws ParseException, IOException
{
client = new RestClient();
closeableresponce=client.Getmethod(FinalUrl);
HashMap<String, String> headermap = new HashMap<>();
headermap.put("content-type", "application/json");
//****************************************************************************
//Status code
//****************************************************************************
int statuscode =closeableresponce.getStatusLine().getStatusCode();
System.out.println("Status code "+statuscode);
Assert.assertEquals(statuscode,RESPONSE_STATUS_CODE_200, "Status code is not 200");
//****************************************************************************
// Json String
//****************************************************************************
String responsestring= EntityUtils.toString(closeableresponce.getEntity(),"UTF-8");
JSONObject respjsonobj = new JSONObject(responsestring);
System.out.println("Respose Json API"+respjsonobj);
//****************************************************************************
// Verify the value of the JSON
//****************************************************************************
String Email =TestUtil.getValueByJPath(respjsonobj,"/email");
System.out.println("******************************************");
System.out.println("Print the value of Email "+Email);
Assert.assertEquals(Email, "johndoe#google.com");
}
I had the same problem.
In my case, I need to get the response content via AOP/