diff --git a/gradle.properties b/gradle.properties index ba0d50b..6496f1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java index 1d83b23..c6b3e8f 100644 --- a/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java +++ b/netty-http-server-api/src/main/java/org/xbib/netty/http/server/api/Domain.java @@ -19,4 +19,6 @@ public interface Domain> { Collection getCertificateChain(); void handle(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder) throws IOException; + + void handleAfterError(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder, Throwable throwable); } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java index 720b05b..ec3d829 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/HttpServerDomain.java @@ -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 { + 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 { } 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 { .addEndpoint(HttpEndpoint.builder() .setPath(path) .build()) - .setDispatcher(filter::handle) + .setDispatcher(filter) .build()); return this; } @@ -349,7 +362,7 @@ public class HttpServerDomain implements Domain { .setPrefix(prefix) .setPath(path) .build()) - .setDispatcher(filter::handle) + .setDispatcher(filter) .build()); return this; } @@ -365,7 +378,7 @@ public class HttpServerDomain implements Domain { .setPath(path) .setMethods(Arrays.asList(methods)) .build()) - .setDispatcher(filter::handle) + .setDispatcher(filter) .build()); return this; } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java index 0a47932..ac71400 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java @@ -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> 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(); } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/util/ExceptionFormatter.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/util/ExceptionFormatter.java new file mode 100644 index 0000000..cd2a5d2 --- /dev/null +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/util/ExceptionFormatter.java @@ -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); + } + } + } +}