Handling Ratpack Context.parse execptions - ratpack

I'm writing an API in Kotlin with the Ratpack framework, using Jackson to deserialize JSON request bodies. When I send an invalid request body, my application throws a 500 internal server error exception:
import com.google.inject.Inject
import com.mycompany.mynamespace.MyComponent
import ratpack.func.Action
import ratpack.handling.Chain
import java.util.UUID
class MyTestEndpoint #Inject constructor(
private val myComponent: MyComponent) : Action<Chain> {
override fun execute(chain: Chain) {
chain
.post { ctx ->
ctx.parse(MyParams::class.java)
.map { parsedObject -> myComponent.process(parsedObject) }
.then { ctx.response.send() }
}
}
}
data class MyParams(val borrowingId: UUID)
The exception when this endpoint is hit with an invalid request body is:
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.mycompany.mynamespace.MyParams] value failed for JSON property borrowingId due to missing (therefore NULL) value for creator parameter borrowingId which is a non-nullable type
I have a generic error handler which checks the type of Exception thrown, and returns an appropriate status. But in this case, checking for a MissingKotlinParameterException and returning 400 bad request does not make sense, because this exception may be thrown in other circumstances.
Additionally, I could add onError after the ctx.parse line, but this is going to be a large API, and implementing that in every handler doesn't follow the pattern of having a generic error handler to keep the API consistent. Is there a way to get Ratpack to throw a specific exception (something like ParseFailedException) when the parse fails, so that I can catch it and return a 400 bad request?

As a workaround I've written an extension method:
fun <T: Any> Context.tryParse(type: Class<T>): Promise<T> {
return parse(type)
.onError { ex -> throw BadRequestException(ex) }
}
Where my generic error handler catches the BadRequestException, and sets the response status to 400

Related

Setting custom date formats through Jackson2ObjectMapperBuilder causing request processing to continue after exception

I have a MockMvc test for testing that a JSON payload to a controller is validated and a HTTP 400 (bad request) is rendered for org.springframework.data.mapping.PropertyReferenceException and org.springframework.http.converter.HttpMessageConversionException.
The respective exception handlers are implemented as follows.
#ControllerAdvice
public class LocalExceptionHandler {
#ExceptionHandler(PropertyReferenceException.class)
public ResponseEntity<Object> handlePropertyReferenceException(PropertyReferenceException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(HttpMessageConversionException.class)
public ResponseEntity<Object> handleHttpMessageConversionException(HttpMessageConversionException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
I'm using a Spock specification for implementing the test. The setup is as follows.
MockMvc mvc
public JsonSerializer[] buildJsonSerializers() {
return new JsonSerializer[]{new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_FORMAT)),
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT))};
}
Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.simpleDateFormat(DATE_TIME_FORMAT);
builder.serializers(buildJsonSerializers());
};
}
protected ObjectMapper buildObjectMapper() {
def objectMapperBuilder = new Jackson2ObjectMapperBuilder()
jsonCustomizer().customize(objectMapperBuilder)
objectMapperBuilder.modules(new MoneyModule()
.withMonetaryAmount(Money::of)
.withAmountFieldName("number")
.withFormattedFieldName("pretty"))
objectMapperBuilder.build()
}
def setup() {
ObjectMapper mapper = buildObjectMapper()
def mockMvcBuilder = MockMvcBuilders
.standaloneSetup(controller)
.setControllerAdvice(LocalExceptionHandler.class)
.setMessageConverters([new MappingJackson2HttpMessageConverter(mapper)]
.toArray(new HttpMessageConverter[1]))
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
mvc = mockMvcBuilder.build()
}
So the above setup just sets the date format through a customizer and then builds the object mapper using the Jackson2ObjectMapperBuilder.
The problem with that setup is that the builder is causing an object mapper configuration that results in a weird MockMvc behaviour.
When posting a bad request to a controller, a proper Exception is thrown and handled by one of the above exception handlers but request processing is not stopped and the controller method is invoked.
When running the production code (as Spring Boot application) error handling is just fine resulting with a HTTP 400.
Just by removing the builder and mimicking just the configuration desired for the test (which is a proper date time format) the test works as expected and request processing is stopped after exception handling.
So basically instead of using the builder I do
def mapper = new ObjectMapper()
mapper.registerModule(new MoneyModule()
.withMonetaryAmount(Money::of)
.withAmountFieldName("number")
.withFormattedFieldName("pretty"))
SimpleModule serializerModule = new SimpleModule()
Arrays.asList(buildJsonSerializers())
.forEach({ s -> serializerModule.addSerializer(s.handledType(), s) })
mapper.registerModule(serializerModule)
So it really looks like the builder is adding some configuration that MockMvc doesn't really deal with properly.
Would appreciate hints on resolving this.

Handling 405 errors

I am trying to handle 405 (Method not Allowed) errors generated from WebApi.
E.g.: Basically this error will be handled whenever someone calls my Api with a Post request instead of a Get one.
I'd like to do this programatically (i.e. no IIS configuration), right now there are no documentation of handling this kind of error and the IExceptionHandler is not triggered when this exception happens.
Any ideas ?
Partial response:
By looking at the HTTP Message lifecycle from here, there is a possibility to add a Message Handler early in the pipeline, before HttpRoutingDispatcher.
So, create a handler class:
public class NotAllowedMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
switch (response.StatusCode)
{
case HttpStatusCode.MethodNotAllowed:
{
return new HttpResponseMessage(HttpStatusCode.MethodNotAllowed)
{
Content = new StringContent("Custom Error Message")
};
}
}
}
return response;
}
}
In your WebApiConfig, in Register method add the following line:
config.MessageHandlers.Add(new NotAllowedMessageHandler());
You can check the status code of the response and generate a custom error message based on it.

How to change controller response in filter to make the response structure consistent all over the API's using spring-boot

I have implemented REST API using spring-boot application. All my API's are now returning response in JSON format for each entity. This response was consumed by other server which expects all these response are in same JSON format. For example;
All my responses should be accommodated within the following structure;
public class ResponseDto {
private Object data;
private int statusCode;
private String error;
private String message;
}
Currently spring-boot returns error responses in a different format. How to achieve this using filter.
Error message format;
{
"timestamp" : 1426615606,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/welcome",
"message" : "Required String parameter 'name' is not present"
}
I need both error and successfull response are in same json structure all over my spring-boot application
This can be achieved simply by utilizing a ControllerAdvice and handling all possible exceptions, then returning a response of your own choosing.
#RestControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(Throwable.class)
public ResponseDto handleThrowable(Throwable throwable) {
// can get details from throwable parameter
// build and return your own response for all error cases.
}
// also you can add other handle methods and return
// `ResponseDto` with different values in a similar fashion
// for example you can have your own exceptions, and you'd like to have different status code for them
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(CustomNotFoundException.class)
public ResponseDto handleCustomNotFoundException(CustomNotFoundException exception) {
// can build a "ResponseDto" with 404 status code for custom "not found exception"
}
}
Some great read on controller advice exception handlers

Spring MVC optionally returning no view in a single mapping

I have a case where i need to :
Return a 304 not modified status if the blog hasnt been modified
Or return the blog view if it's been modified since If-Modified-Since request header
The problem is when i want to return 304 status, how do i tell spring mvc not to assume another view from the null return, and start sending the response with the status immediately ?
#RequestMapping(value={"/blogs/{blogId}"}, method=RequestMethod.GET)
public String hello(final HttpServletRequest req, final HttpServletResponse resp, final Model model,
#PathVariable("blogId") final String blogId) {
if (isModified(req, blogId)) {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return null; // this doesnt stop spring mvc to assume a view name
}
populate(model, grabBlog(blogId));
return "blog";
}
From the HTTP perspective returning a view doesn't make sense at all. The Spring documentation covers that use case:
#RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
long lastModified = // 1. application-specific calculation
if (request.checkNotModified(lastModified)) {
// 2. shortcut exit - no further processing necessary
return null;
}
// 3. or otherwise further request processing, actually preparing content
model.addAttribute(...);
return "myViewName";
}
There are two key elements to note: calling request.checkNotModified(lastModified) and returning null. The former sets the response status to 304 before it returns true. The latter, in combination with the former, causes Spring MVC to do no further processing of the request.
You can create an exception with the appropriate annotation and then throw it. Spring will then generate a page for that error code.
From: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// ...
}

Returning an error and message from a Spring controller or service

I'm migrating some servlets over to the Spring framework, using Spring MVC. Currently in each servlet we authenticate the user and if the authentication fails we do this:
if (authfailed)
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"You are not authorized.");
return;
}
On the front end is a YUI-based application, and when an error status is returned the "failure" callback displays a dialog with the error message given above.
I know in my controller I can get the response object and call sendError, but is that the best way to handle this? sendError also throws an IOException so I'd have to catch that - a bit of annoying code to insert in every method of every controller.
I have the same problem handling exceptions - the servlets have try-catch blocks that call sendError in the catch method. I know I can mark my exception handlers with
#ExceptionHandler
#ResponseStatus(value = HttpStatus.NOT_FOUND)
but doesn't the exception handling class need to be in each controller class?
Finally, if the exception happens in a service called from a controller, does the exception bubble up to the controller or should I handle the exception in the service (thus pushing these exception handling issues into the service layer)?
This seems more difficult than it should be, but as with many things in Spring it's likely I don't understand what's going on. All I want to do is to send an error status and message back in the response!
Thanks,
Paul
It looks like you have the most of the answers in your question itself :)
To reiterate,
Have the controller like this
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request) throws NotFoundException {
String id = request.getParameter("id");
if (id == null)
throw new NotFoundException("Id not found in the request");
return "success";
}
Declare the exception class in NotFoundException.java,
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Id Not Found")
public class NotFoundException extends Exception {
public NotFoundException(String msg) {
super(msg);
}
}
This exception class need not be every controller class. Declare it as public class and import it in every required controller.
This is one way of doing it. If you like the non-spring style, declare HttpServletResponse in every controller arguments and do
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request, HttpServletResponse response) {
...
try {
response.sendError(..)
catch(..) {}
}
Or you can use views to show error message,
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request, Map<String, Object> map){
String id = request.getParameter("id");
if (id == null) {
map.put("status", HttpStatus.NOTFOUND);
map.put("reason", "Id Not Found");
return "error"
}
return "success";
}
Make sure your viewResolver is configured correctly and in the error.jsp to get the error string, you could say.
<body>
${status} ${reason}
</body>
Define error.jsp with nice css for all kind of errors you would expect.
These are not the only ways. With spring you have freedom to do anything. I have seen few ppl rendering json object for error message.
To answer your another question of if the error happens in the service called by the controller is depend on your scenario. For example you are trying to read the user store, if the user store not available error happens, I would handle there itself to read from another replica user store if one available and If I found user does not exist I would leave the exception to the controller to throw.

Resources