前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Akka(32): Http:High-Level-Api,Route exception handling

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

作者头像
用户1150956
发布2018-01-05 11:52:51
6350
发布2018-01-05 11:52:51
举报

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

代码语言:javascript
复制
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:

代码语言:javascript
复制
trait ExceptionHandler extends PartialFunction[Throwable, Route]

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

代码语言:javascript
复制
/**
   * "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:

代码语言:javascript
复制
  /**
   * 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中,例如:

代码语言:javascript
复制
    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进行异常捕捉。

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

代码语言:javascript
复制
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!!!"))
        }
    }

}

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

代码语言:javascript
复制
  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!")
          }
        }
    }

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

代码语言:javascript
复制
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())

}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-10-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档