Just Requests

The basic dispatch documentation contains decent material on creating requests. A request, and underneath the async-http-client request object, needs a method and target url, headers and optionally a body.

dispatch provides several ways to create these requests. There is even a periodic table that was built to describe the different methods that are sometimes encoded using symbols here.

async-http-client creates requests using a RequestBuilder which is a mutable object that you call methods on to mutate. That's not as functional as would be desired so dispatch's Req objects wraps a builder object much in the same way Http wraps an async-http-client object. A "function" is added to the request that configures the underlying request in some way. Once you issue a request in dispatch, a real async-http-client request object is created using the async-http-client request builder and the chain of functions to configure the request are run. This makes the scala dispatch Req object immutable. Because they are immutable, you can create a basic request that has the configuration you need for your program and use that request a trusted base to derive other requests.

Creating the Basic Request

The default Req in dispatch performs some checks so that if you just added some text as the request body but never set the content type, it will set the content type for you.

You always start defining a request by setting the host or url that you want to target. dispatch only provides this as this starting point for all requests. You can create the basic request a few different ways although they produce the same request concept:

 host("localhost", 9000) 
res11: Req = Req(<function1>, Properties(NoBody))
@ :/("localhost") 
res12: Req = Req(<function1>, Properties(NoBody))
@ url("localhost") 
res13: Req = Req(<function1>, Properties(NoBody))
@ :/("localhost", 9000) 
res14: Req = Req(<function1>, Properties(NoBody))

Once you have a basic request, you can continue to modify the URL through dispatch's UrlVerbs:

@ host("localhost") / "blah" 
res15: Req = Req(<function1>, Properties(NoBody))
@ host("localhost") / "blah" / "hah/nah" 
res16: Req = Req(<function1>, Properties(NoBody))

and so on. Note that characters embedded in the string parameter will be escaped. If you need the url from a request, just call yourrequest.url. To ensure that https is used (SSL) add .secure:

@ (host("localhost") / "blah" / "hah/nah").secure.url 
res18: String = "https://localhost/blah/hah%2Fnah"

You can see from the above that the http was change (or in this case added) to https. This will use SSL. If you need to configure the client ot use specific a SSL configration, you will need to configure the Http client.

A convenience function is provided to add path segments from Option[String]:

 (host("localhost:9000") /? Some("blah")).url 
res19: String = "http://localhost:9000/blah"
@ (host("localhost:9000") /? None).url 
res20: String = "http://localhost:9000/"

Once you create a request you can continue to add path segments to it or change the URL entirely. That way, you can create your base request with the base URL and customize it for each call as needed by adding the complete path prior to sending it to the server.

More Request Building: Method

Using various combinators, called "verbs" in dispatch, you build up your request object using functions that take Req and return a Req object. You can do this since requests are mutable. You build up your requests to add a method, headers and a body. The default request as you saw above has a property of NoBody which is an internal property for the Req object that helps with some default setting. You can mostly ignore it.

So once you have a Req object you use combinators to further customize it until you have your basic request object or you are ready to issue it using dispatch's Http client.

Using combinators, you con change your request to have different methods:

@ host("http://localhost:9000").HEAD 
res21: Req = Req(<function1>, Properties(NoBody))
@ host("http://localhost:9000").HEAD.toRequest 
res22: com.ning.http.client.Request = http://http//localhost:9000/    HEAD    headers:
@ host("http://localhost:9000").OPTIONS.toRequest 
res23: com.ning.http.client.Request = http://http//localhost:9000/    OPTIONS    headers:
@ host("http://localhost:9000").POST.toRequest 
res24: com.ning.http.client.Request = http://http//localhost:9000/    POST    headers:

I have used toRequest which converts the Req object to a async-http-client request object so it prints out more clearly. You can see from the first .HEAD that the default string output just shows that Req is built around a function that configures a basic async-http-client request object once one is provided to Req. You should not need to use .toRequest in your production code.

You can also set the method using a traditionally named method call .setMethod.

There are a few, not many, methods that set various aspects of a request and automatically change the request method type e.g. setting a parameter on a GET request automatically converts it to a POST method.

More Request Building: Headers and Cookies

In addition to a method, you need to add a header. There are multiple methods to add headers.

The basic approach to add headers is:

@ host("http://localhost:9000").addHeader("Content-Type", "application/soap+xml").toRequest 
res32: com.ning.http.client.Request = http://http//localhost:9000/    GET    headers:    Content-Type:application/soap+xml
@ (host("http://localhost:9000") <:< Map("Content-Type" -> "application/soap+xml")).toRequest 
res34: com.ning.http.client.Request = http://http//localhost:9000/    GET    headers:    Content-Type:application/soap+xml

You can use addHeader or <:< which can many headers from a Map at once.

If you use .setHeader or .setHeaders you will be setting the headers explicitly and wipe out any other headers.

The special header Content-Type can be set, if you desired to set it this way, using:

@ host("http://localhost:9000").setContentType("application/soap+xml", "utf-8").toRequest 
res43: com.ning.http.client.Request = http://http//localhost:9000/    GET    headers:    Content-Type:application/soap+xml; charset=utf-8

There is only one way to add a cookie:

@ import com.ning.http.client.cookie.Cookie
@ val cookie = new Cookie("JSESSION", "1234", "", ".github.com", "/", 219384848, -1, true, true) 
cookie: Cookie = JSESSION=; domain=.github.com; path=/; expires=219384848; secure; HTTPOnly
@ host("http://localhost:9000").addCookie(cookie).toRequest 
res41: com.ning.http.client.Request = http://http//localhost:9000/    GET    headers:

More Request Building: Query Parameters and Parameters

There are a few Req methods to add URL parameters.

@ (host("http://localhost:9000").GET <<? Map("name" -> "foo")).toRequest 
res45: com.ning.http.client.Request = http://http//localhost:9000/?name=foo    GET    headers:
@ (host("http://localhost:9000").GET <<? Map("name" -> "foo", "answer" -> "maybe")).toRequest 
res46: com.ning.http.client.Request = http://http//localhost:9000/?name=foo&answer=maybe    GET    headers:
@ host("http://localhost:9000").GET.addQueryParameter("name" , "foo").toRequest 
res47: com.ning.http.client.Request = http://http//localhost:9000/?name=foo    GET    headers:

The <<? with the ? suggests query parameters versus POST parameters. All of the query parameters can be set explicitly and any existing parameters removed using .setQueryParameters:

@ host("http://localhost:9000").GET.addQueryParameter("name" , "foo").setQueryParameters(Map("answers" -> Seq("maybe,couldbe,havetobe"))).toRequest 
res49: com.ning.http.client.Request = http://http//localhost:9000/?answers=maybe%2Ccouldbe%2Chavetobe    PUT    headers:

You can see that in .setQueryParameters you model multiple query parameter values as a sequence and set it explicitly unlike some of the other APIs for setting query parameters.

POST parameters typically go into the body. If you add parameters (not query parameters) dispatch will change your GET to a POST automatically:

@ (host("http://localhost:9000").GET << Map("answers" -> "maybe")).toRequest 
res52: com.ning.http.client.Request = http://http//localhost:9000/    POST    headers:    formParams:    answers:maybe

You can also use .addParameter and .setParameters just like with the query parameters.

If you need a valueless query parameter e.g. yoururl?myparam instead of a youurl?myparam=somevalue you can use null as the value for the query parameter value in the map (<<? Map("myparam" -> null). It's very unscala but I think its fine here because its just a marker value and null is not being used in logic per se. We should probably have a <<? method that handles Map[String, Option[String]] but that map might be pain to create all the time.

More Request Building: Body

Setting the body is important of course. You can add a string to the body quite easily:

 (host("http://localhost:9000").GET << "my body").toRequest 
res57: com.ning.http.client.Request = http://http//localhost:9000/    POST    headers:    Content-Type:text/plain; charset=UTF-8

You should notice that we made the initial request a GET and it was turned into a POST. If the initial request was a PUT it would remain a PUT. The .toRequest does not print out the body but you can also see that with a string, dispatch automatically set the content ype to text/plain.

There are a few explicit methods such as .setBody to set the body and they work as expected or to even set a body part if your request is multi-part.

async-http-client also has the concept of a body generator, which can produce a body as needed while allowing the client to control buffering:

 public interface BodyGenerator {
    Body createBody() throws IOException;
}

public interface Body {
    long getContentLength();
    long read(ByteBuffer buffer)
      throws IOException;
    void close() throws IOException;
}

We do not show an example of this in these notes but the intent is clear. You can also get the body, as a string, from a file using dispatch's convenience function Req.<<< as in myrequest <<< yourfile or .setBody(yourfile).

More Request Building: Misc

You can modify the request with additional features such as:

  • proxy server: .setProxyServer(...)

  • virtual host: .setVirtualHost(...)

  • realm: .setRealm(...)

  • authentication: An example is host(yourhost).as(user, password)

See the async-http-client docs for the parameters of each call.

Last updated