Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

http4s - get request body as String or InputStream

I'm trying to define HttpService that receives json and parses it to case class with json4s library:

import org.http4s._
import org.http4s.dsl._
import org.json4s._
import org.json4s.native.JsonMethods._

case class Request(firstName: String, secondName: String)

HttpService {
  case req @ POST -> Root =>
    val request = parse(<map req.body or req.bodyAsText to JsonInput>).extract[Request]
    Ok()
}

How can I get org.json4s.JsonInput from req.body or req.bodyAsText?

I know that json4s also have StringInput and StreamInput that inherits from JsonInput for using with String and InputStream so I think that I need to convert req.body to InputStream or req.bodyAsText to String but I still do not understand how.

I'm new to Scala and I do not yet fully understand some concepts such as scalaz.stream.Process.

like image 432
mixel Avatar asked May 04 '16 20:05

mixel


3 Answers

You can use the http4s-json4s-jackson (or http4s-json4s-native) packages and use an org.http4s.EntityDecoder to easily get a Foo (I renamed your Request case class to Foo below) from a request.

EntityDecoder is a type class which can decode an entity from the request body. We want to get the Foo posted in JSON, so we need to create an EntityDecoder[Foo] which can decode JSON. If we want to create this decoder using json4s we need a Reader (or a JsonFormat).

If you have an EntityDecoder[Foo] instance, we can get the Foo from the request with req.as[Foo].

import org.json4s._
import org.json4s.jackson.JsonMethods._

import org.http4s._
import org.http4s.dsl._
import org.http4s.json4s.jackson._

case class Foo(firstName: String, secondName: String)

// create a json4s Reader[Foo]
implicit val formats = DefaultFormats
implicit val fooReader = new Reader[Foo] { 
  def read(value: JValue): Foo = value.extract[Foo] 
}
// create a http4s EntityDecoder[Foo] (which uses the Reader)
implicit val fooDec = jsonOf[Foo]

val service = HttpService {
  case req @ POST -> Root => 
    // req.as[Foo] gives us a Task[Foo]
    // and since Ok(...) gives a Task[Response] we need to use flatMap
    req.as[Foo] flatMap ( foo => Ok(foo.firstName + " " + foo.secondName) )
}

Note: The json libraries libraries used most often with http4s are probably argonaut and circe. So you might find more http4s examples using one of those libraries.

like image 65
Peter Neyens Avatar answered Nov 19 '22 06:11

Peter Neyens


Peter's solution both corrects the question and answers it, but I stumbled here looking for the solution to OP's stated, but not intended, question: "how to get request body as [...] InputStream" in http4s. Thanks to the discussion in Issue 634 on GitHub, here's what I came up with:

import java.io.InputStream
import org.http4s._
implicit val inputStreamDecoder: EntityDecoder[InputStream] = 
    EntityDecoder.decodeBy(MediaRange.`*/*`) { msg =>
  DecodeResult.success(scalaz.stream.io.toInputStream(msg.body))
}

And then in your HttpService, use that decoder like so:

request.as[InputStream].flatMap { inputStream => ...inputStream is an InputStream... }

Or skip the whole Decoder dance, if you want:

val inputStream = scalaz.stream.io.toInputStream(request.body)
like image 6
chbrown Avatar answered Nov 19 '22 07:11

chbrown


You may use flatMap and as inside it before calling the Http4s service to decode responses from it:

@Test def `Get json gives valid contact`: Unit = {
    val request = Request[IO](GET, uri"/contact")
    val io = Main.getJsonWithContact.orNotFound.run(request)
    // here is magic
    val response = io.flatMap(_.as[Json]).unsafeRunSync()

    val contact = contactEncoder(Contact(1, "Denis", "123"))  // this is encoding to json for assertion
    assertEquals(contact, response)
}

This is how types work here:

val io: IO[Response[IO]] = Main.getJsonWithContact.orNotFound.run(request)
val response: IO[Json] = io.flatMap(_.as[Json])
val res: Json = response.unsafeRunSync()

as[String] will return the string just like this.

like image 1
DenisNovac Avatar answered Nov 19 '22 07:11

DenisNovac