使用scala处理HTTP响应的最佳实践

时间:2017-03-29 09:31:46

标签: scala http playframework

我有ServerA公开客户端的API方法,如下所示:

def methodExposed()= Action.async(json) { req =>

    val reqAsModel = request.body.extractOpt[ClientRequestModel]

    reqAsModel match {
      case Some(clientRequest) =>
        myApiService
          .doSomething(clientRequest.someList)
          .map(res => ???)
      case None =>
        Future.successful(BadRequest("could not extract request"))
    }
  }

所以,我有一个客户端请求的case类,如果我不能从请求体中提取它,那么我返回一个带有消息的BadRequest,否则我调用内部apiService来执行对此请求采取一些行动。

doSomething执行对ServerB的API调用,可以返回3个可能的响应:

  1. 200状态
  2. 我需要提取到案例类的身体400状态
  3. 500状态
  4. doSomething看起来像这样:

    def doSomething(list: List[String]) = {
        wSClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
          response.status match {
            case Status.BAD_REQUEST =>
              parse(response.body).extract[ServiceBResponse]
            case Status.INTERNAL_SERVER_ERROR =>
              val ex = new RuntimeException(s"ServiceB Failed with status: ${response.status} body: ${response.body}")
              throw ex
          }
        }
      }
    

    现在我有两个问题:

    1. 由于200返回时没有正文且400有正文,我不知道应该是doSomething
    2. 的返回类型
    3. 如何在控制器中处理此问题并在methodExposed中正确地将响应返回给客户?

2 个答案:

答案 0 :(得分:1)

我会做这样的事情:

case class ServiceBResponse(status: Int, body: Option[String] = None)

然后,doSomething就像:

def doSomething(list: List[String]) = {
  wSClient.url(url).withHeaders(("Content-Type", "application/json")).post(write(list)).map { response =>
    response.status match {
      case Status.OK =>
        ServiceBResponse(response.status)
      case Status.BAD_REQUEST =>
        ServiceBResponse(response.status, Option(response.body))
      case Status.INTERNAL_SERVER_ERROR =>
        val message = s"ServiceB Failed with status: ${response.status} body: ${response.body}"
        ServiceBResponse(response.status, Option(message))
    }
  }
}

最后,在控制器内部:

def methodExposed() = Action.async(json) { req =>

  val reqAsModel = request.body.extractOpt[ClientRequestModel]

  reqAsModel match {
    case Some(clientRequest) =>
      myApiService
        .doSomething(clientRequest.someList)
        .map(serviceBResponse => Status(serviceBResponse.status)(serviceBResponse.getOrElse("")))
    case None =>
      Future.successful(BadRequest("could not extract request"))
  }
}

另一种选择是直接使用WSResponse

def doSomething(list: List[String]) = {
    wSClient
        .url(url)
        .withHeaders(("Content-Type", "application/json"))
        .post(write(list))
}

控制器:

def methodExposed() = Action.async(json) { req =>

  val reqAsModel = request.body.extractOpt[ClientRequestModel]

  reqAsModel match {
    case Some(clientRequest) =>
      myApiService
        .doSomething(clientRequest.someList)
        .map(wsResponse => Status(wsResponse.status)(wsResponse.body))
    case None =>
      Future.successful(BadRequest("could not extract request"))
  }
}

答案 1 :(得分:0)

如果400是常见的预期错误,我认为类型Future[Either[Your400CaseClass, Unit]]是有意义的。关于methodExposed如何将结果返回给客户端取决于您的业务逻辑:

  • 是否应告知客户的基础400件?当doSomething遇到400
  • 时,methodExposed应该向客户端返回500
  • 否则,您可以将错误传播到客户端。根据业务逻辑,您可能希望也可能不希望将案例类转换为另一种形式,并可能使用不同的http代码。

如果doSomething返回500(或更常见的是任何意外的http代码),则应抛出异常(使用Future.failed)。

(最后,我希望你没有使用500来传递'正常'错误,如验证/身份验证错误.5xx代码只应用于异常和不可恢复的错误。我知道的大多数http客户端会立即抛出异常遇到5xx,这意味着用户将无法处理它)