异常处理

Exception Handling

Exceptions thrown during route execution bubble up through the route structure to the next enclosing handleExceptions directive or the top of your route structure.

在路由执行期间被抛出的异常通过路由结构向上冒泡到下一个围住的 handleExceptions 指令或路由结构的顶部。

Similarly to the way that Rejections are handled the handleExceptions directive delegates the actual job of converting an exception to its argument, an ExceptionHandlerExceptionHandler, which is defined like this:.

类似于 拒绝 的处理方式, handleExceptions 指令将转换异常的实际工作委托给它的参数, 一个 ExceptionHandlerExceptionHandler,它的定义像这样:

trait ExceptionHandler extends PartialFunction[Throwable, Route]

Since an ExceptionHandlerExceptionHandler is a partial function, it can choose which exceptions it would like to handle and which not. Unhandled exceptions will simply continue to bubble up in the route structure. At the root of the route tree any still unhandled exception will be dealt with by the top-level handler which always handles all exceptions.

由于 ExceptionHandlerExceptionHandler 是一个部分函数,所以可以选择要处理哪些异常而不处理哪些异常。未处理异常将在路由结构中继续向上冒泡。 在路由树的根,任何仍未处理的异常都将由顶层处理程序处理,它始终处理 所有 异常。

Route.seal internally wraps its argument route with the handleExceptions directive in order to “catch” and handle any exception.

Route.seal 内部使用 handleExceptions 指令包裹路由参数,以便“捕获”并处理任何异常。

So, if you’d like to customize the way certain exceptions are handled you need to write a custom ExceptionHandlerExceptionHandler. Once you have defined your custom ExceptionHandlerExceptionHandler you have two options for “activating” it:

因此,如果你想定制某些异常的处理方式,你需要写一个自定义 ExceptionHandlerExceptionHandler 。 一旦你定义好了自定义 ExceptionHandlerExceptionHandler,有两种选项来“激活”它:

  1. Bring it into implicit scope at the top-level.Pass it to the seal() method of the RouteRouteRoute class.
  2. Supply it as argument to the handleExceptions directive.

In the first case your handler will be “sealed” (which means that it will receive the default handler as a fallback for all cases your handler doesn’t handle itself) and used for all exceptions that are not handled within the route structure itself. Here you can see an example of it:

在第一种情况,你的处理程序将被“密封”(这意味着它将接收默认处理程序,作为你的处理程序不能处理的所有情况的回退),并且用于路由结构自身没有处理的所有异常。 在这里你可以看到一个例子:

Scala
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server._
import Directives._

object SealedRouteWithCustomExceptionHandler {

  implicit def myExceptionHandler: ExceptionHandler =
    ExceptionHandler {
      case _: ArithmeticException =>
        extractUri { uri =>
          println(s"Request to $uri could not be handled normally")
          complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
        }
    }

  val route: Route = Route.seal(
    path("divide") {
      complete((1 / 0).toString) //Will throw ArithmeticException
    }
  ) // this one takes `myExceptionHandler` implicitly

}
Java
import akka.http.javadsl.model.StatusCodes;
import akka.http.javadsl.server.AllDirectives;
import akka.http.javadsl.server.ExceptionHandler;
import akka.http.javadsl.server.PathMatchers;
import akka.http.javadsl.server.RejectionHandler;
import akka.http.javadsl.server.Route;

import static akka.http.javadsl.server.PathMatchers.integerSegment;

public class ExceptionHandlerInSealExample extends AllDirectives {

  public Route createRoute() {
    final ExceptionHandler divByZeroHandler = ExceptionHandler.newBuilder()
      .match(ArithmeticException.class, x ->
        complete(StatusCodes.BAD_REQUEST, "You've got your arithmetic wrong, fool!"))
      .build();

    final RejectionHandler defaultHandler = RejectionHandler.defaultHandler();

    return path(PathMatchers.segment("divide").slash(integerSegment()).slash(integerSegment()), (a, b) ->
      complete("The result is " + (a / b))
    ).seal(defaultHandler, divByZeroHandler);
  }
}

The second case allows you to restrict the applicability of your handler to certain branches of your route structure.

第二种情况,允许你将处理程序的适用性限制在路由结构的某些分支。

Here is an example for wiring up a custom handler via handleExceptions:

这里是通过 handleExceptions 连接自定义处理程序的示例:

Scala
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import StatusCodes._
import akka.http.scaladsl.server._
import Directives._
import akka.stream.ActorMaterializer

val myExceptionHandler = ExceptionHandler {
  case _: ArithmeticException =>
    extractUri { uri =>
      println(s"Request to $uri could not be handled normally")
      complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
    }
}

object MyApp extends App {

  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  val route: Route =
    handleExceptions(myExceptionHandler) {
      // ... some route structure
    }

  Http().bindAndHandle(route, "localhost", 8080)
}
Java
import akka.NotUsed;
import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectHttp;
import akka.http.javadsl.ServerBinding;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.http.javadsl.model.StatusCodes;
import akka.http.javadsl.server.AllDirectives;
import akka.http.javadsl.server.ExceptionHandler;
import akka.http.javadsl.server.PathMatchers;
import akka.http.javadsl.server.Route;
import akka.http.javadsl.Http;
import akka.stream.ActorMaterializer;
import akka.stream.javadsl.Flow;

import java.util.concurrent.CompletionStage;
import static akka.http.javadsl.server.PathMatchers.integerSegment;

public class ExceptionHandlerExample extends AllDirectives {
  public static void main(String[] args) {
    final ActorSystem system = ActorSystem.create();
    final ActorMaterializer materializer = ActorMaterializer.create(system);
    final Http http = Http.get(system);

    final ExceptionHandlerExample app = new ExceptionHandlerExample();

    final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = app.createRoute().flow(system, materializer);
    final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer);
  }


  public Route createRoute() {
    final ExceptionHandler divByZeroHandler = ExceptionHandler.newBuilder()
      .match(ArithmeticException.class, x ->
        complete(StatusCodes.BAD_REQUEST, "You've got your arithmetic wrong, fool!"))
      .build();

    return path(PathMatchers.segment("divide").slash(integerSegment()).slash(integerSegment()), (a, b) ->
      handleExceptions(divByZeroHandler, () -> complete("The result is " + (a / b)))
    );
  }
}

And this is how to do it implicitly:

这是如何使用隐式参数的例子:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import StatusCodes._
import akka.http.scaladsl.server._
import Directives._
import akka.stream.ActorMaterializer

implicit def myExceptionHandler: ExceptionHandler =
  ExceptionHandler {
    case _: ArithmeticException =>
      extractUri { uri =>
        println(s"Request to $uri could not be handled normally")
        complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
      }
  }

object MyApp extends App {

  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  val route: Route =
  // ... some route structure

  Http().bindAndHandle(route, "localhost", 8080)
}

Default Exception Handler

默认异常处理程序

A default ExceptionHandlerExceptionHandler is used if no custom instance is provided.

如果未提供自定义实例,则使用默认 ExceptionHandlerExceptionHandler

It will handle every NonFatal throwable, write its stack trace and complete the request with InternalServerError (500) status code.

它将处理每一个 NonFatal throwable,写它的堆栈跟踪 (译注:记录异常栈) 并使用 InternalServerError (500) 状态码完成请求。

The message body will contain a string obtained via Throwable#getMessage call on the exception caught.

消息正文将包含一个通过在捕获异常上的 Throwable#getMessage 调用获得的字符串。

In case getMessage returns null (which is true for e.g. NullPointerException instances), the class name and a remark about the message being null are included in the response body.

getMessage 返回 null 在情况下,类名和一句关于消息为 null 的注释被包含到响应正文。

Note that IllegalRequestExceptions’ stack traces are not logged, since instances of this class normally contain enough information to provide a useful error message.

注意,IllegalRequestException 的堆栈跟踪不被记录,因为该类的实例通常包含足够的信息来提供用有的错误消息。

Note

Users are strongly encouraged not to rely on using the ExceptionHandlerExceptionHandler as a means of handling errors. By errors, we mean things that are an expected part of normal operations: for example, issues discovered during input validation. The ExceptionHandlerExceptionHandler is meant to be a means of handling failures. See Failure vs Error in the glossary of the Reactive Manifesto.

强烈建议用户不要依赖使用 ExceptionHandlerExceptionHandler 作为处理错误的方法。 所谓错误,我们的本意指它是正常操作中预期的部分:例如,输入检验期间发现的问题。 ExceptionHandlerExceptionHandler 是处理失败的方法。 见 反应式宣言 词汇表中的 失败 vs 错误

Distinguishing between errors and failures (i.e. thrown Exceptions handled via the ExceptionHandlerExceptionHandler) provides a much better mental model but also leads to performance improvements.

区别错误和失败(例如,通过 ExceptionHandlerExceptionHandler 处理抛出的 Exceptions)提供更好的心智模型,而且也会导致性能的改进。

This is because exceptions are known to have a negative performance impact for cases when the depth of the call stack is significant (stack trace construction cost) and when the handler is located far from the place of the throwable instantiation (stack unwinding costs).

这是因为当调用堆栈深度非常大(堆栈跟踪构建成本)并且处理程序远离可抛出实例化的地方(堆栈展开成本)时,已知异常会对性能产生负面影响。

In a typical Akka application both these conditions are frequently true, so as a rule of thumb, you should try to minimize the number of Throwable instances reaching the exception handler.

在典型 Akka 应用程序里这些条件通常都为真,因此根据经验,应该尽量减少到达异常处理程序的 Throwable 实例的数量

To understand the performance implications of (mis-)using exceptions, have a read at this excellent post by A. Shipilёv: The Exceptional Performance of Lil’ Exception.

要了解(错误-)使用异常对性能可能的影响,阅读 A. Shipilёv 的这篇优秀文章:LilException 的异常性能

Note

Please note that since version 10.1.6, the default ExceptionHandler will also discard the entity bytes automatically. If you want to change this behavior, please refer to the section above; however, might cause connections to stall if the entity is not properly rejected or cancelled on the client side.

请注意,从 10.1.6 版本开始,默认 ExceptionHandler 也将自动丢弃(请求的)实体字节流。 如果你想改变这个行为,请参考 上面的部分 ;但是,如果客户端上的实体没有正确地拒绝或取消,可能会导致连接挂住。

Including sensitive data in exceptions

异常中包含敏感的数据

To prevent certain types of attack, it is not recommended to include arbitrary invalid user input in the response. However, sometimes it can be useful to include it in the exception and logging for diagnostic reasons. In such cases, you can use exceptions that extend ExceptionWithErrorInfo, such as IllegalHeaderException:

要防止某些类型的攻击,在响应里包含任意无效的用户输入是不建议的。 但是,有时为了诊断原因在异常和日志里包含它是有用的。 在此类情况下,你可以使用扩展了 ExceptionWithErrorInfo 的异常,例如:IllegalHeaderException

Scala
import akka.http.scaladsl.model.IllegalHeaderException

val route = get {
  throw IllegalHeaderException("Value of header Foo was illegal", "Found illegal value \"<script>alert('evil_xss_or_xsrf_reflection')</script>\"")
}

// Test:
Get("/") ~> route ~> check {
  responseAs[String] should include("header Foo was illegal")
  responseAs[String] shouldNot include("evil_xss_or_xsrf_reflection")
}
Java
import static akka.http.javadsl.server.Directives.get;

import akka.http.scaladsl.model.IllegalHeaderException;
import akka.http.scaladsl.model.ErrorInfo;

import akka.http.javadsl.testkit.JUnitRouteTest;
import akka.http.javadsl.testkit.TestRoute;
import static junit.framework.TestCase.assertTrue;
TestRoute route = testRoute(
  get(() -> {
      throw new IllegalHeaderException(new ErrorInfo(
        "Value of header Foo was illegal",
        "Found illegal value \"<script>alert('evil_xss_or_xsrf_reflection')</script>\""));
  })
);

String response = route
  .run(HttpRequest.GET("/"))
  .entityString();
assertTrue(response.contains("header Foo was illegal"));
assertTrue(!response.contains("evil_xss_or_xsrf_reflection"));

Respond with headers and Exception Handler

使用头域和异常处理程序响应

If you wrap an ExceptionHandler inside a different directive, then that directive will still apply. Example below shows that wrapping an ExceptionHandler inside a respondWithHeader directive will still add the header to the response.

如果把 ExceptionHandler 包裹在不同的指令里,那么这个指令仍然适用。下面的示例显示,把 ExceptionHandler 包裹在 respondWithHeader 指令里,仍将添加头域到响应。

Scala
import akka.actor.ActorSystem
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.server._
import Directives._
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import RespondWithHeaderExceptionHandler.route


object RespondWithHeaderExceptionHandler {
  def myExceptionHandler: ExceptionHandler =
    ExceptionHandler {
      case _: ArithmeticException =>
        extractUri { uri =>
          println(s"Request to $uri could not be handled normally")
          complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
        }
    }

  val greetingRoutes: Route = path("greetings") {
    complete("Hello!")
  }

  val divideRoutes: Route = path("divide") {
    complete((1 / 0).toString) //Will throw ArithmeticException
  }

  val route: Route =
    respondWithHeader(RawHeader("X-Outer-Header", "outer")) { // will apply, since it gets the response from the handler
      handleExceptions(myExceptionHandler) {
        greetingRoutes ~ divideRoutes ~ respondWithHeader(RawHeader("X-Inner-Header", "inner")) {
          throw new Exception("Boom!") //Will cause Internal server error,
          // only ArithmeticExceptions are handled by myExceptionHandler.
        }
      }
    }
}

object MyApp extends App {
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  Http().bindAndHandle(route, "localhost", 8080)
}
Java
import akka.NotUsed;
import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectHttp;
import akka.http.javadsl.Http;
import akka.http.javadsl.ServerBinding;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.http.javadsl.model.StatusCodes;
import akka.http.javadsl.model.headers.RawHeader;
import akka.http.javadsl.server.AllDirectives;
import akka.http.javadsl.server.ExceptionHandler;
import akka.http.javadsl.server.Route;
import akka.stream.ActorMaterializer;
import akka.stream.javadsl.Flow;

import java.io.IOException;
import java.util.concurrent.CompletionStage;
class RespondWithHeaderHandlerExample extends AllDirectives {
    public static void main(String[] args) throws IOException {
        final ActorSystem system = ActorSystem.create();
        final ActorMaterializer materializer = ActorMaterializer.create(system);
        final Http http = Http.get(system);

        final RespondWithHeaderHandlerExample app = new RespondWithHeaderHandlerExample();

        final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = app.createRoute().flow(system, materializer);
        final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer);
    }

    public Route createRoute() {
        final ExceptionHandler divByZeroHandler = ExceptionHandler.newBuilder()
                .match(ArithmeticException.class, x ->
                        complete(StatusCodes.BAD_REQUEST, "Error! You tried to divide with zero!"))
                .build();

        return respondWithHeader(RawHeader.create("X-Outer-Header", "outer"), () -> //will apply for handled exceptions
                handleExceptions(divByZeroHandler, () -> concat(
                        path("greetings", () -> complete("Hello!")),
                        path("divide", () -> complete("Dividing with zero: " + (1 / 0))),
                        respondWithHeader(RawHeader.create("X-Inner-Header", "inner"), () -> {
                            // Will cause Internal server error,
                            // only ArithmeticExceptions are handled by divByZeroHandler.
                            throw new RuntimeException("Boom!");                                                                     
                        })
                ))
        );
    }
}
在此文档中发现错误?该页面的源代码可以在 这里 找到。欢迎随时编辑并提交 Pull Request。