RestEasy client throwing exceptions - java

I have a REST service where in case of bad authorisation, I return 401 and some error message.
Example if I use postman or other rest client, the response status is 401 and payload:
{
"data": null,
"errors": [
{
"code": "REQUEST_NOT_AUTHORIZED",
"message": "Request not authorized"
}
]
}
If I use RestEasy client, then this exception is thrown automatically by the client:
EJB Invocation failed on component GatewayApi for method public com.example.AuthToken com.example.GatewayApi.authenticate(....): javax.ejb.EJBException: javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized
Caused by: javax.ws.rs.NotAuthorizedException: HTTP 401 Unauthorized
If I try/catch the exception, then my payload is gone.
The way I am implementing is (for example):
ResteasyClient client = new ResteasyClientBuilder().build();
ResteasyWebTarget target = client.target(UriBuilder.fromPath(SERVICE_URL));
proxy = target.proxy(GatewayApiInterface.class);
Later edit - auth method
public AuthToken authenticate(String id, String name, String password) {
try {
ResponseEnvelope<AuthToken> authTokenResponseEnvelope = proxy.authenticate(id, name, password);
return authTokenResponseEnvelope.getData();
} catch (javax.ws.rs.NotAuthorizedException wae) {
return null;
}
}
Is any way to stop RestEasy throwing exception every time status != 200?
Or some way to obtain my original payload from the Rest Server?

Fixed it :). This is a small test example that I used. Before I was not using Response (my bad)
Server Side
#GET
#Path("/test")
public Response test() {
return Response.status(Response.Status.UNAUTHORIZED)
.entity("TEST")
.build();
}
Client Side Proxy Class
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
Response test();
Client
Response response = proxy.test();
String test = response.readEntity(String.class);
System.out.println(test);
System.out.println(response.getStatus());
response.close();

Related

Spring Boot: returned HTTP response code 400, nested exception is java.io.IOException

Consider the below code.Here, when the status code returned by the response is 200, then code execution works perfectly.
But if I try to return the status code 400, then Exception is caught at the specified line.
*Error Response: Internal Server Error in : getResponse I/O error on POST request for "http://localhost:8090/get-data": Server returned HTTP response code: 400 for URL: http://localhost:8090/get-data; nested exception is java.io.IOException: Server returned HTTP response code: 400 for URL: http://localhost:8090/get-data
connection: close, content-length: 485, content-type: application/json, date: Thu, 08 Apr 2021 20:25:47 GMT*
Why is an exception thrown when status 400 is returned?
#RestController
Class ABC{
#Autowired
RestTemplate template;
#PostMapping("....")
ResponseEntity<Object> getResponse(){
ResponseEntity<String> response
try{
HttpEntity<Object> body=new HttpEntity(obj,header); // data obj & httpheader
response=template.postForEntity("http://localhost:8090/get-
data",body,String.class);
}catch(HttpStatusCodeException ex){
throw....
}catch(Exception ex){
throw Internal Server Error..... //Exception is thrown here
}
if(response.getStatusCodeValue()==200){
...
}
...
}
}
#RestController
Class XYZ{
#PostMapping("/get-data")
ResponseEntity<Object> getTheStatus(#RequestHeader HttpHeaders headers ,#ResponseBody MyData data){
return new ResponseEntity<>("",HttpStatus.BAD_REQUEST);
}
}
In HTTP context response status 200 represent success response. Statuses 4xx and 5xx represent some problems. The problem can be related to a request, connection, server, or another source. Some HTTP client implementations like RestTemplate will throw exceptions in case of non-success status code because non-success response status represents failed execution.
If you want to avoid exceptions you can add an error handler.
public class RestTemplateResponseErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return false;
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {}
}
RestTemplate restTemplate = restTemplateBuilder
.errorHandler(new RestTemplateResponseErrorHandler())
.build();
template.postForEntity("http://localhost:8090/get-data", body, String.class);
it's happen because of passing HttpStatus.BAD_REQUEST
IMHO returning 4xx statuses like this isn't the best way, use #ControllerAdvice instead

OkHttp POST error "connection reset by peer" for unauthorized call and large payload

I've been struggling with the following issue:
I have a spring boot application which allows a user to post JSON content to an API endpoint. To use this endpoint, the user has to authenticate himself via basic authentication. Moreover, I use OkHttp (3.6.0) as an HTTP client.
Now if I post a large payload (> 4 MB) while being unauthorized, the OkHttp client fails with the following error:
java.net.SocketException: Connection reset by peer: socket write error
To reproduce the issue, I created a minimal client and server:
Server (Spring Boot Web App)
#SpringBootApplication
#RestController
public class App {
#PostMapping
public String create(#RequestBody Object obj) {
System.out.println(obj);
return "success";
}
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
Client (OkHttp 3.6.0)
public class Main {
public static void main(String[] args) {
OkHttpClient client = new OkHttpClient
.Builder()
.build();
Request request = new Request.Builder()
.url("http://localhost:8080")
.header("Content-Type", "application/json")
.post(RequestBody.create(MediaType.parse("application/json"), new File("src/main/java/content.json")))
// .post(RequestBody.create(MediaType.parse("application/json"), new File("src/main/java/content-small.json")))
.build();
try {
Response response = client.newCall(request).execute();
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Instead of the previously mentioned exception ("java.net.SocketException: Connection reset by peer: socket write error"), I would expect the response to be a default error message with HTTP status code 401, e.g. {"timestamp":1508767498068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/"}. This is the result I get when using cURL and Postman as clients.
When I'm using less payload (content-small.json; approx. 1KB) instead of the large payload (content.json; approx. 4881KB), I receive the expected response, i.e. Response{protocol=http/1.1, code=401, message=, url=http://localhost:8080/}.
The issue is actually embedded in a larger project with Eureka and Feign clients. Threfore, I would like to continue using OkHttp client and I need the expected behavior.
My problem analysis
Of course, I tried to solve this problem myself for quite some time now. The IOException occurs when the request body is written to the HTTP stream:
if (permitsRequestBody(request) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
My assumption is that the server closes the connection as soon as it receives the headers (as the request is unauthorized), but the client continues trying to write to the stream although it is already closed.
Update
I've also implemented a simple client with Unirest which shows the same behavior. Implementation:
public class UnirestMain {
public static void main(String[] args)
throws IOException, UnirestException {
HttpResponse response = Unirest
.post("http://localhost:8080")
.header("Content-Type", "aplication/json")
.body(Files.readAllBytes(new File("src/main/java/content.json").toPath()))
// .body(Files.readAllBytes(new File("src/main/java/content-small.json").toPath()))
.asJson();
System.out.println(response.getStatus());
System.out.println(response.getStatusText());
System.out.println(response.getBody());
}
}
Expected output: {"path":"/","error":"Unauthorized","message":"Full authentication is required to access this resource","timestamp":1508769862951,"status":401}
Actual output: java.net.SocketException: Connection reset by peer: socket write error

Spring endpoint to endpoint using RestTemplate Exception handling

We have a microservice architecture. Each service exposing data through Rest. All controllers are set up using Spring:
#RestController
#RequestMapping(path = "foobar")
public class UiController {
#PostMapping("foo")
public ResponseEntity<Foo> addFoo(#RequestBody final FooDto fooDto) {
Foo fromDb = adminService.addFoo(converterToModel.convert(fooDto);
return ResponseEntity.ok(converterToDto.convert(fromDb));
}
If for some reason fooDto can't be added to the database. A custom Exception is thrown:
#ResponseStatus(value = HttpStatus.CONFLICT)
public class FooAlreadyAssignedException extends RuntimeException {
public FooAlreadyAssignedException(String msg) {
super("The following foos are already assigned to foobar: " + msg);
}
}
In Postman you see the following JSON after the Exception above is thrown
{
"timestamp": 1508247298817,
"status": 409,
"error": "Conflict",
"exception": "com.foo.exception.FooCodeAlreadyExistsException",
"message": "A foo with code: foo already exists",
"path": "/foo/foobar"
}
We have 4 different services like these all set up the same way.
Our UI is made in Angular 4 and makes REST calls to our Gateway. The Gateway is the connection between the microservices and the UI. It also exposes a REST endpoint. It's also implemented with Spring. I added a picture for clarification:
architecture
"edit: I see that I didn't complete the arrows. Of course all data is passed back up to the UI"
The problem
The Gateway uses a RestTemplate to call the APIs of the microservices
when a custom Exception is thrown in the microservice the Gateway returns this:
{
"timestamp": "2017-10-16T15:30:03.456Z",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.web.client.HttpClientErrorException",
"message": "409 null",
"path": "/v1/printstations"
}
My original response a HttpStatus.conflict (status = 409) seems to be wrapped in a status 500 message by the Gateway. I don't want this behavior, I want it to pass the original message to the UI.
Any ideas on how to control this behavior?
Notes
I have tested with Postman that if you access the microservice directly it returns the 409 with the message written in the custom Exception
I have already tried overriding Springs ResponseErrorHandler but was not able to find a suitable solution that way.
In gateway code where spring rest template is calling your microservices, I would recommend catching HttpClientErrorException and then create your own exception class like ApiException as in below example, this way you will be able to pass the exact exception which is thrown from the microservices:
catch (org.springframework.web.client.HttpClientErrorException e) {
throw new ApiException(e.getMessage(), e, e.getRawStatusCode(), e.getResponseHeaders(),
e.getResponseBodyAsString(), fullURIPath, null);
}
where ApiException has a constructor like below:
public ApiException(String message, Throwable throwable, int code, Map<String, List<String>> responseHeaders,
String responseBody, String requestURI, String requestBody) {
super(message, throwable);
this.code = code;
this.responseHeaders = responseHeaders;
this.responseBody = responseBody;
this.requestURI = requestURI;
this.requestBody = requestBody;
}
Issue can be closed.
The solution was to map the exception that happened in the microservice to a valid ResponseEntity in the Gateway, so that the RestTemplate in the Gateway wouldn't repackage the error in a 500 server error.
We did this by creating a #ControllerAdvice class
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {HttpClientErrorException.class})
protected ResponseEntity<Object> handleConflict(HttpClientErrorException ex, WebRequest request) {
return handleExceptionInternal(ex, ex.getResponseBodyAsString(),
new HttpHeaders(), ex.getStatusCode(), request);
}
}
This results in a ResponseEntity with the correct HttpStatus from the Exception in the microservice and a JSON body containing the message needed by the front-end.

Jersey client request to web-service

I`m trying to request to web-service by jersey client:
WebResource service = client.resource(UriBuilder.fromUri("http://localhost:8080/jersey-example-new/").build());
System.out.println(service.path("rs/").path("account/details/1").accept(MediaType.APPLICATION_JSON).get(String.class));
but I get:
GET http://localhost:8080/jersey-example-new/rs/account/details/1 returned a response status of 406 Not Acceptable
Please, note that url path http://localhost:8080/jersey-example-new/rs/account/details/1 works in browser. What is wrong with java client request?
the endpoint code:
#Path("account")
public class AccountDetailsService {
#GET
#Path("/details/{param}")
#Produces(MediaType.TEXT_PLAIN)
public Response getAccountDetails(#PathParam("param") String accountName) {
String output = "Account Name : " + accountName;
return Response.status(200).entity(output).build();
}
}
You should change
System.out.println(service.path("rs/").path("account/details/1").accept(MediaType.APPLICATION_JSON).get(String.class));
to
System.out.println(service.path("rs/").path("account/details/1").accept(MediaType.TEXT_PLAIN).get(String.class));
You are only producing TEXT_PLAIN, but you request the media-type APPLICATION_JSON (via accept header), this is why you get the response, that the request is not acceptable.

Jersey Custom Exception Message

I've written a Jersey Server application and a Client application which is consuming the provided REST Services.
But I've problems to pass exception messages from server to client.
Currently I've implemented it like:
Server WS Method:
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public TestModel doTest(){
throw new NotImplementedException("[doTest] is not implemented yet");
}
NotImplementedException:
public class NotImplementedException extends WebApplicationException{
public NotImplementedException(){
super(Response.status(Response.Status.NOT_IMPLEMENTED)
.entity("The operation you've called is not implemented yet").type(MediaType.APPLICATION_JSON).build());
}
public NotImplementedException(String message){
super(Response.status(Response.Status.BAD_GATEWAY).entity(message).type(MediaType.APPLICATION_JSON).build());
}
}
Client:
public static TestModel doTest() throws Exception{
try {
Client client = getClient();
WebTarget webTarget = client.target("server..../");
WebTarget getGuTarget = webTarget.path("test");
Invocation.Builder ib = getGuTarget.request(MediaType.APPLICATION_JSON);
TestModel response = ib.get(TestModel.class); //here exception is thorwn
return response;
} catch (Exception e) {
throw e;
}
}
The exception caught on the Client looks like:
javax.ws.rs.ServerErrorException: HTTP 502 Bad Gateway
at org.glassfish.jersey.client.JerseyInvocation.createExceptionForFamily(JerseyInvocation.java:1029)
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:1009)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:799)
at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:91)
Unfortunately I'm not able to receive the "[doTest] is not implemented yet" Message on the client. How I can get this message?
When I test the webservice I receive the correct message in the body. Unfortunately I don't know how I can access it via jersey?
Thanks.
You can use an ExceptionMapper for custom exceptions: https://jersey.java.net/apidocs/2.11/jersey/javax/ws/rs/ext/ExceptionMapper.html
Otherwise, Jersey tries to map exceptions as good as it can on its own.

Categories