【问题标题】:Unable to properly handle errors无法正确处理错误
【发布时间】:2018-10-22 06:51:06
【问题描述】:

我找到了我的应用程序没有按照我希望的方式运行的原因,但我不知道如何解决这个问题。总而言之,我的应用程序有一个自定义错误处理程序,如果出现错误,它会被调用。错误处理程序发送json 消息。但是在一个应用程序启动错误场景中(Future 失败),我想Redirect 用户到主页而不是发送json 消息。但这不会发生,因为自定义错误处理程序之前发送json 消息我可以从Futurerecover 发送Redirect

该应用程序的一个功能是注册验证。用户单击具有令牌的url。单击url 时,会调用verifyUser Action。它进行一些检查(使用使用Futures 的数据库查询)并根据成功或失败发送Redirectsignup=successsignup=error 属性(此处不失败取决于数据库中是否存在某些内容或不)。但是,如果Future 失败(我查询了一个不属于数据库架构的错误字段),我想再次Redirect 但它不起作用,因为在recover 之前调用了自定义错误处理程序。如何使我的应用程序重定向?

val result:Future[Result] = for{tokenOption:Option[UserToken] <- userTokenRepo.findOne(UserTokenKey(UUID.fromString(token)))  //generator 1 - get token from database
                                    userOption:Option[User] <- if (tokenOption.isDefined) userRepo.findOne(tokenOption.get.userKeys) else Future.successful(None) //generator2. found token, look for corresponding user to which the token belongs
                                    modifiedUser:Option[User] <- if (userOption.isDefined) confirmSignupforUser(userOption.get) else Future.successful(None) //generator 3. found user and token. Update profile
                                    deletedToken:Option[UserTokenKey] <- if(modifiedUser.isDefined) userTokenRepo.delete(UserTokenKey(UUID.fromString(token))) else Future.successful(None)
       }
         yield { //check if we have user and token and modified user here. If any is missing, return error else success
           println("db query results tokenOption: "+tokenOption+", userOption: "+userOption+" : modifiedUserOption: "+modifiedUser+", deletedToken: "+deletedToken)
           if(tokenOption.isDefined && userOption.isDefined && modifiedUser.isDefined && deletedToken.isDefined)
              Redirect("http://localhost:9000/home"+";signup=success")//TODOM - pick from config
           else
             /*TODOM - when redirecting with error, can provide additional info why sign up failed*/
             if(tokenOption.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else if(userOption.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else if(modifiedUser.isEmpty)
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
           else //this shouldn't happen. Unexpected
             Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
         }
       result.recover { case x => {
         println("Future failed in validateUserSession. Recovering. Returning Internal Server Error" + x)
//before this Redirect, the custom error handler sends json response

         Redirect("http://localhost:9000/home"+";signup=error")//TODOM - pick from config
        }
       }

自定义错误处理程序

class CustomHttpErrorHandler extends HttpErrorHandler {

    def onClientError(request: RequestHeader, statusCode: Int, message: String) = {
      println("client error: request "+request+", statusCode: "+statusCode+", message:"+message)
      Future.successful(
        Status(statusCode)(Json.toJson(JsonResultError(message)))
      )
    }

    def onServerError(request: RequestHeader, exception: Throwable) = {
      println("server error: request: "+request+", exception: "+exception.getMessage)
      Future.successful(
        InternalServerError(Json.toJson(JsonResultError(exception.getMessage)))
      )
    }

}

当我看到两个调试(一个来自自定义错误处理程序,另一个来自恢复)时,我能够验证问题

server error: request: GET /ws/users/signup/312c9eaf-f27b-43c7-8dac-445a628c3be8, exception: bucket_id is not a column defined in this metadata

Future failed in validateUserSession. Recovering. Returning Internal Server Errorjava.lang.IllegalArgumentException: bucket_id is not a column defined in this metadata

我可以尝试根据自定义错误处理程序中收到的异常进行检查,但我认为它过于通用,可能不是一个好的设计方法。

【问题讨论】:

  • 在 singup 错误情况下,您正在执行重定向,您不认为您应该在消息中抛出自定义异常吗?

标签: scala playframework-2.6


【解决方案1】:

错误可能是在创建 Future 之前发生的,因此代码会抛出由默认处理程序处理的异常,而不是被 Future 捕获。

具体来说,表达式

userTokenRepo.findOne(UserTokenKey(UUID.fromString(token)))

在当前线程中进行评估,因此此代码中的任何异常都不会被捕获并会调用默认错误处理程序。

解决方案是在Try 中计算此错误,如果有错误则立即处理。

这可能是这样的:

for {
  tokenKey <- Future.fromTry(Try(UserTokenKey(UUID.fromString(token))))
  tokenOption <- userTokenRepo.findOne(tokenKey)
  userOption <- tokenOption.fold(Future.successful)(userRepo.findOne(_.userKeys)) //generator2. found token, look for corresponding user to which the token belongs
  modifiedUser <- userOption.fold(Future.successful)(confirmSignupforUser) //generator 3. found user and token. Update profile
  ...

此代码中的任何异常都将导致您的recover 代码将处理失败的Future

【讨论】:

  • 如果findOne返回Future,为什么会在当前线程中执行?如果您的意思是 UUID.fromString(token) 可能会抛出错误,那么我知道情况并非如此。错误发生在 findOne 中,它在 Future 中运行代码。
  • @ManuChadha findOne 返回 Future 但它在当前线程中运行。在这个函数创建Future之前,会有一些代码在这个函数中运行,这些代码会在当前线程中运行。
  • @ManuChadha 另外,如果您知道错误出现在 findOne 中,那么提及它并将您的问题精简为显示问题的最少代码会很有帮助。跨度>
  • 很抱歉没有提到蒂姆。 findOne 的定义是def findone = Future{...}。因此没有在当前上下文中运行的代码,异常来自Future{....} 中的代码(Cassandra 数据库查询)。 Cassandra 的驱动程序正在抛出异常。 AFAIK,如果 Future 抛出异常,它会失败并且应该调用 recover。我预计 recover 正在被执行并且它处理所有情况(case x)而不进一步抛出任何异常,我的自定义错误处理程序不应该被执行。但两人都被处决了!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-06
  • 2019-05-17
  • 2016-08-21
  • 2012-07-18
  • 1970-01-01
  • 2019-03-04
相关资源
最近更新 更多