Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active June 25, 2023 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dacr/24d90e8158a09ea928b214506012b605 to your computer and use it in GitHub Desktop.
Save dacr/24d90e8158a09ea928b214506012b605 to your computer and use it in GitHub Desktop.
Fully asynchronous http client call with json response using akka-http will work in all cases, even with chunked responses, this example add automatic http proxy support. / published by https://github.com/dacr/code-examples-manager #a9b24ee9-78bd-4810-bda8-df5182ea01c7/dde96c15b0d3e7ae35ac094ce0b93ea4d328cff2
// summary : Fully asynchronous http client call with json response using akka-http will work in all cases, even with chunked responses, this example add automatic http proxy support.
// keywords : scala, actors, akka, http-client, client, json, json4s, http-proxy
// publish : gist
// authors : David Crosson
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2)
// id : a9b24ee9-78bd-4810-bda8-df5182ea01c7
// created-on : 2019-01-25T14:06:58Z
// managed-by : https://github.com/dacr/code-examples-manager
// execution : scala ammonite script (http://ammonite.io/) - run as follow 'amm scriptname.sc'
/*
Fully asynchronous json http client calls (get and post) using akka-http and json4s
with automatic proxy configuration
---
This script execute the corresponding curl JSON http requests :
```bash
curl -s -X GET -H "Content-Type: application/json" http://httpbin.org/json | jq .slideshow.title
curl -s -X POST -d '{"name":"John Doe", "age":42}' -H "Content-Type: application/json" http://httpbin.org/post | jq .json
```
for local execution :
```
docker run -d -p 8044:80 kennethreitz/httpbin
curl -v -X POST -d '{"name":"John Doe", "age":42}' -H "Content-Type: application/json" http://127.0.0.1:8044/post | jq .json
```
---
scala in script mode using ammonite REPL (http://ammonite.io/)
`amm scriptname.scala` to start it
*/
import $ivy.`com.typesafe.akka::akka-http:10.2.4`
import $ivy.`com.typesafe.akka::akka-stream:2.6.13`
import $ivy.`de.heikoseeberger::akka-http-json4s:1.35.3`
import $ivy.`org.json4s::json4s-jackson:3.6.11`
import java.net.InetSocketAddress
import akka.http.scaladsl._
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import akka.http.scaladsl.settings.{ClientConnectionSettings, ConnectionPoolSettings}
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.util.ByteString
import scala.concurrent._
import scala.concurrent.duration._
import scala.util.{Failure, Success}
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._
import org.json4s.{DefaultFormats, JValue}
import org.json4s.jackson.JsonMethods._
import org.json4s._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Properties.{envOrNone, propOrNone}
case class HttpProxyConfig(host: String, port: Int)
object HttpProxyConfig {
def fromPropOrEnv(): Option[HttpProxyConfig] = {
val keys = Stream("http_proxy", "https_proxy")
keys.flatMap{key =>
propOrNone(key).orElse(envOrNone(key)).flatMap(fromProvidedString)
}.headOption
}
def fromProvidedString(spec: String): Option[HttpProxyConfig] = {
val extractor = """^https?://([^:/]+)(?::(\d+))?/?$""".r
extractor.findFirstIn(spec.trim.toLowerCase()).collect {
case extractor(host, null) => HttpProxyConfig(host, 80)
case extractor(host, port) => HttpProxyConfig(host, port.toInt)
}
}
}
object TestThat {
implicit val system = akka.actor.ActorSystem("MySystem")
implicit val executionContext = system.dispatcher
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats
private val transport =
HttpProxyConfig.fromPropOrEnv().collect {
case proxy@HttpProxyConfig(proxyHost, proxyPort) =>
println(s"using proxy $proxy")
ClientTransport.httpsProxy(InetSocketAddress.createUnresolved(proxyHost, proxyPort))
}
private val connectionSettings =
ClientConnectionSettings(system)
.withTransport(transport.getOrElse(ClientTransport.TCP))
.withIdleTimeout(10.seconds)
.withConnectingTimeout(5.seconds)
private val settings =
ConnectionPoolSettings(system)
.withConnectionSettings(connectionSettings)
.withMaxConnections(10)
.withMaxRetries(1)
def jsonGet(requestUri: String): Future[JValue] = {
val request = HttpRequest(uri = requestUri)
val futureResponse = Http().singleRequest(request, settings = settings)
futureResponse.flatMap { response => Unmarshal(response.entity).to[JValue] }
}
def jsonPost(requestUri: String, jvalue: JValue): Future[JValue] = {
Marshal(jvalue).to[RequestEntity].flatMap { entity =>
val request = HttpRequest(method = HttpMethods.POST, uri = requestUri, entity=entity)
val futureResponse = Http().singleRequest(request, settings = settings)
futureResponse.flatMap { response => Unmarshal(response.entity).to[JValue] }
}
}
def printJson(message:String, jvalue: JValue): Unit = {
println(message+compact(render(jvalue)))
System.out.flush()
}
val httpbin="http://httpbin.org"
//val httpbin="http://localhost:8044" // if 'docker run -d -p 8044:80 kennethreitz/httpbin' is running
val f1 = jsonGet(s"$httpbin/json")
f1.onComplete {
case Success(jvalue) => printJson("f1=", jvalue \ "slideshow" \ "title")
case Failure(ex) => ex.printStackTrace()
}
val jsonData = Extraction.decompose(Map("age" -> 42))
val f2 = jsonPost(s"$httpbin/post", jsonData)
f2.onComplete {
case Success(jvalue) => printJson("f2=", jvalue \ "json")
case Failure(ex) => ex.printStackTrace()
}
// Do not exit before the future has completed ;)
def andWait(): Unit = Await.ready(Future.sequence(List(f1, f2)), 10.seconds)
}
TestThat.andWait()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment