Return JSON akka-http 中的错误 API

Return JSON errors in akka-http API

在我写的 API 中,我想采取和 return JSON,即使在错误的情况下。我试图弄清楚如何保留所有 default RejectionHandler 行为,但将状态代码和文本转换为 JSON 对象。由于默认行为是在函数调用中指定的,而不是作为数据结构指定的,因此似乎唯一的方法是转换它产生的结果的 HttpEntity 。有没有简单的方法可以做到这一点?

你可以在你的 HttpService

中写这样的东西
private val defaultRejectionHandler = RejectionHandler.default

implicit def myRejectionHandler =
  RejectionHandler.newBuilder()
    .handleAll[Rejection] { rejections ⇒

    def prefixEntity(entity: ResponseEntity): ResponseEntity = entity match {
      case HttpEntity.Strict(contentType, data) => {
        import spray.json._
        val text = ErrorResponse(0, "Rejection", data.utf8String).toJson.prettyPrint
        HttpEntity(ContentTypes.`application/json`, text)
      }
      case _ =>
        throw new IllegalStateException("Unexpected entity type")
    }

    mapResponseEntity(prefixEntity) {
      defaultRejectionHandler(rejections).getOrElse {
        complete(StatusCodes.InternalServerError)
      }
    }
  }.handleNotFound {
    complete(StatusCodes.Forbidden -> ErrorResponse(StatusCodes.NotFound.intValue, "NotFound", "Requested resource is not found"))
  }.result()

其中 ErrorResponse 可能是

case class ErrorResponse(error: ErrorInfo)
case class ErrorInfo(code: Int, `type`: String, message: String)

您可以为其定义 json 个编组器。

我拼凑了自己的版本,但它有一些我不喜欢的粗糙边缘。 给了我一些改进的想法。这是我想出的,综合了两种方法的优点:

/**
  * Modifies the Akka-Http default rejection handler to wrap the default
  * message in JSON wrapper, preserving the original status code.
  *
  * @param rejectionWrapper wraps the message in a structure to format the
  *                         resulting JSON object
  * @param writer writer for the wrapper type
  * @tparam WrapperType type of the wrapper
  * @return the modified rejection handler
  */
def defaultRejectionHandlerAsJson[WrapperType](rejectionWrapper: String => WrapperType)(implicit writer: JsonWriter[WrapperType]) = {
  def rejectionModifier(originalMessage: String): String = {
    writer.write(rejectionWrapper(originalMessage)).prettyPrint
  }
  modifiedDefaultRejectionHandler(rejectionModifier, ContentTypes.`application/json`)
}

/**
  * Modifies the Akka-Http default rejection handler, converting the default
  * message to some other textual representation.
  *
  * @param rejectionModifier the modifier function
  * @param newContentType the new Content Type, defaulting to text/plain
  *                       UTF-8
  * @return the modified rejection handler
  */
def modifiedDefaultRejectionHandler(rejectionModifier: String => String, newContentType: ContentType.NonBinary = ContentTypes.`text/plain(UTF-8)`) = new RejectionHandler {
  def repackageRouteResult(entity: ResponseEntity): ResponseEntity = entity match {
    // If the entity isn't Strict (and it definitely will be), don't bother
    // converting, just throw an error, because something's weird.
    case strictEntity: HttpEntity.Strict =>
      val modifiedMessage = rejectionModifier(strictEntity.data.utf8String)
      HttpEntity(newContentType, modifiedMessage)
    case other =>
      throw new Exception("Unexpected entity type")
  }

  def apply(v1: Seq[Rejection]): Option[Route] = {
    // The default rejection handler should handle all possible rejections,
    // so if this isn't the case, return a 503.
    val originalResult = RejectionHandler.default(v1).getOrElse(complete(StatusCodes.InternalServerError))
    Some(mapResponseEntity(repackageRouteResult) {
      originalResult
    })
  }
}