I'm sending API and receiving status code 400 with body I need to parse
When working with RestTemplate I failed to parse response:
try {
ResponseEntity<ResponseVO> response = restTemplate.exchange(uri, HttpMethod.POST, request, ResponseVO.class);
} catch(HttpStatusCodeException e){
// not catch
String errorpayload = e.getResponseBodyAsString();
} catch (RestClientException e) {
// catch without body
}
Also With adding error handlers (default and specific) suggested I always get a ResourceAccessException which isn't catch by HttpClientErrorException and doesn't include body/headers data
How can I still get body/headers in that case? must I use alternatives to RestTemplate ?
Moreover, how can I return to context of request when I'm in caught exception inside handleError:
public void handleError(ClientHttpResponse response) throws IOException {
The issue I was using interceptor which read my body already,
Changed RestTemplate to using BufferingClientHttpRequestFactory I can now re-read my body
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory()));
Using this wrapper allows for multiple reads of the response body.
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 Use the Supplier in my code to call restTemplate and make the custom Message when have exception..
But, im my message, i need get information by my requestCall, But when i cast the request the java thow error
...
My code:
public void execute() {
HttpHeaders headers = buildDefaultHeaders();
UriBuilder uri = UriBuilder.fromUri(wdd3dGatewayEndpoint + API_URL);
HttpEntity request = new HttpEntity(headers);
this.executeRequest(() -> restTemplate.exchange(uri.build(), HttpMethod.DELETE, request, Void.class));
}
My Supplier
protected ResponseEntity executeRequest(Supplier<ResponseEntity> request) {
try {
ResponseEntity response = request.get();
updateSessionToken(response);
return response;
} catch (HttpClientErrorException | HttpServerErrorException e) {
String msg = "WDD3D-Error in service communication<br>" + e.getResponseBodyAsString();
throw new MaestroException(msg);
}
}
Now, i try cast to get URL...
protected ResponseEntity executeRequest(Supplier<ResponseEntity> request) {
try {
ResponseEntity response = request.get();
updateSessionToken(response);
return response;
} catch (HttpClientErrorException | HttpServerErrorException e) {
//THROW EXEPTION HERE... PLEASE HELP...
RequestEntity requestEntity = (RequestEntity) request;
String url = requestEntity.getUrl().toString();
String msg = "WDD3D-Error in service communication<br>" + e.getResponseBodyAsString();
throw new MaestroException(msg);
}
}]
You should use the get() method of the Supplier, see more in the docs.
RequestEntity requestEntity = (RequestEntity) request;
You are trying to cast a Supplier<ResponseEntity> to a RequestEntity.
These are two very different classes and such a cast will never work.
Maybe you want to call request.get() and get the URL from the ResponseEntity that you have.
Tell me if it works for you in the comments or we need to debug further ?
The only thing you are trying to get from the RequestEntity is the URL, which you can't get from the Supplier<ResponseEntity> since it is not a RequestEntity, so why not just pass the URL as another parameter to executeRequest? Then it would have the additional information it needs to log the error.
I am hitting one API using RestTemplate exchange method, Here I am getting responseEntity of ClientResponse Type. If we have any Bad request in first line of code, I'll get 400 and cursor will go to the catch and throwing Error. So remaining code(For setting a response Data) is not executing .Instead of this I want to set the response Data and I want to set status code also and want to execute remain code. How we can do it, Do we need to use Flag variable ??
ResponseEntity<ClientResponse> responseEntity = this.getRestTemplate().exchange(API_URL,
HttpMethod.POST, entity, ClientResponse.class);
response.setResponseEntity(responseEntity);
response.setValue(inputRequest.getValue));
response.setEndTime(LocalDateTime.now());
response.setRequestPayload(gson.toJson(inputRequest));
response.setHttpMethod(HttpMethod.POST);
response.setRequestHeaders(entity.getHeaders().toSingleValueMap());
} catch (Exception e) {
LOG.error("error occurred in service" + e.getMessage());
}
return response;
You can use restTemplate error handler for that. You can parse the error response returned from the rest template like 404 or some other error.
You can use those status code and error response to rest the response as per your need.
For that you need to define a class by implementing ResponseErrorHander.
public class ServiceResponseErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return (response.getStatusCode().is4xxClientError() ||
response.getStatusCode().is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
#SuppressWarnings({ "unchecked", "rawtypes" })
HttpMessageConverterExtractor<ServiceErrorResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ServiceErrorResponse.class, messageConverters);
ServiceErrorResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(
ResponseEntity.status(response.getRawStatusCode())
.headers(response.getHeaders())
.body(errorObject)
);
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
And use it in your rest template bean like this.
RestTemplateResponseErrorHandler errorHandler = new
RestTemplateResponseErrorHandler();
//pass the messageConverters to errror handler and let it convert json to object
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
I have more then 1 http call in try-catch block, exmaple:
try {
HttpHeaders httpHeaders = new HttpHeaders();
ResponseEntity<String> sendGet = http.sendGet(someUrl1, httpHeaders);
ResponseEntity<String> sendPost = http.sendPost(someUrl2, httpHeaders);
}catch(HttpClientErrorException e) {
//print call url here someUrl1/someUrl2
printException(e);
}
catch (Exception e) {
printException(e);
//general e
}
//Print exception
public void printException(Exception e){
//log URL here
}
I want to print the failed URL when I catch the exception but I did not find HttpClientErrorException or RestClientException property that I can use.
EDIT for general case:
I don't think you should use HttpClientErrorException for getting url, you know the URL before calling each request, so my suggestion:
Create a generic send method which receive post method, URL and headers parameters
Call your method for each request and it will also handle exceptions based on current URL
For simple case describe in question:
You can split to 2 try and catch blocks for each request
Or add a boolean ( or currentUrl String variable)
boolean isFirstRequestFinished = false;
Set it to true after first request and check in catch block
if (isFirstRequestFinished) {
We're using org.springframework.web.reactive.function.client.WebClient with
reactor.netty.http.client.HttpClient as part of Spring 5.1.9 to make requests using the exchange() method. The documentation for this method highlights the following:
... when using exchange(), it is the responsibility of the application
to consume any response content regardless of the scenario (success,
error, unexpected data, etc). Not doing so can cause a memory leak.
Our use of exchange() is rather basic, but the documentation for error scenarios is unclear to me and I want to be certain that we are correctly releasing resources for all outcomes. In essence, we have a blocking implementation which makes a request and returns the ResponseEntity regardless of the response code:
try {
...
ClientResponse resp = client.method(method).uri(uri).syncBody(body).exchange().block();
ResponseEntity<String> entity = resp.toEntity(String.class).block();
return entity;
} catch (Exception e) {
// log error details, return internal server error
}
If I understand the implementation, exchange() will always give us a response if the request was successfully dispatched, regardless of response code (e.g. 4xx, 5xx). In that scenario, we just need to invoke toEntity() to consume the response. My concern is for error scenarios (e.g. no response, low-level connection errors, etc). Will the above exception handling catch all other scenarios and will any of them have a response that needs to be consumed?
Note: ClientResponse.releaseBody() was only introduced in 5.2
The response have to be consumed when the request was made, but if you can't do the request probably an exception was be throwed before, and you will no have problems with response.
In the documentation says:
NOTE: When using a ClientResponse through the WebClient exchange() method, you have to make sure that the body is consumed or released by using one of the following methods:
body(BodyExtractor)
bodyToMono(Class) or bodyToMono(ParameterizedTypeReference)
bodyToFlux(Class) or bodyToFlux(ParameterizedTypeReference)
toEntity(Class) or toEntity(ParameterizedTypeReference)
toEntityList(Class) or toEntityList(ParameterizedTypeReference)
toBodilessEntity()
releaseBody()
You can also use bodyToMono(Void.class) if no response content is expected. However keep in mind the connection will be closed, instead of being placed back in the pool, if any content does arrive. This is in contrast to releaseBody() which does consume the full body and releases any content received.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html
You can try to use .retrieve() instead .exchange() and handle errors as your preference.
public Mono<String> someMethod() {
return webClient.method(method)
.uri(uri)
.retrieve()
.onStatus(
(HttpStatus::isError), // or the code that you want
(it -> handleError(it.statusCode().getReasonPhrase())) //handling error request
)
.bodyToMono(String.class);
}
private Mono<? extends Throwable> handleError(String message) {
log.error(message);
return Mono.error(Exception::new);
}
In this example I used Exception but you can create some exception more specific and then use some exception handler to return the http status that you want.
Is not recommended to use block, a better way is pass the stream forward.
create some exception classes
Autowired ObjectMapper
Create a method that returns Throwable
Create a custom class for Error.
return webClient
.get()
.uri(endpoint)
.retrieve()
.bodyToMono(Model.class)
.onErrorMap(WebClientException.class, this::handleHttpClientException);
private Throwable handleHttpClientException(Throwable ex) {
if (!(ex instanceof WebClientResponseException)) {
LOG.warn("Got an unexpected error: {}, will rethrow it", ex.toString());
return ex;
}
WebClientResponseException wcre = (WebClientResponseException)ex;
switch (wcre.getStatusCode()) {
case NOT_FOUND -> throw new NotFoundException(getErrorMessage(wcre));
case BAD_REQUEST -> throw new BadRequestException(getErrorMessage(wcre));
default -> {
LOG.warn("Got a unexpected HTTP error: {}, will rethrow it", wcre.getStatusCode());
LOG.warn("Error body: {}", wcre.getResponseBodyAsString());
return ex;
}
}
}
private String getErrorMessage(WebClientResponseException ex) {
try {
return mapper.readValue(ex.getResponseBodyAsString(), HttpErrorInfo.class).getMessage();
} catch (IOException ioe) {
return ex.getMessage();
}
}