Skip to content

Commit

Permalink
#81 Customizable error handling for "404 not found" scenarios.
Browse files Browse the repository at this point in the history
  • Loading branch information
nmihajlovski committed Aug 3, 2016
1 parent 619c96c commit f493e64
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 140 deletions.
50 changes: 0 additions & 50 deletions rapidoid-commons/src/main/java/org/rapidoid/util/Msc.java
Expand Up @@ -28,8 +28,6 @@
import org.rapidoid.validation.InvalidData;
import org.rapidoid.wrap.BoolWrap;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.management.ManagementFactory;
Expand Down Expand Up @@ -793,54 +791,6 @@ public static boolean isValidationError(Throwable error) {
return (error instanceof InvalidData) || error.getClass().getName().startsWith("javax.validation.");
}

public static ErrCodeAndMsg getErrorCodeAndMsg(Throwable err) {
Throwable cause = rootCause(err);

int code;
String defaultMsg;
String msg = cause.getMessage();

if (cause instanceof SecurityException) {
code = 403;
defaultMsg = "Access Denied!";

} else if (Msc.isValidationError(cause)) {
code = 422;
defaultMsg = "Validation Error!";

if (cause.getClass().getName().equals("javax.validation.ConstraintViolationException")) {
Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) cause).getConstraintViolations();

StringBuilder sb = new StringBuilder();
sb.append("Validation failed: ");

for (Iterator<ConstraintViolation<?>> it = U.safe(violations).iterator(); it.hasNext(); ) {
ConstraintViolation<?> v = it.next();

sb.append(v.getRootBeanClass().getSimpleName());
sb.append(".");
sb.append(v.getPropertyPath());
sb.append(" (");
sb.append(v.getMessage());
sb.append(")");

if (it.hasNext()) {
sb.append(", ");
}
}

msg = sb.toString();
}

} else {
code = 500;
defaultMsg = "Internal Server Error!";
}

msg = U.or(msg, defaultMsg);
return new ErrCodeAndMsg(code, msg);
}

public static <T> List<T> page(Iterable<T> items, int page, int pageSize) {
return Coll.range(items, (page - 1) * pageSize, page * pageSize);
}
Expand Down
6 changes: 6 additions & 0 deletions rapidoid-http-fast/pom.xml
Expand Up @@ -47,6 +47,12 @@
<version>${hibernate.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
<optional>true</optional>
</dependency>

<!-- TEST -->

Expand Down
34 changes: 22 additions & 12 deletions rapidoid-http-fast/src/main/java/org/rapidoid/http/FastHttp.java
Expand Up @@ -11,6 +11,7 @@
import org.rapidoid.data.BufRanges;
import org.rapidoid.data.KeyValueRanges;
import org.rapidoid.http.customize.Customization;
import org.rapidoid.http.customize.JsonResponseRenderer;
import org.rapidoid.http.handler.HttpHandler;
import org.rapidoid.http.impl.*;
import org.rapidoid.http.processor.AbstractHttpProcessor;
Expand Down Expand Up @@ -51,6 +52,8 @@ public class FastHttp extends AbstractHttpProcessor {

private static final HttpParser HTTP_PARSER = new HttpParser();

private static final String INTERNAL_SERVER_ERROR = "Internal Server Error!";

private final HttpRoutesImpl[] routeGroups;

private final Map<String, Object> attributes = Coll.synchronizedMap();
Expand Down Expand Up @@ -198,7 +201,8 @@ public void onRequest(Channel channel, boolean isGet, boolean isKeepAlive, BufRa
}

if (status == HttpStatus.NOT_FOUND) {
HttpIO.write404(channel, isKeepAlive);
handleNotFound(channel, isKeepAlive, contentType, req);
return;
}

if (status != HttpStatus.ASYNC) {
Expand All @@ -214,9 +218,19 @@ private HttpStatus handleIfFound(Channel channel, boolean isKeepAlive, HttpHandl
}
}

private boolean handleError(Channel channel, boolean isKeepAlive, ReqImpl req, MediaType contentType, Throwable e) {
protected void internalServerError(Channel channel, boolean isKeepAlive, Req req, MediaType contentType) {
HttpIO.startResponse(channel, 500, isKeepAlive, contentType);

JsonResponseRenderer jsonRenderer = Customization.of(req).jsonResponseRenderer();
byte[] bytes = HttpUtils.responseToBytes(req, INTERNAL_SERVER_ERROR, contentType, jsonRenderer);

HttpIO.writeContentLengthAndBody(channel, bytes);
HttpIO.done(channel, isKeepAlive);
}

private boolean handleError(Channel channel, boolean isKeepAlive, Req req, MediaType contentType, Throwable e) {
if (req != null) {
if (!req.isStopped()) {
if (!((ReqImpl) req).isStopped()) {
try {
HttpIO.errorAndDone(req, e);
} catch (Exception e1) {
Expand All @@ -234,14 +248,11 @@ private boolean handleError(Channel channel, boolean isKeepAlive, ReqImpl req, M
return false;
}

protected void internalServerError(Channel channel, boolean isKeepAlive, ReqImpl req, MediaType contentType) {
HttpIO.startResponse(channel, 500, isKeepAlive, contentType);
byte[] bytes = HttpUtils.responseToBytes(req, "Internal Server Error!", contentType, custom().jsonResponseRenderer());
HttpIO.writeContentLengthAndBody(channel, bytes);
HttpIO.done(channel, isKeepAlive);
private void handleNotFound(Channel channel, boolean isKeepAlive, MediaType contentType, Req req) {
handleError(channel, isKeepAlive, req, contentType, new NotFound());
}

private Customization custom() {
public Customization custom() {
return routes()[0].custom();
}

Expand Down Expand Up @@ -284,7 +295,7 @@ public synchronized void resetConfig() {
}
}

public void notFound(Channel ctx, boolean isKeepAlive, HttpHandler fromHandler, Req req) {
public void notFound(Channel ctx, boolean isKeepAlive, MediaType contentType, HttpHandler fromHandler, Req req) {
HttpStatus status = HttpStatus.NOT_FOUND;

tryRoutes:
Expand All @@ -311,8 +322,7 @@ public void notFound(Channel ctx, boolean isKeepAlive, HttpHandler fromHandler,
}

if (status == HttpStatus.NOT_FOUND) {
HttpIO.write404(ctx, isKeepAlive);
HttpIO.done(ctx, isKeepAlive);
handleNotFound(ctx, isKeepAlive, contentType, req);
}
}

Expand Down
Expand Up @@ -18,12 +18,17 @@
import org.rapidoid.util.ErrCodeAndMsg;
import org.rapidoid.util.Msc;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.Serializable;
import java.nio.BufferOverflowException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;

/*
* #%L
Expand Down Expand Up @@ -179,8 +184,60 @@ public static Res staticPage(Req req, String... possibleLocations) {
}
}

public static ErrCodeAndMsg getErrorCodeAndMsg(Throwable err) {
Throwable cause = Msc.rootCause(err);

int code;
String defaultMsg;
String msg = cause.getMessage();

if (cause instanceof SecurityException) {
code = 403;
defaultMsg = "Access Denied!";

} else if (cause instanceof NotFound) {
code = 404;
defaultMsg = "The requested resource could not be found!";

} else if (Msc.isValidationError(cause)) {
code = 422;
defaultMsg = "Validation Error!";

if (cause.getClass().getName().equals("javax.validation.ConstraintViolationException")) {
Set<ConstraintViolation<?>> violations = ((ConstraintViolationException) cause).getConstraintViolations();

StringBuilder sb = new StringBuilder();
sb.append("Validation failed: ");

for (Iterator<ConstraintViolation<?>> it = U.safe(violations).iterator(); it.hasNext(); ) {
ConstraintViolation<?> v = it.next();

sb.append(v.getRootBeanClass().getSimpleName());
sb.append(".");
sb.append(v.getPropertyPath());
sb.append(" (");
sb.append(v.getMessage());
sb.append(")");

if (it.hasNext()) {
sb.append(", ");
}
}

msg = sb.toString();
}

} else {
code = 500;
defaultMsg = "Internal Server Error!";
}

msg = U.or(msg, defaultMsg);
return new ErrCodeAndMsg(code, msg);
}

public static String getErrorMessageAndSetCode(Resp resp, Throwable err) {
ErrCodeAndMsg codeAndMsg = Msc.getErrorCodeAndMsg(err);
ErrCodeAndMsg codeAndMsg = getErrorCodeAndMsg(err);
resp.code(codeAndMsg.code());
return codeAndMsg.msg();
}
Expand Down Expand Up @@ -285,4 +342,21 @@ public static String getContextPath(Customization customization, String segment)

return cfg.entry("contextPath").or("/");
}

@SuppressWarnings("unchecked")
public static Object postprocessResult(Req req, Object result) throws Exception {
if (result instanceof Req || result instanceof Resp || result instanceof HttpStatus) {
return result;

} else if (result == null) {
return null; // not found

} else if ((result instanceof Future<?>) || (result instanceof org.rapidoid.concurrent.Future<?>)) {
return req.async();

} else {
return result;
}
}

}
Expand Up @@ -6,13 +6,15 @@
import org.rapidoid.commons.MediaType;
import org.rapidoid.config.Config;
import org.rapidoid.http.HttpUtils;
import org.rapidoid.http.NotFound;
import org.rapidoid.http.Req;
import org.rapidoid.http.Resp;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;
import org.rapidoid.util.Msc;
import org.rapidoid.value.Value;

import java.util.Collections;
import java.util.Map;

/*
Expand Down Expand Up @@ -77,12 +79,14 @@ protected Object handleError(Req req, Resp resp, Throwable error, Customization

try {

Object result = null;

if (handler != null) {
return handler.handleError(req, resp, err);
} else {
return defaultErrorHandling(req, err);
result = handler.handleError(req, resp, err);
}

return result != null ? result : defaultErrorHandling(req, err);

} catch (Exception e) {

if (i >= getMaxReThrowCount(req)) {
Expand All @@ -99,6 +103,17 @@ protected int getMaxReThrowCount(@SuppressWarnings("UnusedParameters") Req req)
}

protected Object defaultErrorHandling(Req req, Throwable error) {

if (error instanceof NotFound) {
Resp resp = req.response().code(404);

if (resp.contentType() == MediaType.JSON_UTF_8) {
return error;
} else {
return resp.view("404").result(Collections.emptyMap());
}
}

boolean validation = Msc.isValidationError(error);

if (!validation) {
Expand Down

0 comments on commit f493e64

Please sign in to comment.