Análise de conteúdo de solicitação em Finatra

Então você está escrevendo um aplicativo na estrutura finatra do Twitter quando ele te atinge: enviar dados formatados é estúpido e simples, mas onde está a análise ? .

Felizmente, não é preciso muito para começar a analisar corpos de solicitação nos endpoints do controlador que precisam disso.

Escrevendo um analisador

Para demonstração, vamos nos concentrar em lidar com solicitações JSON. Usaremos json4s para análise, portanto, precisaremos adicioná-lo a build.sbt:

libraryDependencies += "org.json4s"  %% "json4s-jackson" % "3.2.4"

Também precisaremos importar algumas classes exigidas pelo analisador de corpo:

import scala.util.Try

// For parsing content-type headers in general
import com.twitter.finagle.http.Request
import com.twitter.finatra.ContentType
import com.twitter.finatra.ContentType._

// For parsing JSON bodies in particular
import org.json4s._
import org.json4s.jackson.JsonMethods.parse

O próprio analisador de corpo é muito simples. Quando aplicado, ele retorna uma opção contendo o conteúdo analisado do corpo da solicitação ou Nonese o formato da solicitação não for reconhecido. O tipo de conteúdo analisado depende do analisador; nosso analisador de exemplo usará return quando o corpo for analisado com sucesso e se ocorrer um erro.Some(JValue)Some(JNothing)

object BodyParser {

private def contentType(request: Request):
request
.contentType
.map(_.takeWhile(c => c != ';'))
.flatMap(ContentType(_))
.getOrElse(new All)
request
.contentType.flatMap(ContentType(_)).getOrElse(new All)

private def asJson(body: String): Option[JValue] =
Try(parse(body)).toOption.orElse(Some(JNothing))

def apply(request: Request): Option[AnyRef] = contentType(request) match {
case _: Json => asJson(request.contentString)
case _ => None
}
}

Prolongando uma rota

Vamos configurar uma rota de demonstração em um dos controladores de nosso aplicativo que aplique o BodyParsera uma solicitação de entrada e retorne a resposta.

Além da feliz coincidência em que tudo dá certo, precisaremos abordar dois modos potenciais de falha:

  1. se um não analisável for enviado, falha com 415 (“tipo de mídia não compatível”)Content-type
  2. se a análise falhou em conteúdo aparentemente analisável, falha com 400 (“solicitação incorreta”)

Aqui está o código. Para mantê-lo simples, usamos o JSON serializado em uma plainresposta – algo que seria muito cauteloso na prática:

post("/echo") { request =>
BodyParser(request) match {
case Some(j: JObject) => {
render
.status(200).plain(compact(render(j))).toFuture
}
case Some(JNothing) => {
render
.status(400).plain("Invalid JSON").toFuture
}
case _ => throw new UnsupportedMediaType
}
}

Uma vez que o conteúdo inválido falha por meio da exceção, nosso controlador precisará ter um errormanipulador apropriado no local. Aqui está uma implementação simples emprestada do aplicativo de exemplo de finatra:

error { request =>
request
.error match {
case Some(e:UnsupportedMediaType) =>
render
.status(415).plain("Unsupported Media Type!").toFuture
case _ =>
render
.status(500).plain("Something went wrong!").toFuture
}
}

Por fim, vamos adicionar especificações para verificar se tudo está funcionando bem:

"POST /echo" should "echo valid JSON" in {
val body
= """{"hello":"world"}"""

post
("/echo",
headers
= Map("Content-type" -> "application/json"),
body
= body)

response
.code should equal(200)
response
.body should equal(body)
}

"POST /echo" should "fail for invalid JSON" in {
val body
= """{"hello":"""

post
("/echo",
headers
= Map("Content-type" -> "application/json"),
body
= body)

response
.code should equal(400)
}

"POST /echo" should "fail for unknown content-type" in {

post
("/echo",
headers
= Map("Content-type" -> "application/ruby-slippers")
)

response
.code should equal(415)
}

E isso deveria bastar! Agora estamos prontos para começar a lidar com o conteúdo das solicitações recebidas.