使用 Play WSClient 处理 JSON 个错误响应
Processing JSON error responses with Play WSClient
我正在使用 Play 的 WSClient 与第三方服务交互
request = ws.url(baseUrl)
.post(data)
.map{ response =>
response.json.validate[MyResponseClass]
响应可能是 MyResponseClass
,也可能是 ErrorResponse
,例如 { "error": [ { "message": "Error message" } ] }
是否有典型的方法来解析 Class 或 错误?
我应该这样做吗?
response.json.validateOpt[MyResponseClass].getOrElse(response.json.validateOpt[ErrorClass])
有一个 Either[L, R]
非常适合您可能有一个值或另一个值(不是两者,也不是 none)的情况,您可以这样做(未测试):
val result: Option[Either[ErrorClass, MyResponseClass]] = response
.json
.validateOpt[MyResponseClass]
.map { resp => Right(resp) }
.orElse {
response.json.validateOpt[ErrorClass]
.map { error => Left(error) }
}
左边是错误结果,右边是成功,这是一种常见的模式。
这个问题没有单一的答案。这里有多个微妙的考虑。我的回答将尝试提供一些方向。
至少要处理四种不同的情况[=72=]:
- 应用程序级有效结果(已建立连接、收到响应、200 状态代码)
- 应用程序级错误(已建立连接、收到响应、4xx、5xx 状态代码)
- 网络 IO 错误(连接未建立,或由于超时等原因未收到响应)
- JSON 解析错误(已建立连接,收到响应,无法将 JSON 转换为模型域对象)
伪代码:
已完成 Future
,其中响应为 ErrorResponse
或 MyResponseClass
,即 Either[ErrorResponse, MyResponseClass]
:
- 如果服务returns 200状态码,则解析为
MyResponseClass
- 如果服务 returns >= 400 状态码,则解析为
ErrorResponse
已完成 Future
,内部异常:
- 解析异常,或
- 网络 IO 异常(例如超时)
Future(Left(errorResponse))
对比 Future(throw new Exception)
注意 Future(Left(errorResponse))
和 Future(throw new Exception)
之间的区别:我们只将后者视为 failed future。前者,尽管里面有一个 Left
仍然被认为是一个成功完成的未来。
Future.andThen
对比 Future.recover
注意Future.andThen
and Future.recover
的区别:前者不改变future里面的值,后者可以改变future里面的值和类型。如果无法恢复,我们至少可以使用 andThen
.
记录异常
例子:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import play.api.libs.ws._
import play.api.libs.ws.ahc._
import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.Future
import play.api.libs.json._
import play.api.libs.ws.JsonBodyReadables._
import scala.util.Failure
import java.io.IOException
import com.fasterxml.jackson.core.JsonParseException
case class ErrorMessage(message: String)
object ErrorMessage {
implicit val errorMessageFormat = Json.format[ErrorMessage]
}
case class ErrorResponse(error: List[ErrorMessage])
object ErrorResponse {
implicit val errorResponseFormat = Json.format[ErrorResponse]
}
case class MyResponseClass(a: String, b: String)
object MyResponseClass {
implicit val myResponseClassFormat = Json.format[MyResponseClass]
}
object PlayWsErrorHandling extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
httpRequest(wsClient) map {
case Left(errorResponse) =>
println(s"handle application level error: $errorResponse")
// ...
case Right(goodResponse) =>
println(s"handle application level good response $goodResponse")
// ...
} recover { // handle failed futures (futures with exceptions inside)
case parsingError: JsonParseException =>
println(s"Attempt recovery from parsingError")
// ...
case networkingError: IOException =>
println(s"Attempt recovery from networkingError")
// ...
}
def httpRequest(wsClient: StandaloneWSClient): Future[Either[ErrorResponse, MyResponseClass]] =
wsClient.url("http://www.example.com").get() map { response ⇒
if (response.status >= 400) // application level error
Left(response.body[JsValue].as[ErrorResponse])
else // application level good response
Right(response.body[JsValue].as[MyResponseClass])
} andThen { // exceptions thrown inside Future
case Failure(exception) => exception match {
case parsingError: JsonParseException => println(s"Log parsing error: $parsingError")
case networkingError: IOException => println(s"Log networking errors: $networkingError")
}
}
}
依赖关系:
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-ahc-ws-standalone" % "1.1.3",
"com.typesafe.play" %% "play-ws-standalone-json" % "1.1.3"
)
我正在使用 Play 的 WSClient 与第三方服务交互
request = ws.url(baseUrl)
.post(data)
.map{ response =>
response.json.validate[MyResponseClass]
响应可能是 MyResponseClass
,也可能是 ErrorResponse
,例如 { "error": [ { "message": "Error message" } ] }
是否有典型的方法来解析 Class 或 错误?
我应该这样做吗?
response.json.validateOpt[MyResponseClass].getOrElse(response.json.validateOpt[ErrorClass])
有一个 Either[L, R]
非常适合您可能有一个值或另一个值(不是两者,也不是 none)的情况,您可以这样做(未测试):
val result: Option[Either[ErrorClass, MyResponseClass]] = response
.json
.validateOpt[MyResponseClass]
.map { resp => Right(resp) }
.orElse {
response.json.validateOpt[ErrorClass]
.map { error => Left(error) }
}
左边是错误结果,右边是成功,这是一种常见的模式。
这个问题没有单一的答案。这里有多个微妙的考虑。我的回答将尝试提供一些方向。
至少要处理四种不同的情况[=72=]:
- 应用程序级有效结果(已建立连接、收到响应、200 状态代码)
- 应用程序级错误(已建立连接、收到响应、4xx、5xx 状态代码)
- 网络 IO 错误(连接未建立,或由于超时等原因未收到响应)
- JSON 解析错误(已建立连接,收到响应,无法将 JSON 转换为模型域对象)
伪代码:
已完成
Future
,其中响应为ErrorResponse
或MyResponseClass
,即Either[ErrorResponse, MyResponseClass]
:- 如果服务returns 200状态码,则解析为
MyResponseClass
- 如果服务 returns >= 400 状态码,则解析为
ErrorResponse
- 如果服务returns 200状态码,则解析为
已完成
Future
,内部异常:- 解析异常,或
- 网络 IO 异常(例如超时)
Future(Left(errorResponse))
对比 Future(throw new Exception)
注意 Future(Left(errorResponse))
和 Future(throw new Exception)
之间的区别:我们只将后者视为 failed future。前者,尽管里面有一个 Left
仍然被认为是一个成功完成的未来。
Future.andThen
对比 Future.recover
注意Future.andThen
and Future.recover
的区别:前者不改变future里面的值,后者可以改变future里面的值和类型。如果无法恢复,我们至少可以使用 andThen
.
例子:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import play.api.libs.ws._
import play.api.libs.ws.ahc._
import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.Future
import play.api.libs.json._
import play.api.libs.ws.JsonBodyReadables._
import scala.util.Failure
import java.io.IOException
import com.fasterxml.jackson.core.JsonParseException
case class ErrorMessage(message: String)
object ErrorMessage {
implicit val errorMessageFormat = Json.format[ErrorMessage]
}
case class ErrorResponse(error: List[ErrorMessage])
object ErrorResponse {
implicit val errorResponseFormat = Json.format[ErrorResponse]
}
case class MyResponseClass(a: String, b: String)
object MyResponseClass {
implicit val myResponseClassFormat = Json.format[MyResponseClass]
}
object PlayWsErrorHandling extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val wsClient = StandaloneAhcWSClient()
httpRequest(wsClient) map {
case Left(errorResponse) =>
println(s"handle application level error: $errorResponse")
// ...
case Right(goodResponse) =>
println(s"handle application level good response $goodResponse")
// ...
} recover { // handle failed futures (futures with exceptions inside)
case parsingError: JsonParseException =>
println(s"Attempt recovery from parsingError")
// ...
case networkingError: IOException =>
println(s"Attempt recovery from networkingError")
// ...
}
def httpRequest(wsClient: StandaloneWSClient): Future[Either[ErrorResponse, MyResponseClass]] =
wsClient.url("http://www.example.com").get() map { response ⇒
if (response.status >= 400) // application level error
Left(response.body[JsValue].as[ErrorResponse])
else // application level good response
Right(response.body[JsValue].as[MyResponseClass])
} andThen { // exceptions thrown inside Future
case Failure(exception) => exception match {
case parsingError: JsonParseException => println(s"Log parsing error: $parsingError")
case networkingError: IOException => println(s"Log networking errors: $networkingError")
}
}
}
依赖关系:
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-ahc-ws-standalone" % "1.1.3",
"com.typesafe.play" %% "play-ws-standalone-json" % "1.1.3"
)