In Bee Client, cookies are immutable objects, each comprising a name, a value and various other mostly-optional parameters. Because their identity is a compound structure consisting of the name, domain and path, there is a CookieIdentity
trait.
trait CookieIdentity {
def name: String
def domain: Domain
def path: String
def matches(cookie: CookieIdentity): Boolean = { ... }
}
CookieIdentity
is shared by two case classes: CookieKey
implements CookieIdentity
directly, whilst Cookie
does so and also provides value information. A CookieKey
can easily be given a value to become a Cookie
.
case class CookieKey (name: String, domain: Domain, path: String = "/") extends CookieIdentity {
...
}
case class Cookie (name: String,
value: String,
domain: Domain = Domain.localhost,
path: String = "/",
expires: Option[HttpDateTimeInstant] = None,
creation: HttpDateTimeInstant = new HttpDateTimeInstant(),
persistent: Boolean = false,
hostOnly: Boolean = false,
secure: Boolean = false,
httpOnly: Boolean = false,
serverProtocol: String = "http") extends CookieIdentity {
...
}
Cookie Jars
Cookies are held in immutable collections called cookie jars. Not only do cookie jars store collections of cookies, they also provide the interaction point when you make HTTP requests. You don’t have to do much except provide the existing cookie jar to each request, and then use the cookie jar from the resultant response. The response cookie jar is based on the request cookie jar, but potentially with new cookies having been added or expired cookies having been deleted. Here’s an example.
import uk.co.bigbeeconsultants.http._
import header.{Cookie, CookieJar}
object Example4a {
val cookie = Cookie(name = "XYZ", value = "zzz", domain = "localhost")
val originalCookieJar = CookieJar(cookie)
val url = "http://beeclient/test-echo-back.php"
val httpClient = new HttpClient
def main(args: Array[String]) {
val response = httpClient.get(url, Nil, originalCookieJar)
println(response.body)
val augmentedCookieJar = response.cookies.get
println(augmentedCookieJar)
println(augmentedCookieJar.get("XYZ").get.value) // prints "zzz"
}
}
We firstly create our original cookie jar containing just one cookie (an empty cookie jar would often be used instead). Then we make a GET request. Because the request is to localhost and the cookie has the same domain, this cookie will be sent as a ‘Cookie’ header with the request.
The response comes back and may include ‘Set-Cookie’ headers for us; these headers may add or alter or delete cookies. Our original cookie jar has these changes applied to it, ending up with an augmented cookie jar that is included in the response.
Finally, we should throw away our original cookie jar and keep the augmented one for the next request. You may find it useful to have a ‘var’ here, although be careful if you share the ‘var’ between multiple threads.
var cookieJar = CookieJar(...)
... other stuff
val response = httpClient.get(url, Nil, cookieJar)
cookieJar = response.cookies.get
One thing to bear in mind is that you only get a cookie jar in the response if there was a cookie jar in the request, which is optional. When you make requests without a cookie jar, the response cookies field will always be None
. If you want to make a request and you haven’t got any cookies to send, you need to use CookieJar.Empty
in the request so that the ‘Set-cookie’ headers will be processed.
In summary,
- if
request.cookies
isNone
, thenresponse.cookies
is alsoNone
; - if
request.cookies
isSome(cookieJar)
, thenresponse.cookies
will also beSome(cookieJar)
, but it might be different from the request’s jar.
Remember, in the second case, you can simply start with Some(CookieJar.Empty)
the first time if you want.
What’s in a CookieJar?
A cookie jar is essentially an immutable list of cookies. It looks like this
case class CookieJar (cookies: List[Cookie]) extends Iterable[CookieIdentity] {
...
}
You can find a particular cookie using the get
, contains
, filter
, filterNot
or find
operations, such as
val cookie: Option[Cookie] = cookieJar.get("X1")
val foundX1: Option[Cookie] = cookieJar.find (_.name == "X1")
val filteredX1: Iterable[Cookie] = cookieJar.filter (_.name == "X1")
Automatic Cookie Processing
All the above describes cookie processing in detail, and is needed every time you use HttpClient.
However, to make the cookie round-trip much easier for those cases that make many requests that need cookies, use the HttpBrowser instead of HttpClient
.
A later section describes this in detail.