Add throwable cause of REST API error to request
When an exception is thrown during processing of a REST API request, the exception is converted to a simplified error message. The full contextual detail of the exception may be useful for troubleshooting. This patchset modifies exception handling routines in the REST API to store the exception in the request, where it may be accessed by a debugger or filter. Change-Id: I6495d3fb72b755b91b77f05de247b2dd03a8fe2c
This commit is contained in:
@@ -18,6 +18,18 @@ import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Utilities for manipulating HTTP request objects. */
|
||||
public class RequestUtil {
|
||||
/** HTTP request attribute for storing the Throwable that caused an error condition. */
|
||||
private static final String ATTRIBUTE_ERROR_TRACE =
|
||||
RequestUtil.class.getName() + "/ErrorTraceThrowable";
|
||||
|
||||
public static void setErrorTraceAttribute(HttpServletRequest req, Throwable t) {
|
||||
req.setAttribute(ATTRIBUTE_ERROR_TRACE, t);
|
||||
}
|
||||
|
||||
public static Throwable getErrorTraceAttribute(HttpServletRequest req) {
|
||||
return (Throwable) req.getAttribute(ATTRIBUTE_ERROR_TRACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the same value as {@link HttpServletRequest#getPathInfo()}, but
|
||||
* without decoding URL-encoded characters.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.httpd;
|
||||
|
||||
import static com.google.gerrit.httpd.restapi.RestApiServlet.replyError;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||
|
||||
@@ -77,17 +78,19 @@ class RunAsFilter implements Filter {
|
||||
String runas = req.getHeader(RUN_AS);
|
||||
if (runas != null) {
|
||||
if (!enabled) {
|
||||
RestApiServlet.replyError(req, res,
|
||||
replyError(req, res,
|
||||
SC_FORBIDDEN,
|
||||
RUN_AS + " disabled by auth.enableRunAs = false");
|
||||
RUN_AS + " disabled by auth.enableRunAs = false",
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentUser self = session.get().getCurrentUser();
|
||||
if (!self.getCapabilities().canRunAs()) {
|
||||
RestApiServlet.replyError(req, res,
|
||||
replyError(req, res,
|
||||
SC_FORBIDDEN,
|
||||
"not permitted to use " + RUN_AS);
|
||||
"not permitted to use " + RUN_AS,
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,15 +99,17 @@ class RunAsFilter implements Filter {
|
||||
target = accountResolver.find(runas);
|
||||
} catch (OrmException e) {
|
||||
log.warn("cannot resolve account for " + RUN_AS, e);
|
||||
RestApiServlet.replyError(req, res,
|
||||
replyError(req, res,
|
||||
SC_INTERNAL_SERVER_ERROR,
|
||||
"cannot resolve " + RUN_AS);
|
||||
"cannot resolve " + RUN_AS,
|
||||
e);
|
||||
return;
|
||||
}
|
||||
if (target == null) {
|
||||
RestApiServlet.replyError(req, res,
|
||||
replyError(req, res,
|
||||
SC_FORBIDDEN,
|
||||
"no account matches " + RUN_AS);
|
||||
"no account matches " + RUN_AS,
|
||||
null);
|
||||
return;
|
||||
}
|
||||
session.get().setUserAccountId(target.getId());
|
||||
|
||||
@@ -68,7 +68,7 @@ class ParameterParser {
|
||||
clp.parseOptionMap(in);
|
||||
} catch (CmdLineException e) {
|
||||
if (!clp.wasHelpRequestedByOption()) {
|
||||
replyError(req, res, SC_BAD_REQUEST, e.getMessage());
|
||||
replyError(req, res, SC_BAD_REQUEST, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,27 +345,27 @@ public class RestApiServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
} catch (AuthException e) {
|
||||
replyError(req, res, status = SC_FORBIDDEN, e.getMessage(), e.caching());
|
||||
replyError(req, res, status = SC_FORBIDDEN, e.getMessage(), e.caching(), e);
|
||||
} catch (BadRequestException e) {
|
||||
replyError(req, res, status = SC_BAD_REQUEST, e.getMessage(), e.caching());
|
||||
replyError(req, res, status = SC_BAD_REQUEST, e.getMessage(), e.caching(), e);
|
||||
} catch (MethodNotAllowedException e) {
|
||||
replyError(req, res, status = SC_METHOD_NOT_ALLOWED, "Method not allowed", e.caching());
|
||||
replyError(req, res, status = SC_METHOD_NOT_ALLOWED, "Method not allowed", e.caching(), e);
|
||||
} catch (ResourceConflictException e) {
|
||||
replyError(req, res, status = SC_CONFLICT, e.getMessage(), e.caching());
|
||||
replyError(req, res, status = SC_CONFLICT, e.getMessage(), e.caching(), e);
|
||||
} catch (PreconditionFailedException e) {
|
||||
replyError(req, res, status = SC_PRECONDITION_FAILED,
|
||||
MoreObjects.firstNonNull(e.getMessage(), "Precondition failed"), e.caching());
|
||||
MoreObjects.firstNonNull(e.getMessage(), "Precondition failed"), e.caching(), e);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
replyError(req, res, status = SC_NOT_FOUND, "Not found", e.caching());
|
||||
replyError(req, res, status = SC_NOT_FOUND, "Not found", e.caching(), e);
|
||||
} catch (UnprocessableEntityException e) {
|
||||
replyError(req, res, status = 422,
|
||||
MoreObjects.firstNonNull(e.getMessage(), "Unprocessable Entity"), e.caching());
|
||||
MoreObjects.firstNonNull(e.getMessage(), "Unprocessable Entity"), e.caching(), e);
|
||||
} catch (AmbiguousViewException e) {
|
||||
replyError(req, res, status = SC_NOT_FOUND, e.getMessage());
|
||||
replyError(req, res, status = SC_NOT_FOUND, e.getMessage(), e);
|
||||
} catch (MalformedJsonException e) {
|
||||
replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
|
||||
replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e);
|
||||
} catch (JsonParseException e) {
|
||||
replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request");
|
||||
replyError(req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e);
|
||||
} catch (Exception e) {
|
||||
status = SC_INTERNAL_SERVER_ERROR;
|
||||
handleException(e, req, res);
|
||||
@@ -928,21 +928,24 @@ public class RestApiServlet extends HttpServlet {
|
||||
|
||||
if (!res.isCommitted()) {
|
||||
res.reset();
|
||||
replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error");
|
||||
replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
|
||||
}
|
||||
}
|
||||
|
||||
public static void replyError(HttpServletRequest req,
|
||||
HttpServletResponse res, int statusCode, String msg) throws IOException {
|
||||
replyError(req, res, statusCode, msg, CacheControl.NONE);
|
||||
public static void replyError(HttpServletRequest req, HttpServletResponse res,
|
||||
int statusCode, String msg, @Nullable Throwable err) throws IOException {
|
||||
replyError(req, res, statusCode, msg, CacheControl.NONE, err);
|
||||
}
|
||||
|
||||
public static void replyError(HttpServletRequest req,
|
||||
HttpServletResponse res, int statusCode, String msg,
|
||||
CacheControl c) throws IOException {
|
||||
CacheControl c, @Nullable Throwable err) throws IOException {
|
||||
res.setStatus(statusCode);
|
||||
configureCaching(req, res, null, c);
|
||||
replyText(req, res, msg);
|
||||
if (err != null) {
|
||||
RequestUtil.setErrorTraceAttribute(req, err);
|
||||
}
|
||||
}
|
||||
|
||||
static void replyText(@Nullable HttpServletRequest req,
|
||||
|
||||
Reference in New Issue
Block a user