Custom HTTP status code - java

I consider using fluent-http in a project.
I started with a simple "login/password" page. I create a simple POJO with fields login and password :
public class LoginRequest() {
private String login;
private String password;
//...
}
And I send it to fluent-http via a Resource :
#Prefix("/user")
public class PersonResource {
#Post("/")
public String get(LoginRequest loginRequest) {
//[...]
}
}
And it works well :)
Now, I wondered if it was possible to send a response with code HTTP 200 in case of success and code HTTP 401 in case of failure.
So I tried to inject the Response :
#Post("/")
public String login(LoginRequest loginRequest, Response response) {
if(loginRequest.getPassword().equals("helloworld")) {
response.setStatus(200);
return "SUCCESS";
} else {
response.setStatus(401);
return "ERROR";
}
}
The correct String is returned but the status code does not seem to be used. In both cases, the response has a code HTTP 200.
Note : I found that some status code are pre-implemented :
In case of exception, a code 500 is returned.
In case of resource not found, a code 400 is returned.
Any idea?

If you want to change the default content-type, status or headers, the method should return a net.codestory.http.payload.Payload.
Here's what you should write:
#Post("/")
public Payload login(LoginRequest loginRequest) {
if(!loginRequest.getPassword().equals("helloworld")) {
return new Payload("ERROR").withCode(HttpStatus.UNAUTHORIZED);
}
return new Payload("SUCCESS").withCode(HttpStatus.CREATED);
}

Related

Setting different http status codes based on validation results

I need to set different HTTP Status code for my REST webservice request.
Basically user will send ISBN number , I need to validate it
if user send empty request body , give error message ISBN cannot be empty
and set http status code
if user gives Alphabets , Given error message Alphabets not allowed and set http status code appropriate
if user gives wrong format, Give error message wrong format and set different HTTP status code.
if isbn is not valid, Give error message Not a Valid ISBN number and set appropriate HTTP status code.
If Valid ISBN number then return book name with http status as 200.
I tried setting http status code but its not reflecting.
#RequestMapping(value = "/person", method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
public ResponseEntity<StatusBean> findBook(#RequestBody String json) {
StatusBean sb = new StatusBean();
if(json==null) {
sb.setMessage("Request Cannot be Null");
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
if(!isNumeric(json)) {
sb.setMessage("Request Cannot have Alphabets Characters");
//here i need to set different status
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
if(!isValidFormat(json)) {
sb.setMessage("Request Cannot have Alphabets Characters");
//here i need to set different status
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
if(!isValidISBN(json)) {
sb.setMessage("Request Cannot have Alphabets Characters");
//here i need to set different status
return new ResponseEntity<StatusBean>(sb,HttpStatus.BAD_REQUEST);
}
Map<String,String> map = new HashMap<>();
map.put("book", "Effective Java");
sb.setResponseJSONMap(map);
return new ResponseEntity<StatusBean>(sb,HttpStatus.OK);
}
public class StatusBean {
private String message;
private Map<String,String> responseJSONMap;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Map<String, String> getResponseJSONMap() {
return responseJSONMap;
}
public void setResponseJSONMap(Map<String, String> responseJSONMap) {
this.responseJSONMap = responseJSONMap;
}
}
One of the most elegant solutions is the following:
You can throw a custom exception in case of a validation error, like this:
#RequestMapping(...)
public ResponseEntity<StatusBean> findBook(#RequestBody String json) throws Exception {
...
if(json==null) {
throw new NullRequestException();
}
if(!isNumeric(json)) {
throw new RequestContainsAlphabetsException();
}
if(!isValidFormat(json)) {
throw new InvalidFormatException();
}
...
}
And then you need to define an own, global exception handler at application level. In this custom exception handler, you will catch the thrown exceptions and send back a proper response to clients with a custom error message, an HTTP response code, a timestamp, etc.
For more details see this page.
#RequestMapping(value = "/person", method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
public ResponseEntity<StatusBean> findBook(#RequestBody(required=false) String json) {
// rest of the code
}
try using (required=false) with request body. Spring requires resuest body by default.

How to extract the response body into post filter using zuul

I'm working on a POC i need to use zuul as a sever to route 2 routes first will run normally but it has a custom post filter which will send another request to other api using some data of the response of the first requet,
so need to extract the response body of the first request into my custom post filter and get some specific attributes but i can not find the response as it always be null but the status code is 200.
how can i wait and get a value of specific attribute from the response and get the actual status code not just 200 as default value.
i tried to make this implementation using cloud gateway but i reached the same point of disability of extracting the response.
also i tried to make a response decorator but it failed too.
#Component
public class AddResponseHeaderFilter extends ZuulFilter {
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
System.out.println("this is my filter");
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = new HttpServletRequestWrapper(context.getRequest());
System.out.println(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
HttpServletResponse servletResponse = context.getResponse();
// return an address only
System.out.println(context.getResponseBody().toString());
servletResponse.addHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}
RequestContext.getCurrentContext().getResponseDataStream() works fine for me, I am also able to manipulate the response.
import java.nio.charset.Charset;
import org.springframework.util.StreamUtils;
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String requestLog = StreamUtils.copyToString(request.getInputStream(),
Charset.forName("UTF-8"));

No suitable HttpMessageConverter found for response type in in case the backend response with a error status code

I'm using robospice for my RESTful Communication to a backend.
Here is the specification of the interface:
## carid [/cars]
### Register a car[POST]
+ Request (application/json)
+ Header
Accepted-Language: en
Authorization: Basic xyz
+ Body
{
"carId" : "ad885f2b-dbd3-49ad-aa34-a7671cf5e337"
}
+ Response 200 (application/json)
{
"magicQuestionDefined": true,
"newCar": true
}
+ Response 401
+ Response 404 (application/json)
{
"message" : "No car owner found"
}
+ Response 400
+ Response 500
Here ist the snipped of my listener, when I receive an error (in my case its only a problem with errors, the normal case returning a json works fine) :
private class RegisterCarRequestListener implements RequestListener<RegisterCarResult> {
#Override
public void onRequestFailure(SpiceException e) {
// e: Networkexception
// e.cause=RestClientException
// e.cause.detailMessage="Could not extract response: no suitable HttpMessageConverter found for response type [ch.mycarapp.lib.model.RegisterCarResult] and content type [text/html;charset=iso-8859-1]"
}
It all works fine until the Backend answers with a 401. In this case I except an Exception of type:
HttpStatusCodeException for evaluating the exception status-code.
The reason is "e.cause.detailMessage="Could not extract response: no suitable HttpMessageConverter found for response type [ch.mycarapp.lib.model.RegisterCarResult] and content type [text/html;charset=iso-8859-1]" but it is just a status code 401 that gets returned!
The service is created like this:
public class JsonCarService extends SpringAndroidSpiceService {
#Override
public CacheManager createCacheManager(Application application) throws CacheCreationException {
final CacheManager cacheManager = new CacheManager();
final JacksonObjectPersisterFactory jacksonObjectPersisterFactory = new JacksonObjectPersisterFactory(application);
cacheManager.addPersister(jacksonObjectPersisterFactory);
return cacheManager;
}
#Override
public RestTemplate createRestTemplate() {
final RestTemplate restTemplate = new RestTemplate();
// web services support json responses
final MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
// web Services support also Text Messages ( for some error cases
final StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(Charset.forName("ISO-8859-1"));
final List<HttpMessageConverter<?>> listHttpMessageConverters = restTemplate.getMessageConverters();
listHttpMessageConverters.add(jsonConverter);
listHttpMessageConverters.add(stringConverter);
restTemplate.setMessageConverters(listHttpMessageConverters);
return restTemplate;
}
}
As you can see I have added a stringConverter too, but unfortunately it did not resolve the problem.
public class RegisterCarRequest extends BaseCarSpiceRequest<RegisterCarResult> {
RegisterCarInput input;
private final Context ctx;
public RegisterDeviceRequest(RegisterCarInput input, Context ctx) {
super(RegisterCarResult.class);
this.input = input;
this.ctx = ctx;
}
#Override
public RegisterCarResult loadDataFromNetwork() throws Exception {
final String url = ctx.getString(R.string.base_url) + "/cars";
final HttpEntity<?> requestEntity = new HttpEntity<Object>(input, getRequestHeaders());
final ResponseEntity<RegisterCarResult> response = getRestTemplate().exchange(url, HttpMethod.POST, requestEntity,
RegisterCarResult.class);
return response.getBody();
}
}
The pojo for sending to the server is this one:
public class RegisterCarInput {
private String carId;
public String getCarId() {
return carId;
}
public void setCarId(String carId) {
this.carId = carId;
}
}
the pojo for the Response is this Object:
public class RegisterCarResult {
private Boolean magicQuestionDefined;
private Boolean newCar;
public Boolean getMagicQuestionDefined() {
return magicQuestionDefined;
}
public void setMagicQuestionDefined(Boolean magicQuestionDefined) {
this.magicQuestionDefined = magicQuestionDefined;
}
public Boolean getNewDevice() {
return newCar;
}
public void setNewCar(Boolean newCar) {
this.newCar = newCar;
}
}
The Request gets called like this:
private void performRequest(RegisterCarInput input, User user) {
final RegisterCarRequest request = new RegisterCarRequest(input, getApplicationContext());
request.setAuthUser(user);
spiceManager.execute(request, new RegisterCarRequestListener());
}
Does anybody see a missing link for being able to parse different structures of responses?
Is it really necessary (for robospice) to receive httpstatuscodes (with a
content-type=application/Json for errors with no payload and a 401 status code) ?
http states in the Listener when the backend returns with a 401 ?
tia
Luke
EDITED:
I did some investigation and can now say that the backend returns a 403 when my basic authentication is wrong and sends following response:
+ Response 403 (text/html)
HTTP/1.1 403 Forbidden
Date: Wed, 12 Nov 2014 08:26:14 GMT
Server: Apache
Transfer-Encoding: chunked
Content-Type: text/html
97
<html>
<head>
<title>
403 Forbidden
</title>
</head>
<body>
<h1>Forbidden</h1>
You tried to access a page without having the required permissions.<br>
</body>
</html>
Unfortunately I don't have any influence to the behavior of the backend.
Facts:
The calls are working when everything is ok (200) and receiving RegisterCarResult as json
The calls are working when only a status code (401) (with no content in the response entity) is returned.
The calls are failing when a status code (403) with html content is returned in the entity
So the question, which answer will solve my problem is this:
How can I handle RestResponses, which can have different content-types (application/json)(text/html) in their entities with ROBOSPICE?
I want to send a Json with a request to the backend => ok
I want to receive a 200 as response and a application/json in the response entity => ok
I want to receive only a status code 401 with no content => ok
I want to receive a status code 403 with text/html content => HOW?
All those responses(2,3,4) are possible answers to the one request(1)...

post service is not calling throwing 400 (Bad Request)

Hi friends I am using Angularjs and rest-servies but when I am calling rest services from service.js file something is goning wrong and it is throwing 400(bad request )
main.js
garantiesService.getTarifs($scope.recap.ageDirigeant,$scope.selectedCompany.zipcode)
.success(function(){
console.log('in success');
})
service.js
healthApp.factory('garantiesService', ['$http', function($http) {
var service = {
getTarifs: function(age,zipcode)
{
console.log("age : "+age);
console.log("zipcode : "+zipcode);
var directorHealthInsuranceInfo = {};
directorHealthInsuranceInfo.age=age;
directorHealthInsuranceInfo.department=zipcode;
return $http.post('rest-service/quotes/health /director',directorHealthInsuranceInfo);
}
};
return service;
HealthInsuranceController.java
#Controller
public class HealthInsuranceQuoteResource {
#RequestMapping("quotes/health/director")
#ResponseBody
public String quoteDirector(#RequestBody DirectorHealthInsuranceInfo info) {
System.out.println("------HealthInsuranceQuoteResult------");
return "hi";
}
DirectorHealthInsuranceInfo.java
#Value
public class DirectorHealthInsuranceInfo {
private String department;
private int age;
}
when I am sending the request it is throwing Bad Request 400 error.
I see that there is a space in the url you supplied to the http.post method.
"rest-service/quotes/health /director"
I don't know if that is causing it.
But I also see that you POST your request to the service. Are you sure that your endpoint has been set up for POST requests?
I would recommend creating a basic endpoint that you call with a GET request, and no parameters. Just to root out the problem.

Handling custom error response in JAX-RS 2.0 client library

I am starting to use the new client API library in JAX-RS and really loving it so far. I have found one thing I cannot figure out however. The API I am using has a custom error message format that looks like this for example:
{
"code": 400,
"message": "This is a message which describes why there was a code 400."
}
It returns 400 as the status code but also includes a descriptive error message to tell you what you did wrong.
However the JAX-RS 2.0 client is re-mapping the 400 status into something generic and I lose the good error message. It correctly maps it to a BadRequestException, but with a generic "HTTP 400 Bad Request" message.
javax.ws.rs.BadRequestException: HTTP 400 Bad Request
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:908)
at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:770)
at org.glassfish.jersey.client.JerseyInvocation.access$500(JerseyInvocation.java:90)
at org.glassfish.jersey.client.JerseyInvocation$2.call(JerseyInvocation.java:671)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:424)
at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:667)
at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:396)
at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:296)
Is there some sort of interceptor or custom error handler that can be injected so that I get access to the real error message. I've been looking through documentation but can't see any way of doing it.
I am using Jersey right now, but I tried this using CXF and got the same result. Here is what the code looks like.
Client client = ClientBuilder.newClient().register(JacksonFeature.class).register(GzipInterceptor.class);
WebTarget target = client.target("https://somesite.com").path("/api/test");
Invocation.Builder builder = target.request()
.header("some_header", value)
.accept(MediaType.APPLICATION_JSON_TYPE)
.acceptEncoding("gzip");
MyEntity entity = builder.get(MyEntity.class);
UPDATE:
I implemented the solution listed in the comment below. It is slightly different since the classes have changed a bit in the JAX-RS 2.0 client API. I still think it is wrong that the default behavior is to give a generic error message and discard the real one. I understand why it wouldn't parse my error object, but the un-parsed version should have been returned. I end up having the replicate exception mapping that the library already does.
Thanks for the help.
Here is my filter class:
#Provider
public class ErrorResponseFilter implements ClientResponseFilter {
private static ObjectMapper _MAPPER = new ObjectMapper();
#Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
// for non-200 response, deal with the custom error messages
if (responseContext.getStatus() != Response.Status.OK.getStatusCode()) {
if (responseContext.hasEntity()) {
// get the "real" error message
ErrorResponse error = _MAPPER.readValue(responseContext.getEntityStream(), ErrorResponse.class);
String message = error.getMessage();
Response.Status status = Response.Status.fromStatusCode(responseContext.getStatus());
WebApplicationException webAppException;
switch (status) {
case BAD_REQUEST:
webAppException = new BadRequestException(message);
break;
case UNAUTHORIZED:
webAppException = new NotAuthorizedException(message);
break;
case FORBIDDEN:
webAppException = new ForbiddenException(message);
break;
case NOT_FOUND:
webAppException = new NotFoundException(message);
break;
case METHOD_NOT_ALLOWED:
webAppException = new NotAllowedException(message);
break;
case NOT_ACCEPTABLE:
webAppException = new NotAcceptableException(message);
break;
case UNSUPPORTED_MEDIA_TYPE:
webAppException = new NotSupportedException(message);
break;
case INTERNAL_SERVER_ERROR:
webAppException = new InternalServerErrorException(message);
break;
case SERVICE_UNAVAILABLE:
webAppException = new ServiceUnavailableException(message);
break;
default:
webAppException = new WebApplicationException(message);
}
throw webAppException;
}
}
}
}
I believe you want to do something like this:
Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
System.out.println( response.getStatusType() );
return null;
}
return response.readEntity( MyEntity.class );
Another thing you can try (since I don't know where this API puts stuff -- i.e. in the header or entity or what) is:
Response response = builder.get( Response.class );
if ( response.getStatusCode() != Response.Status.OK.getStatusCode() ) {
// if they put the custom error stuff in the entity
System.out.println( response.readEntity( String.class ) );
return null;
}
return response.readEntity( MyEntity.class );
If you would like to generally map REST response codes to Java exception you can add a client filter to do that:
class ClientResponseLoggingFilter implements ClientResponseFilter {
#Override
public void filter(final ClientRequestContext reqCtx,
final ClientResponseContext resCtx) throws IOException {
if ( resCtx.getStatus() == Response.Status.BAD_REQUEST.getStatusCode() ) {
throw new MyClientException( resCtx.getStatusInfo() );
}
...
In the above filter you can create specific exceptions for each code or create one generic exception type that wraps the Response code and entity.
There are other ways to getting a custom error message to the Jersey client besides writing a custom filter. (although the filter is an excellent solution)
1) Pass error message in an HTTP header field.
The detail error message could be in the JSON response and in an additional header field, such as "x-error-message".
The Server adds the HTTP error header.
ResponseBuilder rb = Response.status(respCode.getCode()).entity(resp);
if (!StringUtils.isEmpty(errMsg)){
rb.header("x-error-message", errMsg);
}
return rb.build();
The Client catches the exception, NotFoundException in my case, and reads the response header.
try {
Integer accountId = 2222;
Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target("http://localhost:8080/rest-jersey/rest");
webTarget = webTarget.path("/accounts/"+ accountId);
Invocation.Builder ib = webTarget.request(MediaType.APPLICATION_JSON);
Account resp = ib.get(new GenericType<Account>() {
});
} catch (NotFoundException e) {
String errorMsg = e.getResponse().getHeaderString("x-error-message");
// do whatever ...
return;
}
2) Another solution is to catch the exception and read the response content.
try {
// same as above ...
} catch (NotFoundException e) {
String respString = e.getResponse().readEntity(String.class);
// you can convert to JSON or search for error message in String ...
return;
}
The class WebApplicationException was designed for that but for some reason it ignores and overwrites what you specify as parameter for the message.
For that reason I created my own extension WebAppException that honors the parameters. It is a single class and it doesn't require any response filter or a mapper.
I prefer exceptions than creating a Response as it can be thrown from anywhere while processing.
Simple usage:
throw new WebAppException(Status.BAD_REQUEST, "Field 'name' is missing.");
The class:
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;
public class WebAppException extends WebApplicationException {
private static final long serialVersionUID = -9079411854450419091L;
public static class MyStatus implements StatusType {
final int statusCode;
final String reasonPhrase;
public MyStatus(int statusCode, String reasonPhrase) {
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
#Override
public int getStatusCode() {
return statusCode;
}
#Override
public Family getFamily() {
return Family.familyOf(statusCode);
}
#Override
public String getReasonPhrase() {
return reasonPhrase;
}
}
public WebAppException() {
}
public WebAppException(int status) {
super(status);
}
public WebAppException(Response response) {
super(response);
}
public WebAppException(Status status) {
super(status);
}
public WebAppException(String message, Response response) {
super(message, response);
}
public WebAppException(int status, String message) {
super(message, Response.status(new MyStatus(status, message)). build());
}
public WebAppException(Status status, String message) {
this(status.getStatusCode(), message);
}
public WebAppException(String message) {
this(500, message);
}
}
A much more concise solution for anyone stumbling on this:
Calling .get(Class<T> responseType) or any of the other methods that take the result type as an argument Invocation.Builder will return a value of the desired type instead of a Response. As a side effect, these methods will check if the received status code is in the 2xx range and throw an appropriate WebApplicationException otherwise.
From the documentation:
Throws: WebApplicationException in case the response status code of
the response returned by the server is not successful and the
specified response type is not Response.
This allows to catch the WebApplicationException, retrieve the actual Response, process the contained entity as exception details (ApiExceptionInfo) and throw an appropriate exception (ApiException).
public <Result> Result get(String path, Class<Result> resultType) {
return perform("GET", path, null, resultType);
}
public <Result> Result post(String path, Object content, Class<Result> resultType) {
return perform("POST", path, content, resultType);
}
private <Result> Result perform(String method, String path, Object content, Class<Result> resultType) {
try {
Entity<Object> entity = null == content ? null : Entity.entity(content, MediaType.APPLICATION_JSON);
return client.target(uri).path(path).request(MediaType.APPLICATION_JSON).method(method, entity, resultType);
} catch (WebApplicationException webApplicationException) {
Response response = webApplicationException.getResponse();
if (response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
throw new ApiException(response.readEntity(ApiExceptionInfo.class), webApplicationException);
} else {
throw webApplicationException;
}
}
}
ApiExceptionInfo is custom data type in my application:
import lombok.Data;
#Data
public class ApiExceptionInfo {
private int code;
private String message;
}
ApiException is custom exception type in my application:
import lombok.Getter;
public class ApiException extends RuntimeException {
#Getter
private final ApiExceptionInfo info;
public ApiException(ApiExceptionInfo info, Exception cause) {
super(info.toString(), cause);
this.info = info;
}
}
[At least with Resteasy] there is one big disadvantage with the solution offered by #Chuck M and based on ClientResponseFilter.
When you use it based on ClientResponseFilter, your BadRequestException, NotAuthorizedException, ... exceptions are wrapped by javax.ws.rs.ProcessingException.
Clients of your proxy must not be forced to catch this javax.ws.rs.ResponseProcessingException exception.
Without filter, we get an original rest exception. If we catch and handle by default, it does not give us much:
catch (WebApplicationException e) {
//does not return response body:
e.toString();
// returns null:
e.getCause();
}
The problem can be solved on another level, when you extract a description from the error. WebApplicationException exception, which is a parent for all rest exceptions, contains javax.ws.rs.core.Response. Just write a helper method, that in case the exception is of WebApplicationException type, it will also check the response body. Here is a code in Scala, but the idea should be clear. The methord returns a clear description of the rest exception:
private def descriptiveWebException2String(t: WebApplicationException): String = {
if (t.getResponse.hasEntity)
s"${t.toString}. Response: ${t.getResponse.readEntity(classOf[String])}"
else t.toString
}
Now we move a responsibility to show exact error, on the client. Just use a shared exception handler to minimize effort for clients.
The following works for me
Response.status(Response.Status.BAD_REQUEST).entity(e.getMessage()).build();

Categories

Resources