using web::Cookie
// TODO: respect domain, path, and secure attributes.
** (Middleware) - Stores cookies found in response objects and sets them in subsequent requests.
** This effectively gives you a *session* when querying web applications.
**
** 'StickyCookiesMiddleware' inspects the 'Max-Age' attribute of the cookies and automatically expires them when required.
**
** 'StickyCookiesMiddleware' does not respect the Domain, Path and Secure attributes.
class StickyCookiesMiddleware : ButterMiddleware {
internal Str:CookieData cookieData := Str:CookieData[:] { caseInsensitive = true }
@NoDoc @Deprecated { msg = "Use 'allCookies()' instead" }
Cookie[] cookies() { allCookies }
@NoDoc @Deprecated { msg = "Use 'addCookie()' instead" }
Void setCookie(Cookie cookie) { addCookie(cookie) }
** Sets the the cookie to be included in the next request
Void addCookie(Cookie cookie) {
cookieData.remove(cookie.name)
cookieData[cookie.name] = CookieData() { it.name = cookie.name; it.cookie = cookie; it.timeSet = DateTime.now }
}
** Returns a cookie by name.
** Returns 'null' if not found.
@Operator
Cookie? getCookie(Str cookieName) {
cookieData[cookieName]?.cookie
}
** Deletes a cookie by name, returning the deleted cookie.
** Returns 'null' if the cookie was not found.
Cookie? removeCookie(Str cookieName) {
cookieData.remove(cookieName)?.cookie
}
** A read only list of all cookies held.
Cookie[] allCookies() {
cookieData.vals.map { it.cookie }.ro
}
@NoDoc
override ButterResponse sendRequest(Butter butter, ButterRequest req) {
// remove any old cookies
cookieData = cookieData.exclude { it.cookie.maxAge != null && (it.timeSet + it.cookie.maxAge) <= DateTime.now }
// set request cookies - being careful not to override any user set cookies
cookies := (req.headers.cookie == null) ? [:] : Str:Cookie[:].addList(req.headers.cookie) { it.name }
cookieData.each {
if (!cookies.containsKey(it.name))
cookies[it.name] = it.cookie
}
req.headers.cookie = cookies.isEmpty ? null : cookies.vals
// the usual
res := butter.sendRequest(req)
// keep any response returned cookies
res.headers.setCookies?.each { addCookie(it) }
// we could delete old cookies now, but as long as they don't get sent back up, why duplicate work!?
return res
}
}
internal class CookieData {
Str name
DateTime timeSet
Cookie cookie
new make(|This|in) { in(this) }
}