better exception handling for runtime exceptions in the server handler
This commit is contained in:
parent
57af07c950
commit
453fc20615
5 changed files with 85 additions and 11 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue