With my very simple JAX-RS service I'm using Tomcat with JDBC realm for authentication, therefore I'm working the the JSR 250 annotations.
The thing is that I want to return a custom message body in the HTTP status response. The status code (403) should stay the same. For example, my service looks like the following:
#RolesAllowed({ "ADMIN" })
#Path("/users")
public class UsersService {
#GET
#Produces(MediaType.TEXT_PLAIN)
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public String getUsers() {
// get users ...
return ...;
}
}
If a user with a different role than "ADMIN" access the service, I want to change the response message to something like that (depending on the media type [xml/json]):
<error id="100">
<message>Not allowed.</message>
</error>
At the moment Jersey returns the following body:
HTTP Status 403 - Forbidden
type Status report
message Forbidden
description Access to the specified resource (Forbidden) has been forbidden.
Apache Tomcat/7.0.12
How can I change the default message body? Is there a way to handle the (maybe thrown) exception to build my own HTTP status response?
The easiest way to handle this sort of thing is to throw an exception and to register an exception mapper to convert into the kind of message you want to send in that case. So, suppose you throw an AccessDeniedException, you would then have a handler like this (with full class names in places for clarity):
#javax.ws.rs.ext.Provider
public class AccessDeniedHandler
implements javax.ws.rs.ext.ExceptionMapper<AccessDeniedException> {
public javax.ws.rs.core.Response toResponse(AccessDeniedException exn) {
// Construct+return the response here...
return Response.status(403).type("text/plain")
.entity("get lost, loser!").build();
}
}
The way in which you register the exception mapper varies according to the framework you're using, but for Jersey you should be fine with just using #Provider. I'll let you figure out for yourself how you want to generate the kind of error documents that you want, but I do recommend handling failures as HTTP error codes of some kind (that's more RESTful...)
With creating an ExceptionMapper (mapping exceptions of WebApplicationException) it is possible to "catch" certain exceptions thrown by the application:
#Provider
public class MyExceptionMapper implements ExceptionMapper<WebApplicationException> {
#Override
public Response toResponse(WebApplicationException weException) {
// get initial response
Response response = weException.getResponse();
// create custom error
MyError error = ...;
// return the custom error
return Response.status(response.getStatus()).entity(error).build();
}
}
You also need to add the package to your application web.xml for registering the provider:
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
com.myapp.userservice; // semi-colon seperated
com.myapp.mappedexception
</param-value>
</init-param>
REST is build upon HTTP so you don't have to change the default behavior of an authentication failure. Having a 403 error when accessing a resource is enough for the client to clearly understand what appends.
The more your resources are HTTP compliant, the more others can understand it.
Related
The JAX-RS implementation Jersey supports MVC style web applications through the Viewable class, which is a container for a template name and a model object. It is used like this:
#GET
#Template
#Produces({MediaType.TEXT_HTML})
public Viewable get() {
JsonObject response = null;
try{
response = service.getDetails(id);
}
catch(Exception ex) {
log.error("failed to get details", ex);
throw ex;
}
return new Viewable("/test", response);
}
this is right way to send the json from Viewable? Is there a way to set a json object explicitly?
A few things: I don't have any experience using Viewable in particular, but I am familiar with JAX-RS and can probably throw a couple of pointers your way.
Exception Handlers
JAX-RS defines a feature for mapping exceptions to responses. This functionality is nice for removing those exception blocks from your resource code. Check out the Jersey docs on this topic for a tutorial on how to register these. A quick summary is: 1) implement ExceptionMapper and 2) register the class as a Provider.
For starters, I recommend creating a simple suite that maps to common HTTP codes. For example:
NotFoundException - returns a 404 response and is used when a single entity is requested but not found.
InvalidInputException - returns a 422 response and is used when a request does not pass validation (like trying to save an phone number in an email field).
BadRequestException - usually the framework will handle these situations for you, but if not, a Bad Request is one that is not formatted properly. So if a required header is missing, or if a client tries to save a collection when only a single entity is allowed.
Exception* - There is a star here because an unexpected exception is usually due to a server error, so 500 is an appropriate default response. A reason you may want to create a global uncaught exception handler is to prevent the stacktrace from being returned in the response body. That can be bad for security reasons.
View and Model
You should not need the #Template annotation if you are using the Viewable object. Also, Viewable is expecting a template as the first argument and a model (map) as the second argument. The model should have keys that match variables in your JSP. Right now your method will look for a file called test.jsp in the root of whatever your template config is set to in web.xml. If you take all of that into consideration, your method could look something like this:
#GET
#Produces(MediaType.TEXT_HTML)
public Viewable getMobileReport() {
return new Viewable("/test", service.getMobileReport(id));
}
I am implementing JAX-RS using apache CXF. I have created an ExceptionMapper to handle bad requests like this:
public class ClientErrorExceptionMapper implements ExceptionMapper<ClientErrorException> {
#Override
public Response toResponse(final ClientErrorException exception) {
return Response.status(Response.Status.BAD_REQUEST).entity("Invalid request: Invalid URI.").build();
}
}
I am not sure how this works internally but i suppose that framework would throw an exception in case user is making an invalid request and this handler will prepare an error message to be send back. My problem is that i wish to preserve some custom headers that user sends in the request, so that i send that back with the response. But using this exception mapper, i cant see any option to get the original request headers. I can set any new header in the response, but i wish to preserve the request headers - like i do in a normal request.
So is there any way in JAX-RS where i can preserve or efficiently refer to the custom headers in current request ?
What we have resorted to is using a thread local variable to save the RequestContext when the request arrives and then in the ExceptionMapper we can obtain request specific information.
Ugly but it works. I think we have a generic filter in the filter list that catches all requests before dispatch.
I have a Jersey 1.8 application running. Jersey is running as a Servlet.
I need to write a servlet filter that given a plain request/response, is able to figure out which REST resource/method will respond to the request and extract values from annotations.
For example, imagine I have the following resource:
#Path("/foo")
#MyAnnotation("hello")
public class FooResource {
#GET
#Path("/bar")
#MyOtherAnnotation("world")
public Response bar(){
...
}
}
When a request GET /foo/bar comes in, I need my servlet filter to be able to extract the values "hello" and "world" from MyAnnotation and MyOtherAnnotation before Jersey's own servlet processes the request.
This filter logic should be able to work for all requests and all resources registered.
Is there a way to access Jersey's internal routing mechanism to obtain a class/method reference where Jersey will dispatch the request?
I'm open to other suggestions as well, but ideally nothing like trying to hack my own routing mechanism by reading the #Path annotations myself.
#Provider
#Priority(Priorities.AUTHORIZATION)
public class MyFilter implements ContainerRequestFilter
#Context // request scoped proxy
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (resourceInfo.getResourceClass().isAnnotationPresent(MyAnnotationion.class) ||
resourceInfo.getResourceMethod().isAnnotationPresent(MyOtherAnnotation.class)) {
to register the filter use
bind(AuthFilter.class).to(ContainerRequestFilter.class).in(Singleton.class);
I am currently developing a webservice which will always return json as a response to any request(all good request are working already). I would like to return a json when the http status of the request is a bad request(status 400 to be exact). I used the #ControllerAdvice of spring mvc to do the job:
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(Exception.class)
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Something went wrong. Please check your JSON REQUEST!")
public #ResponseBody ErrorClass processValidationError() {
// some stuff in setting the error response
return new ErrorClass();
}
But what happen is that it returns the default Tomcat 7 error message for bad request and not the json I set. processValidationError() will return an ErrorClass instance which will automatically be in json format using jackson plugin. Did I missed something?
I'm looking for way to present custom error pages if no matching JAX-RS resource was found. My idea was to use an ExceptionMapper, but I don't know the exception class to use:
#Provider
public class NotFoundMapper implements ExceptionMapper<WHATEXCEPTION?> {
...
}
RestEasy has a proprietary NotFoundException, but is there a way that works with Jersey, too? Ideally a standard compliant way?
Do you mean if your rest service is listening under localhost/rest then localhost/rest/asdf should return you a customized 404 page? It is specific to the JAX-RS engine, you must check the source code of Jersey. For example CXF throws a WebApplicationException, but it does not provide any way to customize it. See JAXRSInInterceptor.processRequest() line 156 to see how it is done :
org.apache.cxf.common.i18n.Message errorMsg =
new org.apache.cxf.common.i18n.Message("NO_ROOT_EXC",
BUNDLE,
message.get(Message.REQUEST_URI),
rawPath);
LOG.warning(errorMsg.toString());
Response resp = JAXRSUtils.createResponse(resource, message, errorMsg.toString(),
Response.Status.NOT_FOUND.getStatusCode(), false);
throw new WebApplicationException(resp);
To summarize there is no nice and standard way, you can try to use a request filter or an ExceptionMapper