Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
http4s client that can work through a client-side https proxy
package net.shrine.utilities.http4sclienttest
import cats.effect.IO
import io.netty.handler.ssl.{SslContext, SslContextBuilder}
import org.asynchttpclient.Dsl
import org.asynchttpclient.netty.ssl.InsecureTrustManagerFactory
import org.asynchttpclient.proxy.ProxyServer
import org.http4s.client.Client
import org.http4s.client.asynchttpclient.AsyncHttpClient
import org.http4s.headers.Accept
import org.http4s.{Headers, Method, ParseResult, Request, Uri}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
/**
* Here is the test I created to get an http4s request/reply through an Apache client-side https proxy.
*
* @author dwalend
* @since 1.25
*/
object Http4sClientTest {
def main(args: Array[String]): Unit = {
try {
if (args.length != 2) throw WrongNumberOfArguments("Requires two arguments: the http verb (PUT or GET) and the URL to call.")
val verbString = args(0)
val verb: ParseResult[Method] = Method.fromString(verbString)
val urlString = args(1)
val uri: ParseResult[Uri] = Uri.fromString(urlString)
(verb,uri) match {
case (Right(v),Right(u)) => {
val requestResponseIo = testHttpRequest(v,u)
//Don't use unsafeRunTimed in long-lived code. It doesn't let the client reclaim the connection and return it to the pool.
requestResponseIo.unsafeRunTimed(30 seconds).fold{println("No response after 30 seconds")}{println(_)}
System.exit(0)
}
case bad => println(s"Bad arguments: $bad")
}
} catch {
case wnoa: WrongNumberOfArguments => {
printUsage()
System.exit(1)
}
case x => {
x.printStackTrace()
System.exit(2)
}
}
}
def testHttpRequest(verb:Method,uri:Uri): IO[String] = {
//you shouldn't do this unless you fear no man-in-the-middle attack.
val sslContext: SslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build()
//if the proxy requires a username and password, pull them in from standard java configs here and use them below. I haven't tested that ever.
val proxyHost = Option(System.getProperty("http.proxyHost"))
val proxyPort = Option(System.getProperty("http.proxyPort")).map(_.toInt)
val config = (proxyHost,proxyPort) match {
case (Some(host),Some(port)) => {
val proxyServer = new ProxyServer.Builder(host,port).build()
Dsl.config().setSslContext(sslContext).setProxyServer(proxyServer).build
}
case (None,None) => Dsl.config().setSslContext(sslContext).build
case x => throw new IllegalArgumentException(s"Only one of http.proxyHost and http.proxyPort set $x")
}
val httpClient: Client[IO] = AsyncHttpClient[IO](config)
//Our server is picky about Accept headers. Yours may not be.
val acceptHeader = Accept(org.http4s.MediaType.`application/json`)
val request = Request[IO](
method = verb,
uri = uri,
headers = Headers(acceptHeader)
)
httpClient.expect[String](request)
}
def printUsage(): Unit = {
println(
"""Usage: ./http4sClientTest VERB URL
|
|Try the URL using the http VERB via the http4s client. This is primarily meant to test the http4s client in
|environments with https client-side proxies, and to detect other potential obsticals between a downstream node
|and a hub in a SHRINE network.
|
|Exit codes: 0 success
| 1 known error
| 2 unknown error (with an accompanying stack trace).
}
case class WrongNumberOfArguments(message:String) extends Exception(message)
}
@FunctorPrototype

This comment has been minimized.

Copy link

@FunctorPrototype FunctorPrototype commented Oct 13, 2019

To use this workaround you should add following dependencies:

  • "io.netty" % "netty-all" % nettyVersion

  • "org.asynchttpclient" % "async-http-client" % asyncClientVersion

  • "org.http4s" %% "http4s-async-http-client" % Http4sVersion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.