| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sling.servlets.resolver.internal.defaults; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| |
| import javax.json.Json; |
| import javax.json.stream.JsonGenerator; |
| import javax.servlet.GenericServlet; |
| import javax.servlet.Servlet; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.sling.api.SlingConstants; |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.request.header.MediaRangeList; |
| import org.apache.sling.api.request.RequestProgressTracker; |
| import org.apache.sling.api.request.ResponseUtil; |
| import org.osgi.framework.Constants; |
| import org.osgi.service.component.annotations.Component; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The <code>DefaultErrorHandlerServlet</code> |
| * |
| * This is the default error handler servlet registered at the end of the |
| * global search path |
| */ |
| @SuppressWarnings("serial") |
| @Component(service = Servlet.class, |
| property = { |
| Constants.SERVICE_VENDOR + "=The Apache Software Foundation", |
| "sling.servlet.paths=sling/servlet/errorhandler/default", |
| "sling.servlet.prefix=-1" |
| }) |
| public class DefaultErrorHandlerServlet extends GenericServlet { |
| private static final String JSON_CONTENT_TYPE = "application/json"; |
| private static final String HTML_CONTENT_TYPE = "text/html"; |
| |
| /** default log */ |
| private final Logger log = LoggerFactory.getLogger(DefaultErrorHandlerServlet.class); |
| |
| @Override |
| public void service(ServletRequest req, ServletResponse res) |
| throws IOException { |
| |
| // get settings |
| Integer scObject = (Integer) req.getAttribute(SlingConstants.ERROR_STATUS); |
| String statusMessage = (String) req.getAttribute(SlingConstants.ERROR_MESSAGE); |
| String requestUri = (String) req.getAttribute(SlingConstants.ERROR_REQUEST_URI); |
| String servletName = (String) req.getAttribute(SlingConstants.ERROR_SERVLET_NAME); |
| |
| // ensure values |
| int statusCode = (scObject != null) |
| ? scObject.intValue() |
| : HttpServletResponse.SC_INTERNAL_SERVER_ERROR; |
| if (statusMessage == null) { |
| statusMessage = statusToString(statusCode); |
| } |
| |
| //properly consider the 'Accept' header conditions to decide whether to send json or html back |
| if (req instanceof HttpServletRequest && |
| JSON_CONTENT_TYPE.equals(new MediaRangeList((HttpServletRequest)req).prefer(HTML_CONTENT_TYPE, JSON_CONTENT_TYPE))) { |
| renderJson(req, res, statusMessage, requestUri, servletName, statusCode); |
| } else { |
| //default to HTML rendering |
| renderHtml(req, res, statusMessage, requestUri, servletName, statusCode); |
| } |
| } |
| |
| /** |
| * Render the error as html |
| */ |
| protected void renderHtml(ServletRequest req, ServletResponse res, String statusMessage, String requestUri, |
| String servletName, int statusCode) throws IOException { |
| // start the response message |
| final PrintWriter pw = sendIntro((HttpServletResponse) res, statusCode, |
| statusMessage, requestUri, servletName); |
| |
| // write the exception message |
| final PrintWriter escapingWriter = new PrintWriter( |
| ResponseUtil.getXmlEscapingWriter(pw)); |
| |
| // dump the stack trace |
| if (req.getAttribute(SlingConstants.ERROR_EXCEPTION) instanceof Throwable) { |
| final Throwable throwable = (Throwable) req.getAttribute(SlingConstants.ERROR_EXCEPTION); |
| pw.println("<h3>Exception:</h3>"); |
| pw.println("<pre>"); |
| pw.flush(); |
| printStackTrace(escapingWriter, throwable); |
| escapingWriter.flush(); |
| pw.println("</pre>"); |
| } |
| |
| // dump the request progress tracker |
| if (req instanceof SlingHttpServletRequest) { |
| final RequestProgressTracker tracker = ((SlingHttpServletRequest) req).getRequestProgressTracker(); |
| pw.println("<h3>Request Progress:</h3>"); |
| pw.println("<pre>"); |
| pw.flush(); |
| tracker.dump(escapingWriter); |
| escapingWriter.flush(); |
| pw.println("</pre>"); |
| } |
| |
| // conclude the response message |
| sendEpilogue(pw); |
| } |
| |
| /** |
| * Render the error as json |
| */ |
| protected void renderJson(ServletRequest req, ServletResponse res, String statusMessage, String requestUri, |
| String servletName, int statusCode) throws IOException { |
| HttpServletResponse response = (HttpServletResponse)res; |
| if (!response.isCommitted()) { |
| response.reset(); |
| response.setStatus(statusCode); |
| response.setContentType(JSON_CONTENT_TYPE); |
| response.setCharacterEncoding("UTF-8"); |
| } else { |
| // Response already committed: don't change status, but report |
| // the error inline and warn about that |
| log.warn("Response already committed, unable to change status, output might not be well formed"); |
| } |
| |
| // send the error as JSON |
| try (JsonGenerator jsonGenerator = Json.createGenerator(res.getWriter())) { |
| jsonGenerator.writeStartObject(); |
| jsonGenerator.write("status", statusCode); |
| |
| String msg = (String)req.getAttribute(SlingConstants.ERROR_MESSAGE); |
| if (msg != null && !msg.isEmpty()) { |
| jsonGenerator.write("message", statusMessage); |
| } |
| |
| if (requestUri != null && !requestUri.isEmpty()) { |
| jsonGenerator.write("requestUri", requestUri); |
| } |
| |
| if (servletName != null && !servletName.isEmpty()) { |
| jsonGenerator.write("servletName", servletName); |
| } |
| |
| String exceptionType = (String)req.getAttribute(SlingConstants.ERROR_EXCEPTION_TYPE); |
| if (exceptionType != null && !exceptionType.isEmpty()) { |
| jsonGenerator.write("exceptionType", exceptionType); |
| } |
| |
| // dump the stack trace |
| if (req.getAttribute(SlingConstants.ERROR_EXCEPTION) instanceof Throwable) { |
| final Throwable throwable = (Throwable) req.getAttribute(SlingConstants.ERROR_EXCEPTION); |
| try (StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw)) { |
| printStackTrace(pw, throwable); |
| jsonGenerator.write("exception", sw.toString()); |
| } |
| } |
| |
| // dump the request progress tracker |
| if (req instanceof SlingHttpServletRequest) { |
| // dump the request progress tracker |
| final RequestProgressTracker tracker = ((SlingHttpServletRequest)req).getRequestProgressTracker(); |
| StringWriter strWriter = new StringWriter(); |
| try (PrintWriter progressWriter = new PrintWriter(strWriter)) { |
| tracker.dump(progressWriter); |
| } |
| jsonGenerator.write("requestProgress", strWriter.toString()); |
| } |
| |
| jsonGenerator.writeEnd(); |
| } |
| } |
| |
| /** |
| * Print the stack trace for the root exception if the throwable is a |
| * {@link ServletException}. If this does not contain an exception, |
| * the throwable itself is printed. |
| */ |
| private void printStackTrace(PrintWriter pw, Throwable t) { |
| // nothing to do, if there is no exception |
| if (t == null) { |
| return; |
| } |
| |
| // unpack a servlet exception |
| if (t instanceof ServletException) { |
| ServletException se = (ServletException) t; |
| while (se.getRootCause() != null) { |
| t = se.getRootCause(); |
| if (t instanceof ServletException) { |
| se = (ServletException) t; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| // dump stack, including causes |
| t.printStackTrace(pw); |
| } |
| |
| /** |
| * Sets the response status and content type header and starts the the |
| * response HTML text with the header, and an introductory phrase. |
| */ |
| private PrintWriter sendIntro(final HttpServletResponse response, |
| final int statusCode, |
| final String statusMessageIn, |
| final String requestUri, |
| final String servletName) |
| throws IOException { |
| |
| final String statusMessage = ResponseUtil.escapeXml(statusMessageIn); |
| |
| // set the status code and content type in the response |
| final PrintWriter pw; |
| if (!response.isCommitted()) { |
| |
| response.reset(); |
| response.setStatus(statusCode); |
| response.setContentType(HTML_CONTENT_TYPE); |
| response.setCharacterEncoding("UTF-8"); |
| |
| pw = response.getWriter(); |
| pw.println("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"); |
| pw.println("<html>"); |
| pw.println("<head>"); |
| pw.print("<title>"); |
| pw.print(statusCode); |
| pw.print(" "); |
| pw.print(statusMessage); |
| pw.println("</title>"); |
| pw.println("</head>"); |
| pw.println("<body>"); |
| |
| } else { |
| |
| // Response already committed: don't change status or write HTML prolog, but report |
| // the error inline and warn about that |
| log.warn("Response already committed, unable to change status, output might not be well formed"); |
| pw = response.getWriter(); |
| |
| } |
| |
| pw.print("<h1>"); |
| pw.print(statusMessage); |
| pw.print(" ("); |
| pw.print(statusCode); |
| pw.println(")</h1>"); |
| pw.print("<p>The requested URL "); |
| pw.print(ResponseUtil.escapeXml(requestUri)); |
| pw.print(" resulted in an error"); |
| |
| if (servletName != null) { |
| pw.print(" in "); |
| pw.print(ResponseUtil.escapeXml(servletName)); |
| } |
| |
| pw.println(".</p>"); |
| |
| return pw; |
| } |
| |
| /** |
| * Ends the response sending with an apache-style server line and closes the |
| * body and html tags of the HTML response text. |
| */ |
| private void sendEpilogue(final PrintWriter pw) { |
| pw.println("<hr>"); |
| pw.print("<address>"); |
| pw.print(ResponseUtil.escapeXml(getServletContext().getServerInfo())); |
| pw.println("</address>"); |
| pw.println("</body>"); |
| pw.println("</html>"); |
| } |
| |
| public static String statusToString(int statusCode) { |
| switch (statusCode) { |
| case 100: |
| return "Continue"; |
| case 101: |
| return "Switching Protocols"; |
| case 102: |
| return "Processing (WebDAV)"; |
| case 200: |
| return "OK"; |
| case 201: |
| return "Created"; |
| case 202: |
| return "Accepted"; |
| case 203: |
| return "Non-Authoritative Information"; |
| case 204: |
| return "No Content"; |
| case 205: |
| return "Reset Content"; |
| case 206: |
| return "Partial Content"; |
| case 207: |
| return "Multi-Status (WebDAV)"; |
| case 300: |
| return "Multiple Choices"; |
| case 301: |
| return "Moved Permanently"; |
| case 302: |
| return "Found"; |
| case 303: |
| return "See Other"; |
| case 304: |
| return "Not Modified"; |
| case 305: |
| return "Use Proxy"; |
| case 307: |
| return "Temporary Redirect"; |
| case 400: |
| return "Bad Request"; |
| case 401: |
| return "Unauthorized"; |
| case 402: |
| return "Payment Required"; |
| case 403: |
| return "Forbidden"; |
| case 404: |
| return "Not Found"; |
| case 405: |
| return "Method Not Allowed"; |
| case 406: |
| return "Not Acceptable"; |
| case 407: |
| return "Proxy Authentication Required"; |
| case 408: |
| return "Request Time-out"; |
| case 409: |
| return "Conflict"; |
| case 410: |
| return "Gone"; |
| case 411: |
| return "Length Required"; |
| case 412: |
| return "Precondition Failed"; |
| case 413: |
| return "Request Entity Too Large"; |
| case 414: |
| return "Request-URI Too Large"; |
| case 415: |
| return "Unsupported Media Type"; |
| case 416: |
| return "Requested range not satisfiable"; |
| case 417: |
| return "Expectation Failed"; |
| case 422: |
| return "Unprocessable Entity (WebDAV)"; |
| case 423: |
| return "Locked (WebDAV)"; |
| case 424: |
| return "Failed Dependency (WebDAV)"; |
| case 500: |
| return "Internal Server Error"; |
| case 501: |
| return "Not Implemented"; |
| case 502: |
| return "Bad Gateway"; |
| case 503: |
| return "Service Unavailable"; |
| case 504: |
| return "Gateway Time-out"; |
| case 505: |
| return "HTTP Version not supported"; |
| case 507: |
| return "Insufficient Storage (WebDAV)"; |
| case 510: |
| return "Not Extended"; |
| default: |
| return String.valueOf(statusCode); |
| } |
| } |
| } |