Skip to content

Instantly share code, notes, and snippets.

@felipecrv
Last active April 8, 2018 13:56
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 felipecrv/2687421a7f98a8b57af6e731f8a633a5 to your computer and use it in GitHub Desktop.
Save felipecrv/2687421a7f98a8b57af6e731f8a633a5 to your computer and use it in GitHub Desktop.
A convenient HTTP API Client based on the Jetty HTTP client written in Scala
package m79.infra
import java.net.URI
import java.time.format.DateTimeFormatter
import m79.infra.HttpAPIClient.Call
import org.eclipse.jetty.client.HttpClient
import org.eclipse.jetty.client.api.{Request, Result}
import org.eclipse.jetty.client.util.{BufferingResponseListener, MultiPartContentProvider, StringContentProvider}
import org.eclipse.jetty.http.HttpMethod
import org.eclipse.jetty.util.Fields
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.slf4j.LoggerFactory
import scala.concurrent.{ExecutionContext, Future, Promise}
object HttpAPIClient {
val Logger = LoggerFactory.getLogger(classOf[HttpAPIClient])
val MaxContentLength: Int = 2 * 1024 * 1024
// HTTP Date header format "Sun, 06 Nov 1994 08:49:37 GMT"
val HttpDateFormat: DateTimeFormatter = DateTimeFormatter.RFC_1123_DATE_TIME
class Call(request: Request) {
private var multiPartContentProvider: MultiPartContentProvider = null
def data(name: String, value: String): Call = {
if (multiPartContentProvider == null) {
multiPartContentProvider = new MultiPartContentProvider
}
multiPartContentProvider.addFieldPart(name, new StringContentProvider(value), null)
this
}
def param(name: String, value: String): Call = {
request.param(name, value)
this
}
def params: Fields = request.getParams
def param(name: String): Option[String] = {
Option(request.getParams.get(name)).map(_.getValue)
}
def header(name: String, value: String) = {
request.header(name, value)
this
}
/**
* Send a HTTP request and get the response content at once asynchronously.
*
* @param maxContentLength The maximum size of response content in bytes
* @return A BufferedResponseListener future providing getContent*() methods
*/
def send(maxContentLength: Int = MaxContentLength)
(implicit ec: ExecutionContext): Future[BufferingResponseListener] = {
send0(maxContentLength).map(_._2)
}
/**
* Send a HTTP request and get only the response asynchronously.
* The content is discarded.
*/
def complete(): Future[Result] = {
if (multiPartContentProvider != null) {
request.content(multiPartContentProvider)
}
val p = Promise[Result]
request.send((result: Result) => {
if (result.isFailed) {
Logger.error("API client request failed", result.getFailure)
p.failure(result.getFailure)
} else {
p.success(result)
}
})
p.future
}
def send0(maxContentLength: Int = 2 * 1024 * 1024): Future[(Result, BufferingResponseListener)] = {
if (multiPartContentProvider != null) {
request.content(multiPartContentProvider)
}
val p = Promise[(Result, BufferingResponseListener)]
request.send(new BufferingResponseListener(maxContentLength) {
override def onComplete(result: Result): Unit = {
if (result.isFailed) {
Logger.error("API client request failed", result.getFailure)
p.failure(result.getFailure)
} else {
val adapter = this
p.success((result, adapter))
}
}
})
p.future
}
}
}
class HttpAPIClient(httpClient: HttpClient) {
httpClient.setFollowRedirects(true)
def this(sslContextFactory: SslContextFactory) = this(new HttpClient(sslContextFactory))
def this() = this(new SslContextFactory)
def start = httpClient.start
def stop = httpClient.stop
def call(uri: URI, method: HttpMethod) = {
val request = httpClient.newRequest(uri).method(method)
new Call(request)
}
def call(uri: String, method: HttpMethod): Call = call(URI.create(uri), method)
def GET(uri: URI): Call = {
val request = httpClient.newRequest(uri).method(HttpMethod.GET)
new Call(request)
}
def POST(uri: URI) = {
val request = httpClient.newRequest(uri).method(HttpMethod.POST)
new Call(request)
}
def GET(uri: String): Call = GET(URI.create(uri))
def POST(uri: String): Call = POST(URI.create(uri))
}
@felipecrv
Copy link
Author

Call client.start() when the program starts and add client.stop() to the JVM shutdown hook.

Then the client can be used like this

    val call = client.GET(s"https://....")
    call.param("apikey", APIKey)
    call.param("foo", "bar")

    val future = call.send().map(result => { result.getContentAsString ... }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment