Sending zio http response from callback function

894 Views Asked by At

I am trying to play around with ZIO http using their simples hello world example. I have a Java-written service which does some logic, and it expecting a handler function, so it can call it when result is ready. How do I user it together with ZIO http ? I want something like this:

object HelloWorld extends App {

  def app(service: JavaService) = Http.collect[Request] {
    case Method.GET -> Root / "text" => {
      service.doSomeStuffWIthCallback((s:String) => Response.text(s))
    }
  }
  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
    Server.start(8090, app(new JavaService)).exitCode
}

Basically I want to send ZIO HTTP response from the callback function, I am just not sure how to go about that. Thanks.

EDIT:

I coudn't get the right types from your code, so I decided to simplify the whole thing, and arrived to this:

val content: HttpData[Blocking, Throwable] = HttpData.fromStream {
    ZStream.fromEffect(doSomeStuffWrapped)
  }

  def doSomeStuffWrapped = {
    UIO.effectAsync[String] { cb =>
        cb(
          IO.succeed("TEST STRING")
        )
    }
  }

However, the issue here is that types do not match, HttpData.fromStream requires ZStream of byte

Here is the link to my gist: https://gist.github.com/pmkyl/a37ff8b49e013c4e2e6f8ab5ad83e258

3

There are 3 best solutions below

6
On BEST ANSWER

You should wrap your Java service with callback in an effect using effectAsync:

def doSomeStuffWrapped(service: JavaService): Task[String] = {
  IO.effectAsync[Throwable, String] { cb =>
    service.doSomeStuffWithCallback((s: String) => {
      // Success case
      cb(IO.succeed(s))
      // Optional error case?
      // cb(IO.fail(someException))
    })
  }
}

def app(service: JavaService) = Http.collectM[Request] {
  case Method.GET -> Root / "text" => {
    doSomeStuffWrapped(service)
      .fold(err => {
        // Handle errors in some way
        Response.text("An error occured")
      }, successStr => {
        Response.text(successStr)
      })
  }
}

You might want to see this article presenting different options for wrapping impure code in ZIO: https://medium.com/@ghostdogpr/wrapping-impure-code-with-zio-9265c219e2e

0
On

In ZIO-http v1.0.0.0-RC18 HttpData.fromStream can also take ZStream[R, E, String] as input with Http charset which defaults to CharsetUtil.UTF_8 however you can pass any charset to the HttpData.fromStream as its second argument. you can find the solution below

val stream: ZStream[Any, Nothing, String] = ZStream.fromEffect(doSomeStuffWrapped)
  val content: HttpData[Any, Nothing]       = HttpData.fromStream(stream)

  def doSomeStuffWrapped = {
    UIO.effectAsync[String] { cb =>
      cb(
        IO.succeed("TEST STRING"),
      )
    }
  }
  // Create HTTP route
  val app                = Http.collect[Request] {
    case Method.GET -> !! / "health" => Response.ok
    case Method.GET -> !! / "file"   => Response(data = content)
  }

  // Run it like any simple app
  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
    Server.start(8090, app.silent).exitCode
  

However in previous versions you could have done something like given below to make it work

val stream: ZStream[Any, Nothing, Byte] =
    ZStream.fromEffect(doSomeStuffWrapped).mapChunks(_.map(x => Chunk.fromArray(x.getBytes(HTTP_CHARSET))).flatten)
  val content: HttpData[Any, Nothing]     = HttpData.fromStream(stream)

  def doSomeStuffWrapped = {
    UIO.effectAsync[String] { cb =>
      cb(
        IO.succeed("TEST STRING"),
      )
    }
  }
  // Create HTTP route
  val app                = Http.collect[Request] {
    case Method.GET -> !! / "health" => Response.ok
    case Method.GET -> !! / "file"   => Response(data = content)
  }

  // Run it like any simple app
  override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] =
    Server.start(8090, app.silent).exitCode
0
On

Here is also another way of achieving the same result:

  case class MyService(name: String) {
    def imDone[R, E](s: String => Unit): Unit = s(name)
  }
  val s: MyService = MyService("test")
  val app: Http[Any, Nothing, Request, UResponse] = Http.collectM[Request] { case Method.GET -> Root / "text" =>
    ZIO.effectAsync[Any, Nothing, UResponse] { cb =>
      s.imDone { b =>
        cb(IO.succeed(Response.text(b)))
      }
    }
  }