【问题标题】:Play framework: How to process server errors in HTTP filters?播放框架:如何处理 HTTP 过滤器中的服务器错误?
【发布时间】:2018-01-07 13:35:21
【问题描述】:

我正在尝试创建一个 HTTP 过滤器,用于记录有关请求的一些信息,例如标头以及请求正文的有限(因此内存不会爆炸)部分,以防出现错误。

为此,我遵循了文档 (https://www.playframework.com/documentation/2.6.x/ScalaHttpFilters) 并提出了如下内容:

class RequestErrorLogFilter @Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
  extends EssentialFilter {

  private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")

  private implicit val logging = Logging(actorSystem.eventStream, logger.getName)

  override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
    override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
      val accumulator: Accumulator[ByteString, Result] = next(request)

      val data = ArrayBuffer.empty[ByteString]
      var totalSize = 0
      val maxSize = 1024

      val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
        .map((in: ByteString) => {
          val left = maxSize - totalSize
          if (left > 0) {
            val takeNow =
              if (in.size > left) {
                in.slice(0, left)
              } else {
                in
              }
            data.append(takeNow)
            totalSize += takeNow.size
          }
          in
        })

      val accumulatorWithResult = accumulator.through(flow).map { result =>
        // this code doesn't get executed in case of an exception in a controller
        logger.info(s"The flow has completed and the result is $result")
        if (result.header.status >= 400) {
          val headerText = data.map(_.utf8String).mkString("")
          logger.warn(s"There was an error. Request head: $headerText")
        }
        result
      }

      accumulatorWithResult
    }
  }
}

这适用于客户端错误(如 400 - 错误请求)或控制器返回的任何错误,但如果控制器内部出现异常,则不会执行过滤器的“回调”,因此没有机会记录发生的事情。

还有一个更简单的“AccessLogHttpFilter”也有同样的问题,我认为这是使用播放应用程序创建访问日志的推荐解决方案:

class LoggingFilter @Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
  extends Filter {

  def apply(nextFilter: RequestHeader => Future[Result])
           (requestHeader: RequestHeader): Future[Result] = {

    val startTime = System.currentTimeMillis

    nextFilter(requestHeader).map { result =>

      val endTime = System.currentTimeMillis
      val requestTime = endTime - startTime

      Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
        s"returned ${result.header.status}")

      result.withHeaders("Request-Time" -> requestTime.toString)
    }
  }
}

有没有办法让 play 调用 http 过滤器代码,即使出现异常? 还有其他解决方法吗?

【问题讨论】:

    标签: http logging error-handling playframework


    【解决方案1】:

    想通了。

    要让 EssentialFilter 处理错误,您需要向累加器添加 .recover() 调用:

    class RequestErrorLogFilter @Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
      extends EssentialFilter {
    
      private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
    
      private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
    
      override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
        override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
          val accumulator: Accumulator[ByteString, Result] = next(request)
    
          val data = ArrayBuffer.empty[ByteString]
          var totalSize = 0
          val maxSize = 1024
    
          val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
            .map((in: ByteString) => {
              val left = maxSize - totalSize
              if (left > 0) {
                val takeNow =
                  if (in.size > left) {
                    in.slice(0, left)
                  } else {
                    in
                  }
                data.append(takeNow)
                totalSize += takeNow.size
              }
              in
            })
    
          val accumulatorWithResult: Accumulator[ByteString, Result] = accumulator.through(flow).map { result =>
            logger.info(s"The flow has completed and the result is $result")
            if (result.header.status >= 400) {
              val headerText = data.map(_.utf8String).mkString("")
              logger.warn(s"There was an error. Request head: $headerText")
            }
            result
          }
    
          accumulatorWithResult.recover {
            case error =>
              val headerText = data.map(_.utf8String).mkString("")
              logger.warn(s"There was an error: $error. Request head: $headerText")
              throw error
          }
        }
      }
    }
    

    对于一个简单的过滤器,您需要对结果future 调用 .failed.foreach:

    class LoggingFilter @Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
      extends Filter {
    
      def apply(nextFilter: RequestHeader => Future[Result])
               (requestHeader: RequestHeader): Future[Result] = {
    
        val startTime = System.currentTimeMillis
    
        val eventualResult = nextFilter(requestHeader)
    
        eventualResult.failed.foreach { error: Throwable =>
          val endTime = System.currentTimeMillis
          val requestTime = endTime - startTime
    
          Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
            s"returned 500 $error")
        }
    
        eventualResult.map { result =>
    
          val endTime = System.currentTimeMillis
          val requestTime = endTime - startTime
    
          Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
            s"returned ${result.header.status}")
    
          result.withHeaders("Request-Time" -> requestTime.toString)
        }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-06-21
      • 2014-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      相关资源
      最近更新 更多