Skip to content

Commit b1a553e

Browse files
committed
Add more specific HTTP exceptions + default logger with mute support
1 parent da24282 commit b1a553e

File tree

49 files changed

+534
-146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+534
-146
lines changed

examples/src/main/java/examples/PlainText.java

Lines changed: 0 additions & 31 deletions
This file was deleted.

jooby/src/main/java/io/jooby/Context.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,8 @@ public interface Context extends Registry {
866866
*/
867867
@Nonnull Context setResponseLength(long length);
868868

869+
@Nullable String getResponseHeader(@Nonnull String name);
870+
869871
/**
870872
* Get response content length or <code>-1</code> when none was set.
871873
*

jooby/src/main/java/io/jooby/DefaultContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.exception.RegistryException;
9+
import io.jooby.exception.TypeMismatchException;
810
import io.jooby.internal.HashValue;
911
import io.jooby.internal.MissingValue;
1012
import io.jooby.internal.SingleValue;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package io.jooby;
2+
3+
import org.slf4j.Logger;
4+
5+
import javax.annotation.Nonnull;
6+
import java.util.Arrays;
7+
import java.util.HashSet;
8+
import java.util.Optional;
9+
import java.util.Set;
10+
import java.util.stream.Stream;
11+
12+
import static io.jooby.MediaType.html;
13+
import static io.jooby.MediaType.json;
14+
import static io.jooby.MediaType.text;
15+
16+
/**
17+
* Default error handler with content negotiation support and optionally mute log statement base
18+
* on status code or exception types.
19+
*
20+
* @author edgar
21+
* @since 2.4.1
22+
*/
23+
public class DefaultErrorHandler implements ErrorHandler {
24+
25+
private Set<StatusCode> muteCodes = new HashSet<>();
26+
27+
private Set<Class> muteTypes = new HashSet<>();
28+
29+
/**
30+
* Generate a log.debug call if any of the status code error occurs as exception.
31+
*
32+
* @param statusCodes Status codes to mute.
33+
* @return This error handler.
34+
*/
35+
public @Nonnull DefaultErrorHandler mute(@Nonnull StatusCode... statusCodes) {
36+
Stream.of(statusCodes).forEach(muteCodes::add);
37+
return this;
38+
}
39+
40+
/**
41+
* Generate a log.debug call if any of the exception types occurs.
42+
*
43+
* @param exceptionTypes Exception types to mute.
44+
* @return This error handler.
45+
*/
46+
public @Nonnull DefaultErrorHandler mute(@Nonnull Class<? extends Exception>... exceptionTypes) {
47+
Stream.of(exceptionTypes).forEach(muteTypes::add);
48+
return this;
49+
}
50+
51+
@Nonnull @Override public void apply(@Nonnull Context ctx, @Nonnull Throwable cause,
52+
@Nonnull StatusCode statusCode) {
53+
Logger log = ctx.getRouter().getLog();
54+
if (mute(cause, statusCode)) {
55+
log.debug(ErrorHandler.errorMessage(ctx, statusCode), cause);
56+
} else {
57+
log.error(ErrorHandler.errorMessage(ctx, statusCode), cause);
58+
}
59+
60+
MediaType type = ctx.accept(Arrays.asList(html, json, text));
61+
if (json.equals(type)) {
62+
String message = Optional.ofNullable(cause.getMessage()).orElse(statusCode.reason());
63+
ctx.setResponseType(json)
64+
.setResponseCode(statusCode)
65+
.send("{\"message\":\"" + XSS.json(message) + "\",\"statusCode\":" + statusCode.value()
66+
+ ",\"reason\":\"" + statusCode.reason() + "\"}");
67+
} else if (text.equals(type)) {
68+
StringBuilder message = new StringBuilder();
69+
message.append(ctx.getMethod()).append(" ").append(ctx.getRequestPath()).append(" ");
70+
message.append(statusCode.value()).append(" ").append(statusCode.reason());
71+
if (cause.getMessage() != null) {
72+
message.append("\n").append(XSS.json(cause.getMessage()));
73+
}
74+
ctx.setResponseType(text)
75+
.setResponseCode(statusCode)
76+
.send(message.toString());
77+
} else {
78+
String message = cause.getMessage();
79+
StringBuilder html = new StringBuilder("<!doctype html>\n")
80+
.append("<html>\n")
81+
.append("<head>\n")
82+
.append("<meta charset=\"utf-8\">\n")
83+
.append("<style>\n")
84+
.append("body {font-family: \"open sans\",sans-serif; margin-left: 20px;}\n")
85+
.append("h1 {font-weight: 300; line-height: 44px; margin: 25px 0 0 0;}\n")
86+
.append("h2 {font-size: 16px;font-weight: 300; line-height: 44px; margin: 0;}\n")
87+
.append("footer {font-weight: 300; line-height: 44px; margin-top: 10px;}\n")
88+
.append("hr {background-color: #f7f7f9;}\n")
89+
.append("div.trace {border:1px solid #e1e1e8; background-color: #f7f7f9;}\n")
90+
.append("p {padding-left: 20px;}\n")
91+
.append("p.tab {padding-left: 40px;}\n")
92+
.append("</style>\n")
93+
.append("<title>")
94+
.append(statusCode)
95+
.append("</title>\n")
96+
.append("<body>\n")
97+
.append("<h1>").append(statusCode.reason()).append("</h1>\n")
98+
.append("<hr>\n");
99+
100+
if (message != null && !message.equals(statusCode.toString())) {
101+
html.append("<h2>message: ").append(XSS.html(message)).append("</h2>\n");
102+
}
103+
html.append("<h2>status code: ").append(statusCode.value()).append("</h2>\n");
104+
105+
html.append("</body>\n")
106+
.append("</html>");
107+
108+
ctx
109+
.setResponseType(MediaType.html)
110+
.setResponseCode(statusCode)
111+
.send(html.toString());
112+
}
113+
}
114+
115+
private boolean mute(Throwable cause, StatusCode statusCode) {
116+
return muteCodes.contains(statusCode)
117+
// same class filter
118+
|| muteTypes.stream().anyMatch(type -> type == cause.getClass())
119+
// sub-class filter
120+
|| muteTypes.stream().anyMatch(type -> type.isInstance(cause));
121+
}
122+
}

jooby/src/main/java/io/jooby/ErrorHandler.java

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -21,68 +21,6 @@
2121
*/
2222
public interface ErrorHandler {
2323

24-
/**
25-
* Default error handler with support for content-negotiation. It renders a html error page
26-
* or json.
27-
*/
28-
ErrorHandler DEFAULT = (ctx, cause, statusCode) -> {
29-
ctx.getRouter().getLog().error(errorMessage(ctx, statusCode), cause);
30-
31-
MediaType type = ctx.accept(Arrays.asList(html, json, text));
32-
if (json.equals(type)) {
33-
String message = Optional.ofNullable(cause.getMessage()).orElse(statusCode.reason());
34-
ctx.setResponseType(json)
35-
.setResponseCode(statusCode)
36-
.send("{\"message\":\"" + XSS.json(message) + "\",\"statusCode\":" + statusCode.value()
37-
+ ",\"reason\":\"" + statusCode.reason() + "\"}");
38-
} else if (text.equals(type)) {
39-
StringBuilder message = new StringBuilder();
40-
message.append(ctx.getMethod()).append(" ").append(ctx.getRequestPath()).append(" ");
41-
message.append(statusCode.value()).append(" ").append(statusCode.reason());
42-
if (cause.getMessage() != null) {
43-
message.append("\n").append(XSS.json(cause.getMessage()));
44-
}
45-
ctx.setResponseType(text)
46-
.setResponseCode(statusCode)
47-
.send(message.toString());
48-
} else {
49-
String message = cause.getMessage();
50-
StringBuilder html = new StringBuilder("<!doctype html>\n")
51-
.append("<html>\n")
52-
.append("<head>\n")
53-
.append("<meta charset=\"utf-8\">\n")
54-
.append("<style>\n")
55-
.append("body {font-family: \"open sans\",sans-serif; margin-left: 20px;}\n")
56-
.append("h1 {font-weight: 300; line-height: 44px; margin: 25px 0 0 0;}\n")
57-
.append("h2 {font-size: 16px;font-weight: 300; line-height: 44px; margin: 0;}\n")
58-
.append("footer {font-weight: 300; line-height: 44px; margin-top: 10px;}\n")
59-
.append("hr {background-color: #f7f7f9;}\n")
60-
.append("div.trace {border:1px solid #e1e1e8; background-color: #f7f7f9;}\n")
61-
.append("p {padding-left: 20px;}\n")
62-
.append("p.tab {padding-left: 40px;}\n")
63-
.append("</style>\n")
64-
.append("<title>")
65-
.append(statusCode)
66-
.append("</title>\n")
67-
.append("<body>\n")
68-
.append("<h1>").append(statusCode.reason()).append("</h1>\n")
69-
.append("<hr>\n");
70-
71-
if (message != null && !message.equals(statusCode.toString())) {
72-
html.append("<h2>message: ").append(XSS.html(message)).append("</h2>\n");
73-
}
74-
html.append("<h2>status code: ").append(statusCode.value()).append("</h2>\n");
75-
76-
html.append("</body>\n")
77-
.append("</html>");
78-
79-
ctx
80-
.setResponseType(MediaType.html)
81-
.setResponseCode(statusCode)
82-
.send(html.toString());
83-
}
84-
};
85-
8624
/**
8725
* Produces an error response using the given exception and status code.
8826
*
@@ -128,4 +66,8 @@ public interface ErrorHandler {
12866
.append(statusCode.reason())
12967
.toString();
13068
}
69+
70+
static @Nonnull DefaultErrorHandler create() {
71+
return new DefaultErrorHandler();
72+
}
13173
}

jooby/src/main/java/io/jooby/ForwardingContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.exception.RegistryException;
9+
810
import javax.annotation.Nonnull;
911
import javax.annotation.Nullable;
1012
import java.io.InputStream;
@@ -382,6 +384,10 @@ public Context setResponseHeader(@Nonnull String name, @Nonnull Instant value) {
382384
return this;
383385
}
384386

387+
@Nullable @Override public String getResponseHeader(@Nonnull String name) {
388+
return ctx.getResponseHeader(name);
389+
}
390+
385391
@Override public long getResponseLength() {
386392
return ctx.getResponseLength();
387393
}

jooby/src/main/java/io/jooby/Jooby.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.jooby;
77

88
import com.typesafe.config.Config;
9+
import io.jooby.exception.RegistryException;
910
import io.jooby.internal.RouterImpl;
1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;

jooby/src/main/java/io/jooby/MessageDecoder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.exception.UnsupportedMediaType;
9+
810
import javax.annotation.Nonnull;
911
import java.lang.reflect.Type;
1012

@@ -21,7 +23,7 @@ public interface MessageDecoder {
2123
*/
2224
MessageDecoder UNSUPPORTED_MEDIA_TYPE = new MessageDecoder() {
2325
@Override public <T> T decode(Context ctx, Type type) {
24-
throw new StatusCodeException(StatusCode.UNSUPPORTED_MEDIA_TYPE);
26+
throw new UnsupportedMediaType(ctx.header("Content-Type").valueOrNull());
2527
}
2628
};
2729

jooby/src/main/java/io/jooby/MessageEncoder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.exception.NotAcceptableException;
9+
810
import javax.annotation.Nonnull;
911
import java.nio.charset.StandardCharsets;
1012

@@ -21,7 +23,7 @@ public interface MessageEncoder {
2123
if (ctx.accept(ctx.getResponseType())) {
2224
return value.toString().getBytes(StandardCharsets.UTF_8);
2325
}
24-
throw new StatusCodeException(StatusCode.NOT_ACCEPTABLE);
26+
throw new NotAcceptableException(ctx.header("Accept").valueOrNull());
2527
};
2628

2729
/**

jooby/src/main/java/io/jooby/Registry.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
package io.jooby;
77

8+
import io.jooby.exception.RegistryException;
9+
810
import javax.annotation.Nonnull;
911

1012
/**

0 commit comments

Comments
 (0)