http4s - get request body as String or InputStream
Asked Answered
H

3

11

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.

Hyperform answered 4/5, 2016 at 20:19 Comment(0)
R
7

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.

Rizzi answered 4/5, 2016 at 22:54 Comment(0)
B
6

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)
Bryan answered 2/10, 2016 at 0:8 Comment(0)
N
1

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.

Newberry answered 23/1, 2020 at 18:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.