简体   繁体   English

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

[英]Best practices of handling HTTP response with scala

I have ServerA which exposes an API method for a client, which looks like this: 我有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"))
    }
  }

So, I have a case class for the client request and if I cannot extract it from the request body, then I return a BadRequest with the message and otherwise I call an internal apiService to perform some action with this request. 因此,我为客户端请求提供了一个案例类,如果无法从请求主体中提取该案例类,则返回带有消息的BadRequest ,否则我将调用内部apiService对该请求执行某些操作。

doSomething performs an API call to ServerB that can return 3 possible responses: doSomething对ServerB执行API调用,该API可以返回3种可能的响应:

  1. 200 status 200状态
  2. 400 status with body that I need to extract to a case class 我需要提取到案例类的正文的400个状态
  3. 500 status 500状态

doSomething looks like this: 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
      }
    }
  }

Now I have two issues: 现在我有两个问题:

  1. Since the 200 returns with no body and 400 has a body, I don't know what should be the return type of doSomething 由于200没有实体,而400有实体,我不知道doSomething的返回类型应该是什么
  2. How should I handle this in the controller and return the response to the client properly in methodExposed ? 我应该如何在控制器中处理此问题,并在methodExposed响应正确返回给客户端?

I would do something like this: 我会做这样的事情:

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

And then, doSomething would be like: 然后, 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))
    }
  }
}

Finally, inside the controller: 最后,在控制器内部:

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

Another alternative is directly use WSResponse : 另一种选择是直接使用WSResponse

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

And the controller: 和控制器:

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

If 400 is a common expected error, I think the type Future[Either[Your400CaseClass, Unit]] makes sense. 如果400是常见的预期错误,我认为类型Future[Either[Your400CaseClass, Unit]]是有意义的。 In terms of how methodExposed returns the result to the client depends on your business logic: 关于methodExposed如何将结果返回给客户端的方式取决于您的业务逻辑:

  • Is the underlying 400 something the client should be informed of? 客户应该被告知底层的400件事吗? If methodExposed should return a 500 to the client when doSomething encounters a 400 如果doSomething遇到400时,methodExposed应该返回500给客户端
  • Otherwise you can propagate the error to the client. 否则,您可以将错误传播到客户端。 Depending on the business logic, you may or may not want to transform your case class into another form, and potentially with a different http code. 根据业务逻辑,您可能会或可能不希望将案例类转换为另一种形式,并可能使用其他http代码。

You should throw an exception (using Future.failed) if doSomething returns 500 (or more generally, any unexpected http code). 如果doSomething返回500(或更常见的是任何意外的http代码),则应引发异常(使用Future.failed)。

(Lastly, I hope you're not using 500 to communicate a 'normal' error like validation / authentication error. 5xx code should only be used for exceptional and unrecoverable errors. Most http clients I know will throw an exception immediately when a 5xx is encountered, which means the user won't get the chance to handle it) (最后,我希望您不要使用500来传达“正常”错误,例如验证/身份验证错误。5xx代码仅应用于例外和不可恢复的错误。我知道的大多数HTTP客户端在5xx为遇到,这意味着用户将没有机会处理它)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM