Last active
June 21, 2021 16:02
-
-
Save dwalend/3f8439e882cd4430b0e266675b3fba94 to your computer and use it in GitHub Desktop.
http4s client that can work through a client-side https proxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
A better work-around for us was: https://jdk-http-client.http4s.org/stable/ (Not available back in 2019.)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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