LoranceChen
LoranceChen

Reputation: 2574

How to use http4s server and client library as a proxy?

I want use http4s as proxy(like nginx), how to forward all data from my http4s server to another http server?

What I really want do is append a verify function on every request before do forward function. Hopefully like this:

HttpService[IO] {
  case request =>
    val httpClient: Client[IO] = Http1Client[IO]().unsafeRunSync
    if(verifySuccess(request)) { // forward all http data to host2 and
                                 // get a http response.
      val result = httpClient.forward(request, "http://host2")
      result
    } else {
      Forbidden //403
    }
}

How to do this with http4s and it's client?
Thanks

Updated

with the help of @TheInnerLight, I give it a try with the snippet code:

  val httpClient = Http1Client[IO]()

  val service: HttpService[IO] = HttpService[IO] {
    case req =>
      if(true) {
        for {
          client <- httpClient
          newAuthority = req.uri.authority.map(_.copy(host = RegName("scala-lang.org"), port = Some(80)))
          proxiedReq = req.withUri(req.uri.copy(authority = newAuthority))
          response <- client.fetch(proxiedReq)(IO.pure(_))
        } yield response
      } else {
        Forbidden("Some forbidden message...")
      }

  }

With a request: http://localhost:28080(http4s server listen at 28080):
but occurred a error:

[ERROR] org.http4s.client.PoolManager:102 - Error establishing client connection for key RequestKey(Scheme(http),localhost) 
java.net.ConnectException: Connection refused
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishConnect(UnixAsynchronousSocketChannelImpl.java:252)
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:198)
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
    at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:301)
    at java.lang.Thread.run(Thread.java:748)
[ERROR] org.http4s.server.service-errors:88 - Error servicing request: GET / from 0:0:0:0:0:0:0:1 
java.net.ConnectException: Connection refused
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishConnect(UnixAsynchronousSocketChannelImpl.java:252)
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:198)
    at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
    at sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:301)
    at java.lang.Thread.run(Thread.java:748)

Latest Version

 val httpClient: IO[Client[IO]] = Http1Client[IO]()

  override val service: HttpService[IO] = HttpService[IO] {
    case req =>
      val hostName = "scala-lang.org"
      val myPort = 80
      if(true) {
        val newHeaders = {
          val filterHeader = req.headers.filterNot{h =>
            h.name == CaseInsensitiveString("Connection") ||
            h.name == CaseInsensitiveString("Keep-Alive") ||
            h.name == CaseInsensitiveString("Proxy-Authenticate") ||
            h.name == CaseInsensitiveString("Proxy-Authorization") ||
            h.name == CaseInsensitiveString("TE") ||
            h.name == CaseInsensitiveString("Trailer") ||
            h.name == CaseInsensitiveString("Transfer-Encoding") ||
            h.name == CaseInsensitiveString("Upgrade")
          }
          filterHeader.put(Header("host", hostName))
        }

        for {
          client <- httpClient
          newAuthority = req.uri.authority
            .map(_.copy(host = RegName(hostName), port = Some(myPort)))
            .getOrElse( Authority(host = RegName(hostName), port = Some(myPort)))
          proxiedReq = req.withUri(req.uri.copy(authority = Some(newAuthority)))
            .withHeaders(newHeaders)
          response <- client.fetch(proxiedReq)(x => IO.pure(x))
        } yield {
          val rst = response
          rst
        }

      } else {
        Forbidden("Some forbidden message...")
      }

  }

It works fine enough for my REST API web server.
There are some error when proxy scala-lang.org for test:

[ERROR] org.http4s.blaze.pipeline.Stage:226 - Error writing body 
org.http4s.InvalidBodyException: Received premature EOF.

Upvotes: 4

Views: 2919

Answers (1)

TheInnerLight
TheInnerLight

Reputation: 12184

How about something like this:

HttpService[IO] {
  case req =>
    if(verifyRequest(req)) {
      for {
        client <- Http1Client[IO]()
        newHost = "host2"
        newAuthority = Authority(host = RegName("host2"), port = Some(80))
        proxiedReq =
          req.withUri(req.uri.copy(authority = Some(newAuthority)))
           .withHeaders(req.headers.put(Header("host", newHost)))
        response <- client.fetch(proxiedReq)(IO.pure(_))
      } yield response
    } else {
      Forbidden("Some forbidden message...")
    }
}

Note that you should definitely avoid littering your code with calls tounsafeRunSync. You should generally be using it at most once in your program (in Main). In other circumstances, you should focus on lifting the effects into the monad you're working in.

Upvotes: 1

Related Questions