O java-restify oferece diferentes abordagens para manipulação e recuperação de erros.
Além dos erros de I/O (comunicação, rede, streams, etc) que podem ocorrer durante a requisiçao HTTP, o java-restify converte respostas HTTP 4xx (client error) e 5xx (server error) para exceções. Esse é o comportamento padrão, mas pode ser customizado para atender diferentes necessidades.
Todas as exceções do java-restify extendem com.github.ljtfreitas.restify.http.client.HttpException. Exceções ocorridas durante a execução do método ou durante a requisição HTTP sempre serão encapsuladas e propagadas em uma exceção do tipo HttpException.
Existem duas especializações dessa exceção:
HttpClientException, que representa erros de I/O
HttpMessageException, para exceções durante a manipulação da requisição e da resposta.
HttpMessageException possui uma subclasse chamada EndpointResponseException, que representa uma resposta HTTP de erro (status code 4xx ou 5xx). Essa classe possui várias sub-exceções, cada uma representando um status de erro HTTP específico. Isso permite implementar um controle fino para situações específicas:
public interface MyApi {
@Path("/customers/{id}") @Get
Customer getCustomerById(@PathParameter String id);
}
MyApi myApi = new RestifyProxyBuilder()
.target(MyApi.class)
.build();
try {
Customer customer = myApi.getCustomerById("abc123");
} catch (EndpointResponseNotFoundException e) {
// 404 Not Found
} catch (EndpointResponseUnauthorizedException e) {
// 401 Unauthorized
} catch (EndpointResponseNotAcceptableException e) {
// 406 Not Acceptable
} catch (EndpointResponseException e) {
// qualquer outra resposta de erro HTTP
} catch (HttpClientException e) {
// erros de I/O
}
Requisições assíncronas
Caso a requisição seja assíncrona, o tratamento de falhas deve ser implementado usando o próprio objeto de retorno ou com o uso de callbacks.
Por exemplo, caso o retorno seja um CompletableFuture:
public interface MyApi {
@Path("/customers/{id}") @Get
CompletableFuture<Customer> getCustomerById(@PathParameter String id);
}
MyApi myApi = new RestifyProxyBuilder()
.target(MyApi.class)
.build();
myApi.getCustomerById("abc123")
.whenComplete((customer, exception) -> {
// lógica de falha ou sucesso
});
Ou utilizando callbacks:
public interface MyApi {
@Path("/customers/{id}") @Get
void getCustomerById(@PathParameter String id, @CallbackParameter EndpointCallSuccessCallback<Customer> success, @CallbackParameter EndpointCallFailureCallback failure);
}
MyApi myApi = new RestifyProxyBuilder()
.target(MyApi.class)
.build();
myApi.getCustomerById("abc123",
customer -> { /* lógica de sucesso */},
exception -> { /*lógica de falha */});
A interface EndpointCallFailureCallback (representada acima como uma expressão lambda) tem um único método, onFailure, que recebe um Throwable como parâmetro, permitindo o tratamento de qualquer exceção; existe uma especialização chamada EndpointResponseFailureCallback específica para respostas de erro, que permite uma manipulação fina de cada cenário de erro:
public interface MyApi {
@Path("/customers/{id}") @Get
void getCustomerById(@PathParameter String id, @CallbackParameter EndpointCallSuccessCallback<Customer> success, @CallbackParameter EndpointResponseFailureCallback failure);
}
MyApi myApi = new RestifyProxyBuilder()
.target(MyApi.class)
.build();
EndpointResponseFailureCallback failure = new EndpointResponseFailureCallback() { // classe abstrata
// sobrescreva os métodos para cada resposta de erro que deseje manipular
@Override
protected void onNotFound(EndpointResponse<String> response) {
// 404 Not Found
}
@Override
protected void onUnauthorized(EndpointResponse<String> response) {
// 404 Not Found
}
@Override
protected void onNotAcceptable(EndpointResponse<String> response) {
// 404 Not Found
}
protected void onFailure(EndpointResponse<String> response) {
// qualquer outra resposta de erro HTTP
}
protected void onException(Throwable throwable) {
// qualquer outra exceção, incluindo erros de I/O
}
};
myApi.getCustomerById("abc123",
customer -> { /* lógica de sucesso */},
failure);
Recuperação de respostas de erro
Além da captura da exceção, através de try/catch ou callback de falha, outra abordagem possível é implementar estratégias para recuperação de respostas de erro.
O objeto EndpointResponseErrorFallback é responsável pela manipulação de respostas 4xx e 5xx, podendo eventualmente retornar outra resposta. A implementação padrão é o comportamento demonstrado acima: a propagação de exceções específicas por tipo de resposta.
Mas implementações dessa interface podem implementar qualquer comportamento sobre respostas de erro. Por exemplo, um caso especial é o status code404 (Not Found); respostas com esse status podem ser tratadas como uma resposta de corpo vazio, ao invés de uma exceção.
public interface MyApi {
// a API devolve 404, caso não encontre um Customer com o id
@Path("/customers/{id}") @Get
Customer getCustomerById(@PathParameter String id);
}
MyApi myApi = new RestifyProxyBuilder()
.error()
.emptyOnNotFound() // respostas 404 serão consideradas como uma resposta sem corpo
.target(MyApi.class)
.build();
// se a resposta for 404, o retorno do método será null
Customer customer = myApi.getCustomerById("xyz123");
Uma solução elegante para a situação acima seria utilizar um Optional como retorno de método, representando uma resposta potencialmente vazia:
public interface MyApi {
// a API devolve 404, caso não encontre um Customer com o id
@Path("/customers/{id}") @Get
Optional<Customer> getCustomerById(@PathParameter String id);
}
MyApi myApi = new RestifyProxyBuilder()
.error()
.emptyOnNotFound() // respostas 404 serão consideradas como uma resposta sem corpo
.target(MyApi.class)
.build();
// se a resposta for 404, o retorno do método será um Optional vazio
Optional<Customer> customer = myApi.getCustomerById("xyz123");
Também é possível implementar qualquer outra lógica para recuperação de respostas 4xx ou 5xx:
class MyResponseErrorFallback implements EndpointResponseErrorFallback {
@Override
public <T> EndpointResponse<T> onError(HttpResponseMessage response, JavaType responseType) {
/*
HttpResponseMessage é um objeto de nível mais baixo, que fornece acesso à resposta HTTP "crua".
O segundo parâmetro representa o tipo de retorno do método.
Implemente sua lógica de recuperação para retornar um EndpointResponse compatível com o tipo de retorno esperado, ou lançe uma exceção mais adequada ao seu domínio.
*/
}
}
MyApi myApi = new RestifyProxyBuilder()
.error(new MyResponseErrorFallback())
.target(MyApi.class)
.build();
// ou
MyApi myApi = new RestifyProxyBuilder()
.error()
.using(new MyResponseErrorFallback())
.target(MyApi.class)
.build();
EndpointResponse
Esse objeto possui alguns métodos que permitem implementar uma lógica de recuperação de falhas, em caso de respostas 4xx ou 5xx.
public interface MyApi {
@Path("/customers/{id}") @Get
EndpointResponse<Customer> getCustomerById(@PathParameter String id);
}
MyApi myApi = new RestifyProxyBuilder()
.target(MyApi.class)
.build();
EndpointResponse<Customer> response = myApi.getCustomerById("abc123");
// o método recover recebe o tipo de exceção que deve ser manipulada, e uma funcão que retorna uma nova resposta
Customer customer = response.recover(EndpointResponseNotFoundException.class, e -> EndpointResponse.empty(StatusCode.ok()));
O EndpointResponse deve ser manipulado com cuidado; ao tentar acessar o corpo, em caso de respostas de erro, será lançada a exceção correspondente ao status code:
public interface MyApi {
@Path("/customers/{id}") @Get
EndpointResponse<Customer> getCustomerById(@PathParameter String id);
}
MyApi myApi = new RestifyProxyBuilder()
.target(MyApi.class)
.build();
// digamos que a resposta desse request foi 500 (Internal Server Error)
EndpointResponse<Customer> response = myApi.getCustomerById("abc123");
Customer customer = response.body(); // será lançada uma exceção do tipo EndpointResponseInternalServerErrorException