Skip to content

Commit 266adb3

Browse files
committed
Whoops: pretty error pages
1 parent 325fd8b commit 266adb3

Some content is hidden

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

45 files changed

+689
-738
lines changed

jooby/pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@
167167
<optional>true</optional>
168168
</dependency>
169169

170-
171170
<!-- javax.inject -->
172171
<dependency>
173172
<groupId>javax.inject</groupId>

jooby/src/main/java/io/jooby/DefaultErrorHandler.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,30 @@ public class DefaultErrorHandler implements ErrorHandler {
5454
}
5555

5656
@Nonnull @Override public void apply(@Nonnull Context ctx, @Nonnull Throwable cause,
57-
@Nonnull StatusCode statusCode) {
57+
@Nonnull StatusCode code) {
5858
Logger log = ctx.getRouter().getLog();
59-
if (mute(cause, statusCode)) {
60-
log.debug(ErrorHandler.errorMessage(ctx, statusCode), cause);
59+
if (mute(cause, code)) {
60+
log.debug(ErrorHandler.errorMessage(ctx, code), cause);
6161
} else {
62-
log.error(ErrorHandler.errorMessage(ctx, statusCode), cause);
62+
log.error(ErrorHandler.errorMessage(ctx, code), cause);
6363
}
6464

6565
MediaType type = ctx.accept(Arrays.asList(html, json, text));
6666
if (json.equals(type)) {
67-
String message = Optional.ofNullable(cause.getMessage()).orElse(statusCode.reason());
67+
String message = Optional.ofNullable(cause.getMessage()).orElse(code.reason());
6868
ctx.setResponseType(json)
69-
.setResponseCode(statusCode)
70-
.send("{\"message\":\"" + XSS.json(message) + "\",\"statusCode\":" + statusCode.value()
71-
+ ",\"reason\":\"" + statusCode.reason() + "\"}");
69+
.setResponseCode(code)
70+
.send("{\"message\":\"" + XSS.json(message) + "\",\"statusCode\":" + code.value()
71+
+ ",\"reason\":\"" + code.reason() + "\"}");
7272
} else if (text.equals(type)) {
7373
StringBuilder message = new StringBuilder();
7474
message.append(ctx.getMethod()).append(" ").append(ctx.getRequestPath()).append(" ");
75-
message.append(statusCode.value()).append(" ").append(statusCode.reason());
75+
message.append(code.value()).append(" ").append(code.reason());
7676
if (cause.getMessage() != null) {
7777
message.append("\n").append(XSS.json(cause.getMessage()));
7878
}
7979
ctx.setResponseType(text)
80-
.setResponseCode(statusCode)
80+
.setResponseCode(code)
8181
.send(message.toString());
8282
} else {
8383
String message = cause.getMessage();
@@ -96,23 +96,23 @@ public class DefaultErrorHandler implements ErrorHandler {
9696
.append("p.tab {padding-left: 40px;}\n")
9797
.append("</style>\n")
9898
.append("<title>")
99-
.append(statusCode)
99+
.append(code)
100100
.append("</title>\n")
101101
.append("<body>\n")
102-
.append("<h1>").append(statusCode.reason()).append("</h1>\n")
102+
.append("<h1>").append(code.reason()).append("</h1>\n")
103103
.append("<hr>\n");
104104

105-
if (message != null && !message.equals(statusCode.toString())) {
105+
if (message != null && !message.equals(code.toString())) {
106106
html.append("<h2>message: ").append(XSS.html(message)).append("</h2>\n");
107107
}
108-
html.append("<h2>status code: ").append(statusCode.value()).append("</h2>\n");
108+
html.append("<h2>status code: ").append(code.value()).append("</h2>\n");
109109

110110
html.append("</body>\n")
111111
.append("</html>");
112112

113113
ctx
114114
.setResponseType(MediaType.html)
115-
.setResponseCode(statusCode)
115+
.setResponseCode(code)
116116
.send(html.toString());
117117
}
118118
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ public interface ErrorHandler {
2020
*
2121
* @param ctx Web context.
2222
* @param cause Application error.
23-
* @param statusCode Status code.
23+
* @param code Status code.
2424
*/
2525
@Nonnull void apply(@Nonnull Context ctx, @Nonnull Throwable cause,
26-
@Nonnull StatusCode statusCode);
26+
@Nonnull StatusCode code);
2727

2828
/**
2929
* Chain this error handler with next and produces a new error handler.

modules/jooby-bom/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@
315315
<version>${jooby.version}</version>
316316
<type>jar</type>
317317
</dependency>
318+
<dependency>
319+
<groupId>io.jooby</groupId>
320+
<artifactId>jooby-whoops</artifactId>
321+
<version>${jooby.version}</version>
322+
<type>jar</type>
323+
</dependency>
318324
<dependency>
319325
<groupId>io.jooby</groupId>
320326
<artifactId>jooby-maven-plugin</artifactId>

modules/jooby-whoops/NOTE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
To make a new release:
2+
3+
- https://github.com/filp/whoops/releases
4+
- This repo used 2.7.1
5+
- Unzip the release
6+
- copy to test/resources/whoops/views
7+
- Run GenerateHTML
8+
- Verify all php lines were converted (manually)
9+
- Run WhoopsTest

modules/jooby-whoops/pom.xml

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>io.jooby</groupId>
88
<artifactId>modules</artifactId>
9-
<version>2.7.3-SNAPSHOT</version>
9+
<version>2.7.4-SNAPSHOT</version>
1010
</parent>
1111

1212
<modelVersion>4.0.0</modelVersion>
@@ -26,8 +26,8 @@
2626
</dependency>
2727

2828
<dependency>
29-
<groupId>com.github.jknack</groupId>
30-
<artifactId>handlebars</artifactId>
29+
<groupId>io.pebbletemplates</groupId>
30+
<artifactId>pebble</artifactId>
3131
<optional>true</optional>
3232
</dependency>
3333

@@ -91,20 +91,18 @@
9191
<minimizeJar>true</minimizeJar>
9292
<artifactSet>
9393
<includes>
94-
<include>com.github.jknack:handlebars</include>
94+
<include>io.pebbletemplates:pebble</include>
95+
<include>org.unbescape:unbescape</include>
9596
</includes>
9697
</artifactSet>
97-
<filters>
98-
<filter>
99-
<excludes>
100-
<exclude>*.js</exclude>
101-
</excludes>
102-
</filter>
103-
</filters>
10498
<relocations>
10599
<relocation>
106-
<pattern>com.github.jknack.handlebars</pattern>
107-
<shadedPattern>io.jooby.internal.hbs</shadedPattern>
100+
<pattern>com.mitchellbosecke.pebble</pattern>
101+
<shadedPattern>io.jooby.internal.pebble</shadedPattern>
102+
</relocation>
103+
<relocation>
104+
<pattern>org.unbescape</pattern>
105+
<shadedPattern>io.jooby.internal.pebble.unbescape</shadedPattern>
108106
</relocation>
109107
</relocations>
110108
</configuration>

modules/jooby-whoops/src/main/java/io/jooby/internal/whoops/Frame.java

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
16
package io.jooby.internal.whoops;
27

8+
import java.io.File;
39
import java.nio.file.Files;
4-
import java.util.ArrayList;
5-
import java.util.Arrays;
10+
import java.util.Collections;
11+
import java.util.LinkedList;
612
import java.util.List;
713
import java.util.Optional;
814
import java.util.stream.Collectors;
15+
import java.util.stream.Stream;
16+
17+
import static java.util.Optional.ofNullable;
918

1019
public class Frame {
1120
private static final int SAMPLE_SIZE = 10;
@@ -14,21 +23,22 @@ public class Frame {
1423

1524
private String methodName;
1625

17-
private int lineNumber;
18-
1926
private int lineStart;
2027

21-
private int lineNth;
28+
private int line;
2229

2330
private String location;
2431

2532
private String source;
2633

27-
private String open;
34+
private boolean open;
2835

2936
private String className;
3037

31-
private List<FrameComment> comments;
38+
private List<Throwable> comments;
39+
40+
private Frame() {
41+
}
3242

3343
public String getFileName() {
3444
return fileName;
@@ -38,16 +48,12 @@ public String getMethodName() {
3848
return methodName;
3949
}
4050

41-
public int getLineNumber() {
42-
return lineNumber;
43-
}
44-
4551
public int getLineStart() {
4652
return lineStart;
4753
}
4854

49-
public int getLineNth() {
50-
return lineNth;
55+
public int getLine() {
56+
return line;
5157
}
5258

5359
public String getLocation() {
@@ -58,15 +64,15 @@ public String getSource() {
5864
return source;
5965
}
6066

61-
public String getOpen() {
67+
public boolean isOpen() {
6268
return open;
6369
}
6470

6571
public String getClassName() {
6672
return className;
6773
}
6874

69-
public List<FrameComment> getComments() {
75+
public List<Throwable> getComments() {
7076
return comments;
7177
}
7278

@@ -75,63 +81,51 @@ public boolean hasSource() {
7581
}
7682

7783
public static List<Frame> toFrames(SourceLocator locator, Throwable cause) {
78-
List<Throwable> causal = getCausalChain(cause);
79-
Throwable head = causal.get(causal.size() - 1);
80-
List<Frame> frames = causal.stream()
84+
LinkedList<Throwable> causalChain = getCausalChain(cause);
85+
Throwable head = causalChain.getLast();
86+
List<Frame> frames = causalChain.stream()
8187
.filter(it -> it != head)
8288
.map(it -> toFrame(locator, it, it.getStackTrace()[0]))
8389
.collect(Collectors.toList());
8490

85-
frames.addAll(frames(locator, head));
91+
Stream.of(head.getStackTrace())
92+
.map(e -> toFrame(locator, head, e))
93+
.forEach(frames::add);
8694

87-
// truncate frames
95+
// Keep application frames (ignore all others)
8896
return frames.stream()
8997
.filter(Frame::hasSource)
9098
.collect(Collectors.toList());
9199
}
92100

93-
private static List<Frame> frames(final SourceLocator locator, final Throwable cause) {
94-
List<StackTraceElement> stacktrace = Arrays.asList(cause.getStackTrace());
95-
// int limit = IntStream.range(0, stacktrace.size())
96-
// .filter(i -> stacktrace.get(i).getClassName().equals(HANDLER)).findFirst()
97-
// .orElse(stacktrace.size());
98-
return stacktrace.stream()
99-
// trunk stack at HttpHandlerImpl (skip servers stack)
100-
// .limit(limit)
101-
.map(e -> toFrame(locator, cause, e))
102-
.collect(Collectors.toList());
103-
}
104-
105-
static Frame toFrame(final SourceLocator locator,
106-
final Throwable cause, final StackTraceElement e) {
101+
static Frame toFrame(final SourceLocator locator, final Throwable cause,
102+
final StackTraceElement e) {
107103
int line = Math.max(e.getLineNumber(), 1);
108-
String className = e.getClassName();
109-
SourceLocator.Source source = locator.source(className);
110-
int[] range = source.range(line, SAMPLE_SIZE);
111-
int lineStart = range[0];
112-
int lineNth = line - lineStart;
113-
// Path filePath = source.getPath();
114-
Optional<Class> clazz = locator.findClass(className);
115-
String filename = Optional.ofNullable(e.getFileName()).orElse("~unknown");
104+
String className = ofNullable(e.getClassName()).orElse("~unknown");
105+
String filename = ofNullable(e.getFileName()).orElse(className.replace(".", File.separator));
106+
107+
SourceLocator.Source source = locator.source(filename, line, SAMPLE_SIZE);
108+
SourceLocator.Preview preview = source.preview(line, SAMPLE_SIZE);
109+
116110
Frame frame = new Frame();
117111
frame.fileName = filename;
118-
frame.methodName = Optional.ofNullable(e.getMethodName()).orElse("~unknown");
119-
frame.lineNumber = line;
120-
frame.lineStart = lineStart + 1;
121-
frame.lineNth = lineNth;
112+
frame.methodName = ofNullable(e.getMethodName()).orElse("~unknown");
113+
frame.lineStart = preview.getLineStart();
114+
frame.line = line;
122115
frame.location = Files.exists(source.getPath())
123116
? locator.getBasedir().relativize(source.getPath()).toString()
124117
: filename;
125-
frame.source = source.source(range[0], range[1]);
126-
frame.open = "";
127-
frame.className = clazz.map(Class::getName).orElse("~unknown");
128-
frame.comments = Arrays.asList(new FrameComment(cause.getClass().getName(),
129-
Optional.ofNullable(cause.getMessage()).orElse("")));
118+
frame.source = preview.getCode();
119+
frame.open = false;
120+
frame.className = className
121+
// clean up kotlin generated class name: App$1$1 => App
122+
.replaceAll("\\$\\d+", "");
123+
frame.comments = Collections.singletonList(cause);
130124
return frame;
131125
}
132126

133-
private static List<Throwable> getCausalChain(Throwable throwable) {
134-
List<Throwable> causes = new ArrayList<>(4);
127+
private static LinkedList<Throwable> getCausalChain(Throwable throwable) {
128+
LinkedList<Throwable> causes = new LinkedList<>();
135129
causes.add(throwable);
136130

137131
// Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches

modules/jooby-whoops/src/main/java/io/jooby/internal/whoops/FrameComment.java

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

0 commit comments

Comments
 (0)