Setting and reading cookies

Picos can be used with a variety of event generators. When the event generator is a browser, it is useful for a pico to be able to set a cookie in the browser. Then, rules reacting to later events, and queries responding to requests from that same browser will have access to the value of the cookie.

This is one way that picos can pass information from one to another. For example, a pico which does subscriptions might set its wellKnown_Tx channel id in a cookie. Then, events sent to  other picos from the same browser would make that information be, well, well known. Such other picos would then be in a position to propose subscriptions to the first pico using that channel id.

experimental feature in version 1.0.11

This feature is recent, so you will need to do a git pull after updating to version 1.0.10, or wait until the feature has been included in version 1.0.11 (or higher)

Setting a cookie

Here is a KRL rule which causes a cookie to be set in the browser which, as an event generator, sends a matching event:

  rule set_cookie {
    select when cookie needed
    pre {
      eci = wrangler:channels(["wellKnown_Rx","Tx_Rx"]).head(){"id"} || meta:eci;
    every {
      send_directive("_cookie",{"cookie": <<wellKnown_Rx=#{eci}; Path=/sky>>});

Note that this assumes (line 4) that the set_cookie rule is part of a ruleset which uses io.picolabs.wrangler as a module (see below). Line 4 computes an ECI which is either the well known one for subscriptions (if that ruleset is installed in this pico), or the ECI used to send the cookie:needed event (see meta, the library).

Line 7 sends a directive with a special name, _cookie. At the conclusion of event processing, the pico engine interprets this directive, and will include the appropriate HTTP "Set-Cookie" header. Upon receipt of the response including this header, a browser will remember the cookie name and value, for future interaction with the same pico engine hostname. 

The use of the wrangler module would be indicated in the meta section of the ruleset by:

    use module io.picolabs.wrangler alias wrangler

Conditional setting of a cookie

An alternative, which sets a cookie for wellKnown_Rx only when the pico actually does subscriptions would be:

  rule set_cookie {
    select when cookie needed
    pre {
      eci = wrangler:channels(["wellKnown_Rx","Tx_Rx"]).head(){"id"};
    if eci then every {
      send_directive("_cookie",{"cookie": <<wellKnown_Rx=#{eci}; Path=/sky>>});

Reading a cookie

Every event includes an attribute, named _headers, which is all of the HTTP headers (available only when the event generator is a browser or other user agent communicating over HTTP). Similarly, in such cases every query will include an argument named _headers giving the function access to the HTTP headers.

Here is a KRL function which produces a map from cookie names to values:

    cookies = function(_headers) {
      arg = event:attr("_headers") || _headers;
      arg{"cookie"}.isnull() => {} |
      arg{"cookie"}                      //.klog("cookie")
        .split("; ")                     //.klog("split")
        .map(function(v){v.split("=")})  //.klog("map1")
        .collect(function(v){v.head()})  //.klog("collect")
        .map(function(v){v.head()[1]})   //.klog("map2")

Line 1 mentions the argument name _headers for the case in which this function is called directly through a /sky/cloud query.

Line 2 binds the name arg to either the event attribute named _headers or the argument named _headers so that, whether it is called via a query or from a rule which is reacting to an event, it will be a map of the HTTP headers.

If the HTTP headers do not exist, or they do not contain an entry for cookie, then this function returns an empty map, meaning there are no cookies available (line 3).

Otherwise the function continues with a chain of operators (lines 4-8) computing the desired map from cookie names to values.

Sample output from the klog calls suggested in the comments, for a particular function invocation shown here:

cookie  "wellKnown_Rx=4MVMwL7AsyqRfSRjGcuruQ; sessionid=38afes7a8"
split   ["wellKnown_Rx=4MVMwL7AsyqRfSRjGcuruQ","sessionid=38afes7a8"]
map1    [["wellKnown_Rx","4MVMwL7AsyqRfSRjGcuruQ"],["sessionid","38afes7a8"]]
collect {"wellKnown_Rx":[["wellKnown_Rx","4MVMwL7AsyqRfSRjGcuruQ"]],"sessionid":[["sessionid","38afes7a8"]]}
map2    {"wellKnown_Rx":"4MVMwL7AsyqRfSRjGcuruQ","sessionid":"38afes7a8"}

This chain of operators is very similar to the one used to decode WWW form-encoded parameters except that cookies are separated by a semi-colon and space.

Reading a cookie using /sky/cloud

Copyright Picolabs | Licensed under Creative Commons.