better exception handling for runtime exceptions in the server handler

This commit is contained in:
Jörg Prante 2020-09-10 19:08:59 +02:00
parent 57af07c950
commit 453fc20615
5 changed files with 85 additions and 11 deletions

View file

@ -1,6 +1,6 @@
group = org.xbib
name = netty-http
version = 4.1.52.0
version = 4.1.52.1
gradle.wrapper.version = 6.4.1
netty.version = 4.1.52.Final

View file

@ -19,4 +19,6 @@ public interface Domain<R extends EndpointResolver<?>> {
Collection<? extends X509Certificate> getCertificateChain();
void handle(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder) throws IOException;
void handleAfterError(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder, Throwable throwable);
}

View file

@ -21,6 +21,7 @@ import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
import org.xbib.netty.http.server.api.Filter;
import org.xbib.netty.http.server.security.CertificateUtils;
import org.xbib.netty.http.server.security.PrivateKeyUtils;
import org.xbib.netty.http.server.util.ExceptionFormatter;
import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
@ -40,12 +41,16 @@ import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The {@code HttpServerDomain} class represents a virtual server with a name.
*/
public class HttpServerDomain implements Domain<HttpEndpointResolver> {
private static final Logger logger = Logger.getLogger(HttpServerDomain.class.getName());
private final String name;
private final HttpAddress httpAddress;
@ -164,12 +169,20 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
} else {
if (serverResponseBuilder != null) {
serverResponseBuilder.setStatus(HttpResponseStatus.NOT_FOUND)
.setContentType("text/plain")
.setContentType("text/plain;charset=utf-8")
.build().write("no endpoint found to match request");
}
}
}
@Override
public void handleAfterError(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder, Throwable throwable) {
logger.log(Level.SEVERE, throwable.getMessage(), throwable);
serverResponseBuilder.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR)
.setContentType("text/plain;charset=utf-8")
.build().write(ExceptionFormatter.format(throwable));
}
@Override
public String toString() {
return name + " (" + httpAddress + ")";
@ -335,7 +348,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
.addEndpoint(HttpEndpoint.builder()
.setPath(path)
.build())
.setDispatcher(filter::handle)
.setDispatcher(filter)
.build());
return this;
}
@ -349,7 +362,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
.setPrefix(prefix)
.setPath(path)
.build())
.setDispatcher(filter::handle)
.setDispatcher(filter)
.build());
return this;
}
@ -365,7 +378,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
.setPath(path)
.setMethods(Arrays.asList(methods))
.build())
.setDispatcher(filter::handle)
.setDispatcher(filter)
.build());
return this;
}

View file

@ -207,9 +207,9 @@ public final class Server implements AutoCloseable {
}
public URL getBaseURL(HttpHeaders headers) {
String scheme = null;
String host = null;
String port = null;
String scheme;
String host;
String port;
if (headers == null) {
URL bindURL = serverConfig.getDefaultDomain().getHttpAddress().base();
scheme = bindURL.getScheme();
@ -258,7 +258,7 @@ public final class Server implements AutoCloseable {
}
public void handle(ServerRequest.Builder serverRequestBuilder,
ServerResponse.Builder serverResponseBuilder) throws IOException {
ServerResponse.Builder serverResponseBuilder) {
URL baseURL = getBaseURL(serverRequestBuilder.getHeaders());
serverRequestBuilder.setBaseURL(baseURL);
Domain<? extends EndpointResolver<?>> domain = getDomain(baseURL);
@ -266,8 +266,9 @@ public final class Server implements AutoCloseable {
executor.submit(() -> {
try {
domain.handle(serverRequestBuilder, serverResponseBuilder);
} catch (IOException e) {
executor.afterExecute(null, e);
} catch (Throwable t) {
executor.afterExecute(null, t);
domain.handleAfterError(serverRequestBuilder, serverResponseBuilder, t);
} finally {
serverRequestBuilder.release();
}
@ -275,6 +276,8 @@ public final class Server implements AutoCloseable {
} else {
try {
domain.handle(serverRequestBuilder, serverResponseBuilder);
} catch (Throwable t) {
domain.handleAfterError(serverRequestBuilder, serverResponseBuilder, t);
} finally {
serverRequestBuilder.release();
}

View file

@ -0,0 +1,56 @@
package org.xbib.netty.http.server.util;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Format exception messages and stack traces.
*/
public final class ExceptionFormatter {
private ExceptionFormatter() {
}
/**
* Format exception with stack trace.
*
* @param t the thrown object
* @return the formatted exception
*/
public static String format(Throwable t) {
StringBuilder sb = new StringBuilder();
append(sb, t, 0, true);
return sb.toString();
}
/**
* Append Exception to string builder.
*/
private static void append(StringBuilder sb, Throwable t, int level, boolean details) {
if (((t != null) && (t.getMessage() != null)) && (!t.getMessage().isEmpty())) {
if (details && (level > 0)) {
sb.append("\n\nCaused by\n");
}
sb.append(t.getMessage());
}
if (details) {
if (t != null) {
if ((t.getMessage() != null) && (t.getMessage().isEmpty())) {
sb.append("\n\nCaused by ");
} else {
sb.append("\n\n");
}
}
StringWriter sw = new StringWriter();
if (t != null) {
t.printStackTrace(new PrintWriter(sw));
}
sb.append(sw.toString());
}
if (t != null) {
if (t.getCause() != null) {
append(sb, t.getCause(), level + 1, details);
}
}
}
}