使用 Action(parse.json) 时如何禁用 HTML 错误响应?

How to disable HTML error responses when using Action(parse.json)?

在使用 Play Framework (2.4) 实现的 REST API 中,我使用 Action(parse.json) 从传入的 POST 请求正文中解析 JSON。

使用我当前的代码(见下文),

在后一种情况下,如何删除 HTML 响应主体? 我希望响应主体包含一个简短的 JSON 错误消息(例如 {"error":"Invalid JSON input"}),或者什么也没有。 Play 是否为此提供配置选项,或者我是否需要创建自定义操作?最简单的方法是什么?

控制器方法:

def test = Action(parse.json) { request =>
  request.body.validate[Input].map(i => {
    Ok(i.foo)
  }).getOrElse(BadRequest(errorJson("Missing input fields")))
}

上面用到的其他东西:

case class Input(foo: String, bar: String)

object Input {
  implicit val reads = Json.reads[Input]
}

case class ErrorJson(error: String)

object ErrorJson {
  implicit val writes = Json.writes[ErrorJson]
}

private def errorJson(message: String) = Json.toJson(ErrorJson(message))

长html默认生成HttpErrorHandler。您可以通过关注 this guide 来提供您自己的。引用示例代码:

class ErrorHandler extends HttpErrorHandler {

  def onClientError(request: RequestHeader, statusCode: Int, message: String) = {
    Future.successful(
      Status(statusCode)("A client error occurred: " + message)
    )
  }

  def onServerError(request: RequestHeader, exception: Throwable) = {
    Future.successful(
      InternalServerError("A server error occurred: " + exception.getMessage)
    )
  }
}

注意:如果您在没有 Guice 的情况下管理依赖项,则必须在 ApplicationLoader

中提供您的自定义 HttpErrorHandler

简答:在https://github.com/playframework/playframework/blob/48a76b851946261a952a2edcc4b8dbeeb303e07b/framework/src/play/src/main/scala/play/api/mvc/ContentTypes.scala#L379

Json.parse(bytes.iterator.asInputStream)

Json.parse 委托给 Jacksons 的 parseJsValue,这会引发异常。我不能正确地遵循代码,为什么在 Iteratee 框架中没有正确捕获异常。

最直接的解决方案是复制 ContentTypes.scala 中的代码,捕获 Json.parse 附近的异常,并将其正确转换为 BodyParserLeftIteratee。不幸的是,Play 不会公开构建块,因此如果您想像在 Play 中那样做,就需要很多 copy-paste。

或者你可以在累积的字节数组上做哑 Iteratee.fold 和直接 Json.parse,那不好;您可能想要检查接受 headers ,使用 bytearray 生成器,并限制输入的最大大小

val betterJson: BodyParser[JsValue] = BodyParser("better json") { _request =>
  import play.api.libs.iteratee.Execution.Implicits.defaultExecutionContext

  Iteratee.fold(new Array[Byte](0)) { (bytes: Array[Byte], acc: Array[Byte]) =>
    bytes ++ acc
  } map { bytes =>
    val res: Either[Result, JsValue] = Try(Json.parse(bytes)) match {
      case Success(v) => Right(v)
      case Failure(e) => Left(BadRequest("bad json"))
    }
    res
  }
}

在控制器中使用 betterJson

def test = Action(betterJson) { request =>
  request.body.validate[Int].map(i => {
    Ok(i.toString)
  }).getOrElse(BadRequest("my error"))
}

测试:

// works:
// $ curl -H "Content-Type: application/json" -X POST -d '123' localhost:9000/test
// 123
// $ curl -H "Content-Type: application/json" -X POST -d '{}' localhost:9000/test
// my error
//
// issue:
// $ curl -H "Content-Type: application/json" -X POST -d '.' localhost:9000/test
// bad json