• Akka(32): Http:High-Level-Api,Route exception handling


      Akka-http routing DSL在Route运算中抛出的异常是由内向外浮出的:当内层Route未能捕获异常时,外一层Route会接着尝试捕捉,依次向外扩展。Akka-http提供了ExceptionHandler类来处理Route运算产生的异常:

    trait ExceptionHandler extends ExceptionHandler.PF {
    
      /**
       * Creates a new [[ExceptionHandler]] which uses the given one as fallback for this one.
       */
      def withFallback(that: ExceptionHandler): ExceptionHandler
    
      /**
       * "Seals" this handler by attaching a default handler as fallback if necessary.
       */
      def seal(settings: RoutingSettings): ExceptionHandler
    }
    
    object ExceptionHandler {
      type PF = PartialFunction[Throwable, Route]
      private[http] val ErrorMessageTemplate: String = {
        "Error during processing of request: '{}'. Completing with {} response. " +
          "To change default exception handling behavior, provide a custom ExceptionHandler."
      }
    
      implicit def apply(pf: PF): ExceptionHandler = apply(knownToBeSealed = false)(pf)
    
      private def apply(knownToBeSealed: Boolean)(pf: PF): ExceptionHandler =
        new ExceptionHandler {
          def isDefinedAt(error: Throwable) = pf.isDefinedAt(error)
          def apply(error: Throwable) = pf(error)
          def withFallback(that: ExceptionHandler): ExceptionHandler =
            if (!knownToBeSealed) ExceptionHandler(knownToBeSealed = false)(this orElse that) else this
          def seal(settings: RoutingSettings): ExceptionHandler =
            if (!knownToBeSealed) ExceptionHandler(knownToBeSealed = true)(this orElse default(settings)) else this
        }
    
      def default(settings: RoutingSettings): ExceptionHandler =
        apply(knownToBeSealed = true) {
          case IllegalRequestException(info, status) ⇒ ctx ⇒ {
            ctx.log.warning("Illegal request: '{}'. Completing with {} response.", info.summary, status)
            ctx.complete((status, info.format(settings.verboseErrorMessages)))
          }
          case NonFatal(e) ⇒ ctx ⇒ {
            val message = Option(e.getMessage).getOrElse(s"${e.getClass.getName} (No error message supplied)")
            ctx.log.error(e, ErrorMessageTemplate, message, InternalServerError)
            ctx.complete(InternalServerError)
          }
        }
    
      /**
       * Creates a sealed ExceptionHandler from the given one. Returns the default handler if the given one
       * is `null`.
       */
      def seal(handler: ExceptionHandler)(implicit settings: RoutingSettings): ExceptionHandler =
        if (handler ne null) handler.seal(settings) else ExceptionHandler.default(settings)
    }

    简单来说ExceptionHandler类型就是一种PartialFunction:

    trait ExceptionHandler extends PartialFunction[Throwable, Route]

    因为ExceptionHandler就是PartialFunction,所以我们可以用case XXException来捕获需要处理的异常。留下未捕获的异常向外层Route浮出。当未处理异常到达最外层Route时统一由最顶层的handler处理。与RejectionHandler一样,最顶层的handler是通过Route.seal设置的:

    /**
       * "Seals" a route by wrapping it with default exception handling and rejection conversion.
       *
       * A sealed route has these properties:
       *  - The result of the route will always be a complete response, i.e. the result of the future is a
       *    ``Success(RouteResult.Complete(response))``, never a failed future and never a rejected route. These
       *    will be already be handled using the implicitly given [[RejectionHandler]] and [[ExceptionHandler]] (or
       *    the default handlers if none are given or can be found implicitly).
       *  - Consequently, no route alternatives will be tried that were combined with this route
       *    using the ``~`` on routes or the [[Directive.|]] operator on directives.
       */
      def seal(route: Route)(implicit
        routingSettings: RoutingSettings,
                             parserSettings:   ParserSettings   = null,
                             rejectionHandler: RejectionHandler = RejectionHandler.default,
                             exceptionHandler: ExceptionHandler = null): Route = {
        import directives.ExecutionDirectives._
        // optimized as this is the root handler for all akka-http applications
        (handleExceptions(ExceptionHandler.seal(exceptionHandler)) & handleRejections(rejectionHandler.seal))
          .tapply(_ ⇒ route) // execute above directives eagerly, avoiding useless laziness of Directive.addByNameNullaryApply
      }

    上面的exceptionHandler没有默认值,看起来好像有可能有些异常在整个Route运算里都不会被捕获。但实际上Akka-http提供了默认的handler ExceptionHandler.default:

      /**
       * Creates a sealed ExceptionHandler from the given one. Returns the default handler if the given one
       * is `null`.
       */
      def seal(handler: ExceptionHandler)(implicit settings: RoutingSettings): ExceptionHandler =
        if (handler ne null) handler.seal(settings) else ExceptionHandler.default(settings)

    通过这个ExceptionHandler.seal函数设置了最顶层的exception handler。

    我们可以通过下面的方法来定制异常处理的方式:

    自定义ExceptionHandler,然后:

    1、把Exceptionhandler的隐式实例放在顶层Route的可视域内(implicit scope)

    2、或者,直接调用handleExceptions,把自定义handler当作参数传入,把Route结构中间某层及其所有内层包嵌在handleExceptions中,例如:

        val route: Route =
        get {
          pathSingleSlash {
            complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
          } ~
            path("ping") {
             handleExceptions(customExceptionHandler) {  
               onSuccess(Future.successful("ok"))
               complete("PONG!")
              }
            } ~
            path("crash") {
              sys.error("BOOM!")
            }
        }

    第一种办法是一种顶层对所有未捕获异常统一处理的方式,第二种办法可以限制处理区域针对某层以内的Route进行异常捕捉。

    下面是第一种办法的使用示范:

    object ExceptiontionHandlers {
      implicit def implicitExceptionHandler: ExceptionHandler =
        ExceptionHandler {
          case _: ArithmeticException =>
            extractUri { uri =>
              complete(HttpResponse(InternalServerError, entity = s"$uri: Bad numbers, bad result!!!"))
            }
        }
      def customExceptionHandler: ExceptionHandler =
        ExceptionHandler {
          case _: RuntimeException =>
            extractUri { uri =>
              complete(HttpResponse(InternalServerError, entity = s"$uri: Runtime exception!!!"))
            }
        }
    
    }

    第二种方式的使用示范如下:

      val route: Route =
        get {
          pathSingleSlash {
            complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
          } ~
            path("ping") {
                onSuccess(Future.successful("ok"))
                complete("PONG!")
            } ~
            handleExceptions(customExceptionHandler) {
              path("crash") {
                sys.error("BOOM!")
              }
            }
        }

    下面是本次讨论中的示范源代码:

    import akka.actor._
    import akka.http.scaladsl.Http
    import akka.http.scaladsl.model._
    import akka.http.scaladsl.server._
    import akka.http.scaladsl.server.Directives._
    import akka.stream._
    import StatusCodes._
    import scala.concurrent._
    
    object ExceptiontionHandlers {
      implicit def implicitExceptionHandler: ExceptionHandler =
        ExceptionHandler {
          case _: ArithmeticException =>
            extractUri { uri =>
              complete(HttpResponse(InternalServerError, entity = s"$uri: Bad numbers, bad result!!!"))
            }
        }
      def customExceptionHandler: ExceptionHandler =
        ExceptionHandler {
          case _: RuntimeException =>
            extractUri { uri =>
              complete(HttpResponse(InternalServerError, entity = s"$uriRuntime exception!!!"))
            }
        }
    
    }
    
    object ExceptionHandlerDemo extends App {
      import ExceptiontionHandlers._
    
      implicit val httpSys = ActorSystem("httpSys")
      implicit val httpMat = ActorMaterializer()
      implicit val httpEc = httpSys.dispatcher
    
      val (port, host) = (8011,"localhost")
    
      val route: Route =
        get {
          pathSingleSlash {
            complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
          } ~
            path("ping") {
                onSuccess(Future.successful("ok"))
                complete("PONG!")
            } ~
            handleExceptions(customExceptionHandler) {
              path("crash") {
                sys.error("BOOM!")
              }
            }
        }
    
      val bindingFuture: Future[Http.ServerBinding] = Http().bindAndHandle(route,host,port)
    
      println(s"Server running at $host $port. Press any key to exit ...")
    
      scala.io.StdIn.readLine()
    
      bindingFuture.flatMap(_.unbind())
        .onComplete(_ => httpSys.terminate())
    
    }
  • 相关阅读:
    Git与GitHub的基本使用
    HTML&CSS基础-外边框
    HTML&CSS基础-内边框
    SHELL脚本编程变量输入
    GoLang基础数据类型-切片(slice)详解
    GoLang基础数据类型--->数组(array)详解
    SHELL脚本编程的运算符
    SHELL脚本编程变量详解
    HTML&CSS基础-边框简写属性
    GoLang基础数据类型--->字符串处理大全
  • 原文地址:https://www.cnblogs.com/tiger-xc/p/7735377.html
Copyright © 2020-2023  润新知