ppurang / asynch

Consume REST or HTTP endpoints. Scala http client based on scalaz Task and AsyncHttpClient.

Version Matrix

Build Status

Maven Central

Move to 0.7.20 or newer releases, now!

Sunsetting of Bintray is forcing our hand to give up on versions before 0.7.20. So please migrate to 0.7.20 or later ASAP. Checkout the releases https://github.com/ppurang/asynch/releases.

Prologue

Sbt library dependency

libraryDependencies += "org.purang.net" %% "asynch" % "0.7.20" withSources()

From Maven Central, check the badge above.

For older releases use:

resolvers += "ppurang bintray" at "https://dl.bintray.com/ppurang/maven"

Note: Soon the bintray service will be sunset and not available hence use the maven central versions. Checkout - https://search.maven.org/search?q=org.purang.net

Quick

The code below executes a blocking POST against http://httpize.herokuapp.com/post with request headers Accept: application/json, text/html, text/plain, Cache-Control: no-cache and Content-Type: text/plain, and request entity some very important message. It expects a 200 with some response body. If it encounters an exception or another status code then they are returned too. The type returned is \/[String, String]: left String (-\/[String]) indicates the error, and the right String (\/-[String]) contains the successful response body.

import org.purang.net.http._
import scalaz._, Scalaz._
import org.purang.net.http.ning.DefaultAsyncHttpClientNonBlockingExecutor
import org.asynchttpclient.DefaultAsyncHttpClientConfig

implicit val sse = java.util.concurrent.Executors.newScheduledThreadPool(2)
val config = new DefaultAsyncHttpClientConfig.Builder()
  .setCompressionEnforced(true)
  .setConnectTimeout(500)
  .setRequestTimeout(3000)
  .setCookieStore(null) //recommended as clients should be stateless
  .build()
implicit val newExecutor = DefaultAsyncHttpClientNonBlockingExecutor(config)

val response = (POST >
   "http://httpize.herokuapp.com/post" >>
   ("Accept" `:` "application/json" ++ "text/html" ++ "text/plain") ++
   ("Cache-Control" `:` "no-cache") ++
   ("Content-Type" `:` "text/plain") >>>
   "some very important message").~>(
     (x: ExecutedRequest) => x.fold(
        t => t._1.getMessage.left,
        {
          case (200, _, Some(body), _) => body.right
          case (status: Status, headers: Headers, body: Body, req: Request) => status.toString.left
        }
      ))


// close the client
// newExecutor.close()
// sse.shutdownNow()

For examples of non blocking/ asynchronous calls look at src/test/scala/NonBlockingExecutorSpec.scala

For examples of blocking calls look at src/test/scala/ExecutorSpec.scala

For an example of a custom configured executor look at src/test/scala/CustomNingExecutorSpec.scala. Here is the meat of the code:

implicit val sse = java.util.concurrent.Executors.newScheduledThreadPool(2)
val config = new DefaultAsyncHttpClientConfig.Builder()
  .setCompressionEnforced(true)
  .setConnectTimeout(500)
  .setRequestTimeout(3000)
  .setCookieStore(null) //recommended as clients should be stateless
  .build()
implicit val newExecutor = DefaultAsyncHttpClientNonBlockingExecutor(config)

Types

type FailedRequest =  (Throwable, Request)

type AResponse = (Status, Headers, Body, Request)

type or[+E, +A] = \/[E, A]

type ExecutedRequest = FailedRequest or AResponse

type NonBlockingExecutedRequest = scalaz.concurrent.Task[AResponse]

trait NonBlockingExecutor extends (Timeout => Request => NonBlockingExecutedRequest)

type ExecutedRequestHandler[T] = (ExecutedRequest => T)

Testing support? Easy.

Here is an example of test executor src/test/scala/TestExecutor.scala that looks up things in a Map used internally to test things.

Philosophy

  1. Timeouts - Yes! we do timeouts.
  2. Immutable - API to assemble requests and response handling is immutable.
  3. Easy parts are easy (if you can look beyond weird operators and operator precedence). For example a request is easy to assemble GET > "http://www.google.com" actually even the GET isn't really needed either ("http://www.host.com" >> Accept(ApplicationJson)).
  4. Full control - you are forced to deal with the exceptions and responses. You even have the request that got executed if you wanted to modify it to re-execute.
  5. Parts are done with scalaz goodness.

Limitations

  1. Too many implicits!
  2. Entity bodies can only be strings or types that can implictly be converted to strings. No endless Streams.
  3. No explicit Authentication support.
  4. No web socket or such support.
  5. Underlying http call infrastructure is as asynchronous, fast, bug-free as async-http-client.
  6. No metrics and no circuit breakers.

Help/Join

Critique is sought actively. Help will be provided keenly. Contributions are welcome. Install simple build tool 0.13+, fork the repo and get hacking.

LICENSE

licenses += ("BSD", url("http://www.tldrlegal.com/license/bsd-3-clause-license-%28revised%29"))

Disclaimer

Use at your own risk. See LICENSE for more details.