All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.ext.web.handler.impl.ErrorHandlerImpl Maven / Gradle / Ivy

/*
 * Copyright 2014 Red Hat, Inc.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */

package io.vertx.ext.web.handler.impl;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.MIMEHeader;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.ErrorHandler;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

/**
 * @author Paulo Lopes
 * @author Tim Fox
 */
public class ErrorHandlerImpl implements ErrorHandler {

  private static final Logger LOG = LoggerFactory.getLogger(ErrorHandlerImpl.class);

  /**
   * Flag to enable/disable printing the full stack trace of exceptions.
   */
  private final boolean displayExceptionDetails;

  /**
   * Cached template for rendering the html errors
   */
  private final String errorTemplate;

  public ErrorHandlerImpl(Vertx vertx, String errorTemplateName, boolean displayExceptionDetails) {
    Objects.requireNonNull(errorTemplateName);
    this.errorTemplate = vertx.fileSystem()
      .readFileBlocking(errorTemplateName)
      .toString(StandardCharsets.UTF_8);
    this.displayExceptionDetails = displayExceptionDetails;
  }

  @Override
  public void handle(RoutingContext context) {

    HttpServerResponse response = context.response();

    Throwable failure = context.failure();

    if (response.headWritten()) {
      // response is already being processed, so we can't really
      // format the error as a "pretty print" message
      if (LOG.isWarnEnabled()) {
        LOG.warn("Response headers are already written", failure);
      }

      try {
        // force a close of the socket to
        // avoid dangling connections
        response.close();
      } catch (RuntimeException e) {
        // ignore
      }
      return;
    }

    int errorCode = context.statusCode();

    // force default error code
    if (errorCode == -1) {
      errorCode = 500;
    }

    response
      .setStatusCode(errorCode);

    answerWithError(context, errorCode);
  }

  private void answerWithError(RoutingContext context, int errorCode) {
    if (!sendErrorResponseMIME(context, errorCode) && !sendErrorAcceptMIME(context, errorCode)) {
      // fallback plain/text
      sendError(context, "text/plain", errorCode);
    }
  }

  private boolean sendErrorResponseMIME(RoutingContext context, int errorCode) {
    // does the response already set the mime type?
    String mime = context.response().headers().get(HttpHeaders.CONTENT_TYPE);

    if (mime == null) {
      // does the route have an acceptable content type?
      mime = context.getAcceptableContentType();
    }

    return mime != null && sendError(context, mime, errorCode);
  }

  private boolean sendErrorAcceptMIME(RoutingContext context, int errorCode) {
    // respect the client accept order
    List acceptableMimes = context.parsedHeaders().accept();

    for (MIMEHeader accept : acceptableMimes) {
      if (sendError(context, accept.value(), errorCode)) {
        return true;
      }
    }
    return false;
  }

  private boolean sendError(RoutingContext context, String mime, int errorCode) {

    final String title = "An unexpected error occurred";

    final HttpServerResponse response = context.response();
    final Throwable exception = context.failure();

    final String errorMessage;

    if (displayExceptionDetails) {
      if (exception == null) {
        errorMessage = response.getStatusMessage();
      } else {
        errorMessage = exception.getMessage();
      }
    } else {
      errorMessage = response.getStatusMessage();
    }

    if (mime.startsWith("text/html")) {
      StringBuilder stack = null;
      if (exception != null && displayExceptionDetails) {
        stack = new StringBuilder();
        for (StackTraceElement elem : exception.getStackTrace()) {
          stack
            .append("
  • ") .append(escapeHTML(elem.toString())) .append("
  • "); } } response.putHeader(HttpHeaders.CONTENT_TYPE, "text/html"); response.end( errorTemplate .replace("{title}", title) .replace("{errorCode}", Integer.toString(errorCode)) .replace("{errorMessage}", htmlFormat(errorMessage)) .replace("{stackTrace}", stack == null ? "" : stack.toString()) ); return true; } if (mime.startsWith("application/json")) { JsonObject jsonError = new JsonObject(); jsonError.put("error", new JsonObject().put("code", errorCode).put("message", errorMessage)); if (exception != null && displayExceptionDetails) { JsonArray stack = new JsonArray(); for (StackTraceElement elem : exception.getStackTrace()) { stack.add(elem.toString()); } jsonError.put("stack", stack); } response.putHeader(HttpHeaders.CONTENT_TYPE, "application/json"); response.end(jsonError.encode()); return true; } if (mime.startsWith("text/plain")) { response.putHeader(HttpHeaders.CONTENT_TYPE, "text/plain"); StringBuilder sb = new StringBuilder(); sb.append("Error "); sb.append(errorCode); sb.append(": "); sb.append(errorMessage); if (exception != null && displayExceptionDetails) { for (StackTraceElement elem : exception.getStackTrace()) { sb.append("\tat ").append(elem).append("\n"); } } response.end(sb.toString()); return true; } return false; } /** * Very incomplete html escape that will escape the most common characters on error messages. * This is to avoid pulling a full dependency to perform a compliant escape. Error messages * are created by developers as such that they should not be to complex for logging. */ private static String escapeHTML(String s) { StringBuilder out = new StringBuilder(Math.max(16, s.length())); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c > 127 || c == '"' || c == '\'' || c == '<' || c == '>' || c == '&') { out.append("&#"); out.append((int) c); out.append(';'); } else { out.append(c); } } return out.toString(); } private static String htmlFormat(String errorMessage) { if (errorMessage == null) { return ""; } // step #1 (escape html entities) String escaped = escapeHTML(errorMessage); // step #2 (replace line endings with breaks) return escaped.replaceAll("\\r?\\n", "
    "); } }




    © 2015 - 2025 Weber Informatics LLC | Privacy Policy