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 group = org.xbib
name = netty-http name = netty-http
version = 4.1.52.0 version = 4.1.52.1
gradle.wrapper.version = 6.4.1 gradle.wrapper.version = 6.4.1
netty.version = 4.1.52.Final netty.version = 4.1.52.Final

View file

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

View file

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