Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-httpclient

Named HTTP clients for Pudu: a client factory, delegating handlers, pooled keep-alive connections, handler lifetimes, logging, and resilience

0.1.1Apache-2.01

InstallClose

Cookies.pudu

Pudu84 lines3.3 KB

GitHub ↗
1/** @HttpClient.Cookies.Module — a jar of cookies shared by the requests of one transport */2module PuduLangHttpClient.Cookies34import Std.Json as Json5import Std.Time as Time6import PuduLangHttpClient.Content as Content7import PuduLangHttpClient.Domain.Cookie as Cookie8import PuduLangHttpClient.Domain.Uri as Uri9import PuduLangHttpClient as HttpClient10import PuduLangHttpClient.Utils.Shared as Shared1112/** @HttpClient.Cookies.Jar — the cookies stored so far, shared between threads */13export type Jar = { held: Shared.Shared[Array[Cookie.Stored]] }1415/// An empty jar.16export fn jar() -> Jar { Jar{held: Shared.shared([])} }1718/// Stores the cookies a response from `uri` set through its `set-cookie` values.19export fn store(target: &Jar, uri: Str, values: &Array[Str]) -> () {20  if let Some(endpoint) = Uri.endpointOf(uri) {21    let now = wallClock()22    let path = Uri.split(uri).path23    for value in values {24      if let Some(cookie) = Cookie.parse(value, endpoint.host, path, now) {25        Shared.update(&target.held, |held: Array[Cookie.Stored]| Cookie.store(&held, &cookie, now))26      }27    }28  }29}3031/// Adds one cookie for `uri`, as though its response had set it.32export fn add(target: &Jar, uri: Str, name: Str, value: Str) -> () { store(target, uri, &[name + "=" + value]) }3334/// The cookies a request to `uri` carries.35export fn cookiesFor(source: &Jar, uri: Str) -> Array[Cookie.Stored] {36  match Uri.endpointOf(uri) {37    case Some(endpoint) => {38      let now = wallClock()39      let path = Uri.split(uri).path40      Shared.current(&source.held).filter(|cookie: Cookie.Stored| Cookie.applies(&cookie, endpoint.host, path, endpoint.secure, now))41    }42    case None => []43  }44}4546/// The `cookie` header value a request to `uri` carries, or `None` when no cookie applies.47export fn header(source: &Jar, uri: Str) -> Option[Str] {48  let applying = cookiesFor(source, uri)49  if applying.isEmpty() { None } else { Some(Cookie.render(&applying)) }50}5152/// Every cookie that has not expired.53export fn cookies(source: &Jar) -> Array[Cookie.Stored] {54  let now = wallClock()55  Shared.current(&source.held).filter(|cookie: Cookie.Stored| !Cookie.expired(&cookie, now))56}5758/// Removes every cookie.59export fn clear(target: &Jar) -> () { Shared.update(&target.held, |_held: Array[Cookie.Stored]| []) }6061/// Removes every cookie without an expiry, which lasts only as long as the session.62export fn clearSession(target: &Jar) -> () {63  Shared.update(&target.held, |held: Array[Cookie.Stored]| held.filter(|cookie: Cookie.Stored| Cookie.isPersistent(&cookie)))64}6566/// Every cookie that lasts beyond the session, as JSON a later `restore` reads back.67export fn save(source: &Jar) -> Str {68  Json.encode(&cookies(source).filter(|cookie: Cookie.Stored| Cookie.isPersistent(&cookie)).encode())69}7071/// Stores every unexpired cookie a `save` wrote, replacing cookies of the same name, domain, and72/// path; text that is not a saved jar is `Unreadable`.73export fn restore(target: &Jar, saved: Str) -> HttpClient.Outcome[()] {74  let restored: Array[Cookie.Stored] = Content.readAs(&Content.plain(saved)) ?75  let now = wallClock()76  for cookie in restored {77    Shared.update(&target.held, |held: Array[Cookie.Stored]| Cookie.store(&held, &cookie, now))78  }79  Ok(())80}8182/// Milliseconds since the Unix epoch, the clock cookie expiries are written in.83fn wallClock() -> Int { Time.toMillis(&Time.currentInstant()) }84