【问题标题】:Best practices of handling HTTP response with scala使用 scala 处理 HTTP 响应的最佳实践
【发布时间】:2017-03-29 09:31:46
【问题描述】:

我有 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"))
    }
  }

所以,我有一个客户端请求的案例类,如果我无法从请求正文中提取它,那么我会返回带有消息的 BadRequest,否则我会调用内部 apiService 来执行一些操作请求。

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

  1. 200 状态
  2. 我需要提取到案例类的正文的 400 状态
  3. 500 状态

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返回时没有body,而400有body,我不知道doSomething的返回类型应该是什么
  2. 我应该如何在控制器中处理这个问题并在methodExposed中正确地向客户端返回响应?

【问题讨论】:

  • 对于 200 与 400,我会推荐 Future[Either] 之类的东西。对于 500,我认为这在很大程度上取决于您通常如何处理应用程序中的故障(或者您是否完全处理它们)。由于doSomething 返回Future,您可以通过未来传播失败。
  • @rethab 但是怎么可能,因为 200 我不想在 doSomething 中做任何事情,只需将其滚动到控制器,然后再滚动到客户端,而 400 带有案例类
  • 所以右边可能是Unit。或者可能是一些虚拟的Done 对象?

标签: scala http playframework


【解决方案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"))
  }
}

【讨论】:

    【解决方案2】:

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

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

    如果doSomething 返回 500(或更一般地说,任何意外的 http 代码),您应该抛出异常(使用 Future.failed)。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-13
      • 1970-01-01
      • 1970-01-01
      • 2013-08-24
      • 1970-01-01
      • 1970-01-01
      • 2013-08-25
      相关资源
      最近更新 更多