CHAPTER 7

image

Persisting JSON: I

In Chapter 5, you learned how JSON.stringify captures the data possessed by an identified JavaScript value. This occurs by reverse engineering the specified target into its literal form, in accordance with the JSON grammar, thus capturing the current state of a model for a particular application as JSON text. You further learned that JSON.parse taps into the innate ability of the JavaScript engine to “parse” the literals that make up a valid JSON text. This revives the state from a previous model for use within the existing session.

To illustrate how to use JSON.parse, each example in Chapter 6 was preceded by the stringify method, in order to provide something to be parsed. Furthermore, this was meant to illustrate the lifecycle of how one method gives rise to the other.

While this is sufficient for the purposes of a demonstration, it will be rare to parse data immediately after it has been serialized by our application. This would result in a very linear and limited use case. These two methods really shine, however, when they are paired with data persistence. It is the persistence of data that enables both methods, stringify and parse, to be used independently of each other. This offers an application many more use-case scenarios. This contrast is illustrated in Figure 7-1.

9781484202036_Fig07-01.jpg

Figure 7-1. Contrast between use-case scenarios

Computer science defines the persistence of data as a state that continues to exist after the process from which it was created.1 Much like the phrase, “you can’t step in the same spot of a moving river twice,” the process that serializes data will cease to exist the moment the JSON text is produced and the function that ran the process is exited. Therefore, in order to utilize the produced JSON beyond the given process that created it, it must be stored for later retrieval.

Believe it or not, in the examples in Chapter 5, we were using a slight form of data persistence, according to the aforementioned definition. When the stringify method exited, the produced JSON returned by each example was able to continue to be referenced by the application. This is because we had assigned it as the value to a variable, which was often labeled JSONtext. Therefore, we managed to persist JSON by definition. However, if we were to navigate away from the application at any point in the course of running the Chapter 5 examples within a browser, the variable JSONtext would cease to persist, and the JSON it was assigned would be lost as well.

Because the Internet was founded atop a request-and-response protocol, each request made of a server, regardless of whether it’s for .html, .jpg, .js, etc., occurs without consideration of any previous or subsequent requests by the same visitor. This is even if requests made are to the same domain. What is returned from the server is simply the fulfillment of the resource requested. Over the years, many a developer has needed to be able to string together the isolated requests of a common server, in order to facilitate things such as shopping carts for e-commerce. One of the technologies that was forged from this requirement brought forth a technique that we will leverage in order to achieve the persistence of JSON. That technology is the HTTP cookie.

HTTP Cookie

As was previously mentioned, the HTTP/1.1 protocol is incapable of persisting state; therefore, it becomes the duty of the user-agent to manage this undertaking. The HTTP cookie, or cookie for short, was created as a means to string together the actions taken by the user per “isolated” request and provide a convenient way to persist the state of one page into that of another. The cookie is simply a chunk of data that the browser has been notified to retain. Furthermore, the browser will have to supply, per subsequent request, the retained cookie to the server for the domain that set it, thereby providing state to a stateless protocol.

The cookie can be utilized on the client side of an application with JavaScript. Additionally, it is available to the server, supplied within the header of each request made by the browser. The header can be parsed for any cookies and made available to server-side code. Cookies provide both front-end and back-end technologies the ability to collaborate and reflect the captured state, in order to properly handle each page view or request accordingly. The ability to continue to progress the state from one page to another allows each action to no longer be isolated and, instead, occur within the entirety of the user’s interaction with a web site.2

Like JSON, cookies possess a specification and protocol all their own. By understanding its syntax, we can tap into the persistence of the HTTP cookie and, by extension, persist JSON for later use with an application. The great news is that HTTP cookies are extremely simple, in addition to being recognized by all major browsers dating back to Internet Explorer 3.

Syntax

At its most atomic unit, the cookie is simply a string of ASCII encoded characters composed of one or more attribute-value pairs, separated by a semicolon (;) token. Listing 7-1 outlines the syntax for the HTTP cookie.

Image Note  ASCII is short for “American Standard Code for Information Interchange” and is composed of 128 characters, which are letters from the English alphabet, digits 0–9, basic punctuation, and a few control characters.

Listing 7-1. Set-Cookie Syntax as Defined by RFC 6265

set-cookie      =       "Set-Cookie:" cookies
cookies         =       1#cookie
cookie          =       NAME "=" VALUE *(";" cookie-av)
NAME            =       attr
VALUE           =       value
cookie-av       =       "expires" "=" value
                |       "max-age" "=" value
                |       "domain" "=" value
                |       "path" "=" value
                |       "secure"
                |       "httponly"

Listing 7-1 uses the grammar defined by the HTTP/1.1 specification to outline the syntax of the HTTP cookie. In order to understand the syntax, I would like to direct your focus to the line cookie = NAME "=" VALUE *(";" cookie-av). This line outlines the entire syntax of the cookie. We will dissect this line in two passes. The first half will regard only cookie = NAME "=" VALUE. This portion of the syntax outlines the following: “Set some cookie specified by the indicated NAME, to possess the assigned VALUE.” A cookie, in short, is nothing more than a key/value pair.

As with all key/value pairs, it will be the purpose of the “key” represented by NAME to both identify as well as provide the means to access an assigned value. VALUE, on the other hand, represents the data or state that’s intended to be persisted for the application. To ensure a cookie is stored uniformly among all browsers, it will be imperative that both NAME and VALUE be made up of valid ASCII characters, such as those shown in Listing 7-2.

Listing 7-2. Key/Value Pairs Intended to Be Persisted As a Cookie Must Both Be Valid ASCII Characters

"greetings=Hello World!";
"greetingJSON=["Hello World!"]";

Image Note  Safari as well as Internet Explorer do not correctly handle cookies that contain non-ASCII characters.

While the tokens that make up JSON text are valid ASCII characters, the values held within are not limited to ASCII but, rather, UTF-8. Therefore, if the characters that are represented in your application fall outside of the ASCII range, it will be necessary to encode your UTF-8 characters with Base64 encoding. Two libraries you can use for this purpose are https://jsbase64.codeplex.com/releases/view/89265 and https://code.google.com/p/javascriptbase64/. While both utilize different namespaces, Base64 and B64, they both rely on the same methods to encode and decode. Either of these libraries will be capable of converting your non-ASCII values into ASCII-encoded values. Listing 7-3 demonstrates the use of one of the aforementioned Base64 libraries by converting the characters of our string of UTF-8 characters into those of ASCII, in order to be compliant with the HTTP cookie syntax.

Listing 7-3. UTF-8 Characters Being Converted into ASCII Using a Base64 Library

var unicodeValue = "привет мир!";  // Hello World! in Russian;
var asciiString = Base64.encode( JSON.stringify( unicodeValue ) );
console.log(asciiString); // "ItC/0YDQuNCy0LXRgiDQvNC40YAhIg=="
var decodedValue = Base64.decode( asciiString );
console.log( decodedValue );  // "привет мир!"

The second half of the line in review, *(";" cookie-av), explains that our cookie can be supplied a sequence of any of the six optional cookie attribute-value pairs, as required by an application. The token that must separate them from their successor in the string is the semicolon (;). While it is not necessary to supply whitespace characters between the semicolon and the attribute value, it will aid to keep your code clean and legible. The possible cookie-av values are listed in Listing 7-1 as "expires", "max-age", "domain", "path", "secure", and "httponly". Each attribute value defines a specific scope to the defined cookie.

expires

The expires attribute is quite literally the “key,” pun intended, to the duration over the persistence of the specified cookie. Should the expires attribute be specified, its value counterpart will inform the browser of the date and time it is no longer necessary to further store said cookie. The value supplied is required to be in UTC Greenwich Mean Time format. Being that UTC GMT is a standard, we can achieve this value with ease, by way of the built-in methods of the Date object as demonstrated in Listing 7-4.

Listing 7-4. toUTCString Produces a UTC Greenwich Mean Time Value

var date= new Date("Jan 1 2015 12:00 AM");
var UTCdate= date.toUTCString() ;
console.log( UTCdate );  // "Thu, 01 Jan 2015 06:00:00 GMT"

Listing 7-4 initiates a date instance with the supplied string of January 1, 2015. Furthermore, the time is set to exactly 12 AM. Utilizing date’s built-in method, toUTCString, the date and time it represents is translated into its GMT equivalent and then returned to the caller of the method. When we log that value, we can clearly note that the date has been converted, as it is signified by the appended abbreviation GMT. If you were to run the code from Listing 7-4, you might receive a different value. That is because the JavaScript Date Object correlates to your location and time zone. Nevertheless, the date and time that you specify will be equal to the difference in time zone between your location and Greenwich.

If we were to assign the date from Listing 7-4 to our author cookie in Listing 7-5, the cookie would be available until exactly Thursday, 12:00 AM January 1, 2015, or Thursday, 01 Jan 2015 06:00:00 Greenwich Mean Time.

Listing 7-5. Appending Date to the Key/Value Pair to Provide an Expiration

var date= new Date("Jan 1 2015 12:00 AM");
"author=test; expires="+ date.toUTCString();

If the value supplied to the expires attribute occurs in the past, the cookie is immediately purged from memory. On the other hand, if the expires attribute is omitted, then the cookie will be discarded the moment the session has ended. Essentially, the browser would continue to persist the cookie only as long as the session remained open.

It used to be that the moment you exited the browser, all sessions were immediately closed. Today, however, it’s worth noting that sessions may persist well after the browser is exited. This is due to the specific features that vendors have incorporated into their browsers, such as restoring previously viewed pages/tabs if the browser crashes. Additionally, they provide us the ability to restore pages/tabs from History. Therefore, session cookies may continue to persist in memory longer than expected.

As we will be looking to persist our JSON indefinitely, we will almost always supply an expires attribute value to our cookies.

max-age

The max-age attribute, like the expires attribute, specifies how long a cookie should persist. The difference between the two is that max-age specifies the life span of the cookie in seconds. While the max-age attribute is defined by the original specification and continues to exist today, it is not an attribute that is acknowledged by Internet Explorer 6 through 8. That said, it will be wise to favor the expires attribute and ignore max-age.

domain

The domain attribute explicitly defines the domain(s) to which the cookie is to be made available. However, the domain specified must somehow possess a relationship to the origin setting the cookie. In other words, if www.sandboxed.guru is setting a cookie, it cannot supply apress.com as the domain. This would prove to be a huge security concern, if it were possible to set cookies for other domains.

It is the responsibility of the browser to make available, to both JavaScript and the server, all cookies whose supplied domain attribute matches that of the domain of the visited URL. To ensure that the domains match, the browser will compare the two. This comparison can be illustrated with a regular expression (see Listing 7-6).

Listing 7-6. Using a Regular Expression to Demonstrate Matching Origins

var regExp=(/www.sandboxed.guru$/i).test('www.sandboxed.guru'),  //true

Listing 7-6 defines a pattern that matches against the tail end of a host domain. The pattern www.sandboxed.guru represents the cookie’s assigned domain attribute. The $ token further specifies that the pattern explicitly ends with .guru. This is necessary to prevent the cookies of sandboxed.guru from being available to another domain that might just so happen to possess our origin within its subdomain. This would be quite the security risk. Note the difference between the URLs sandboxed.guru and guru.com. They are two entirely different domains. Now consider what might occur if guru.com were to use the following subdomain: www.sandboxed.guru.com (see Listing 7-7).

Listing 7-7. Matching URLs are Determined Through the Top Level Domain (.com)

(/sandboxed.guru/i).test('sandboxed.guru.com'),  //true
(/sandboxed.guru$/i).test('sandboxed.guru.com'), //false

Listing 7-7 demonstrates that without specifying the $ to force a tail-end match, two completely different properties could potentially be considered a match.

Image Note  To prevent possible matches that could exist within subdomains, browsers explicitly check that a match must end with the appropriate top-level domain.

The i simply informs the pattern to remain case-insensitive during the match. If the domain attribute and the server domain are determined to be a match, then for each HTTP request, any and all cookies will be sent to the server and made available to the JavaScript application of each page.

The domain attribute is optional, but for security purposes, one must be set. By default, the domain attribute will be set to the absolute origin that the cookie is set from. This can be slightly limiting if you have subdomains that require visibility of these cookies, or vice versa. Consider a domain attribute that is defaulted to www.sandboxed.guru for a particular cookie. That cookie will never be available to sandboxed.guru because of the preceding www. Similarly, if the domain attribute is defaulted to sandboxed.guru, that cookie will not be visible to json.sandboxed.guru.

However, by assigning the domain attribute value, we have the ability to broaden the scope of our cookies. For instance, if we specify a domain attribute as the top-level domain, preceded by the . token (.sandboxed.guru), the domain attribute would match not only a top-level domain (sandboxed.guru) but any and all subdomains as well (json.sandboxed.guru). This is demonstrated in Table 7-1.

Table 7-1. Illustrating Which Origins Are Considered Matches Against the Value Possessed by the domain Attribute

domain Attribute

Origin

Match

www.sandboxed.guru

sandboxed.guru

false

sandboxed.guru

www.sandboxed.guru

false

.sandboxed.guru

sandboxed.guru

true

.sandboxed.guru

www.sandboxed.guru

true

.sandboxed.guru

json.sandboxed.guru

true

It is not necessary to apply the . token. As long as we explicitly specify a hostname for the domain attribute, the . token will automatically be prepended to all non-fully-qualified domains by the user agent.

path

While the domain attribute specifies to which domain(s) a set cookie is scoped, the path attribute further enforces to which subdirectories a cookie is available. If a path attribute is not explicitly specified, the value is defaulted to the current directory that set the cookie. Furthermore, every subdirectory of the defaulted directory will be provided access. However, explicitly defining the path attribute allows us to narrow or broaden the scope of the cookie to that of a particular directory and all of its subdirectories. Listing 7-8 demonstrates how cookies can further scope a cookie to that of a particular URL for any domain that is deemed a potential match.

Listing 7-8. Demonstrating Path Scoping with Cookies Set from http://json.sandboxed.guru/chapter7/ficticious.html

"cookieDefault=test; domain=.sandboxed.guru";
  http://json.sandboxed.guru/chapter7/       //cookieDefault is provided for this request
  http://json.sandboxed.guru/chapter7/css/   //cookieDefault is provided for this request
  https://www.sandboxed.guru/                //cookieDefault is NOT provided for this request
  http://json.sandboxed.guru/chapter3/js/      //cookieDefault is NOT provided for this request
  https://json.sandboxed.guru/chapter3/img/    //cookieDefault is NOT provided for this request

"cookieA=test; domain=.sandboxed.guru; path=/";
  http://json.sandboxed.guru/chapter7/       //cookieA is provided for this request
  https://www.sandboxed.guru/                //cookieA is provided for this request
  http://json.sandboxed.guru/chapter3/js/    //cookieA is provided for this request
  https://json.sandboxed.guru/chapter3/img/  //cookieA is provided for this request

"cookieB=test; domain=.sandboxed.guru; path=chapter3/js/";
  http://json.sandboxed.guru/chapter7/       //cookieB is NOT provided for this request
  http://json.sandboxed.guru/                //cookieB is NOT provided for this request
  https://json.sandboxed.guru/chapter3/js/   //cookieB is provided for this request
  https://json.sandboxed.guru/chapter3/      //cookieB is NOT provided for this request

Image Note  Cookies that are scoped to a particular domain and/or path are able to be used indistinguishably by HTTP and HTTPS protocols.

secure

The secure attribute is slightly misleading, as it does not provide security. Rather, this attribute, which does not require being assigned a variable, informs the browser to send the cookie to the server only if the connection over which it is to be sent is a secure connection, such as HTTPS. Transmitting data over a secure transport reduces the ability for any network hijackers to view the contents being transported. This helps to ensure that the cookie remains concealed from possible snoopers. While this flag ensures that a cookie’s value remains hidden from an attacker, it does not prevent the cookie from being overwritten or even deleted by an attacker.

httponly

The httponly attribute, when specified, limits the availability of the cookie to the server and the server alone. This means the cookie will not be available to the client side, thereby preventing client-side JavaScript from referencing, deleting, or updating the cookie. This httponly flag, when used in conjunction with the secure flag, helps to reduce cross-site scripting from exploiting the cookie. As this chapter is focused on the persistence of JSON data from a client-side perspective, we will be avoiding this attribute.

Image Note  Cookies set with the httponly flag can only be set by the server.

When specifying any of the preceding attribute-value pairs, there is no particular order in which they must be specified. Furthermore, each is case-insensitive and can appear in lowercase or uppercase form.

document.cookie

A cookie can be created by a server, server-side code, HTML meta tags, and even JavaScript. In this chapter, we will solely be focused on the creation and the retrieval of cookies by way of the JavaScript language. Up until now, we have been equating a particular syntax of string as the representative for a cookie. The reality is that it is not a cookie until we supply it to our document.

The Document Object Model, or DOM for short, can be referenced via the document object in JavaScript. This document object possesses a variety of interfaces that allows us to manipulate HTML elements and more. One interface on which we will be focusing is the appropriately named document.cookie interface. The cookie attribute of the document object is responsible for supplying the browser with a provided string of name/value pairs, enabling the persistence of said key/value pairs. Additionally, this property acts as the interface for their retrieval from the document. Listing 7-9 uses document.cookie to create our first cookie.

Listing 7-9. Supplying Our First Key/Value Pair to document.cookie in Order to Become a Cookie

document.cookie= "ourFirstCookie=abc123";

While it appears in Listing 7-9 that we are assigning a string to the cookie property, in actuality we are providing a string as the argument to a setter method. A setter method is a method that is used to control changes to a variable.3 Behind the scenes, the document receives the value being assigned and treats it as an argument to an internal method, which immediately sets the assignment as the value to be stored within an internal collection. This collection, which has come to be referred to as the cookie jar, is stored in a file that is available only to the browser that stores it. Because each browser sets cookies within its cookie jar, cookies are only available to the browser that is used at the time they are set.

As we are not truly assigning a value to the document.cookie property, we can add any number of name/value pairs to document.cookie, without fear that we will overwrite what we had previously set as a cookie, as seen in Listing 7-10.

Listing 7-10. Subsequent Assignments to document.cookie

document.cookie= "ourFirstCookie=abc123";
document.cookie= "ourSecondCookie=doeRayMe";
document.cookie= "ourThirdCookie=faSoLaTeaDoe";

As I stated earlier, the name/value pairs are not being overridden with each new assignment. All name/value pairs assigned to document.cookie are not held as the value of cookie but, rather, stored safely within the cookie jar. The cookie jar is simply a resource located on the file system of the user’s computer, which is why cookies have the ability to persist.

In order to view all cookies on your machine, follow the outlined steps for the modern browser of your choice.

For Chrome:

  1. Open Chrome.
  2. Navigate your browser to chrome://settings/cookies.
  3. Click any site to view all cookies for that particular site.

For Firefox:

  1. Open Firefox.
  2. From the Firefox menu, select Preferences.
  3. Click the Privacy tab.
  4. Click the linked words “remove individual cookies.”
  5. Click any site to view all cookies for that particular site.

For Safari:

  1. From the Safari menu, select Preferences.
  2. In the preferences window, select Privacy.
  3. In the Privacy window, click Details. (Unfortunately, with Safari, you can only see what sites have set cookies. You won’t be able to view full details.)

For Internet Explorer:

  1. Open Internet Explorer.
  2. From the Tools menu (the gear icon), select Internet Options.
  3. On the General tab, within the section “Browser History,” select Settings.
  4. From the Settings panel, click “View objects” or “View Files.”

If you only care to view the cookies that are available to the sites you are currently viewing, this can easily be achieved by way of the developer console. Utilizing the developer’s tools provided by a modern browser, we can easily witness the cookies we have created thus far. Figure 7-2 displays the stored cookies of Listing 7-10, by way of the developer tools provided by Chrome Version 35.0.1916.114.

9781484202036_Fig07-02.jpg

Figure 7-2. Chrome’s Developer Tools Console displays the cookies for the currently visited URL json.sandboxed.guru/chapter7/

As you can note from the Name column in Figure 7-2, each cookie has, in fact, been stored rather than overwritten. Furthermore, you can see what values are set for each optional cookie-av, as follows:

Domain: json.sandboxed.guru
Path: /chapter7
Expires: Session

As you may recall, Listing 7-10 merely supplied the name/value pair and did not append any optional cookie attribute values. However, the domain, path, and expires attributes are required of the cookie. Therefore, the values supplied, as shown in Figure 7-2, have been set to their defaulted values.

As discussed earlier, both the domain and path attribute values are defaulted to the respective aspects of the URL from which a cookie is set. The domain attribute, which is set to json.sandboxed.guru, clearly identifies the domain name from which the application ran. Furthermore, the path set to /chapter7 is a reflection of the directory from which the resource set the preceding cookies.

Image Note  The preceding results reflect the cookies set from the following URL:  http://json.sandboxed.guru/chapter7/7-7.html.

Last, the expires attribute is defaulted to a session, which means that the moment the session ends, the browser is no longer required to store the cookie further. In order to provide a level of control over the cookie attribute values, we must append them as required by the syntax of the HTTP cookie. This can be done easily by devising a function to handle this, as portrayed in Listing 7-11.

Listing 7-11. The setCookie Function Simplifies the Creation of HTTP Cookie Values

function setCookie(name, value, expires, path, domain, secure, httpOnly) {
                document.cookie = name + "=" + value
                //if expires is not null append the specified GMT date
                + ((expires)? "; expires=" + expires.toUTCString() : "")
                //if path is not null append the specified path
                + ((path) ? "; path=" + path : "")
                //if domain is not null append the specified domain
                + ((domain) ? "; domain=" + domain : "")
                //if secure is not null provide the secure Flag to the cookie
                + ((secure) ? "; secure" : "");
};

The function setCookie within Listing 7-11 provides us with a simple means to create a cookie, by supplying the necessary arguments for each cookie-av parameter. For each value that you wish to override, the function setCookie may be supplied with the appropriate string value. That is, except for the expires attribute, which requires a date. For any optional cookie attribute value that you wish to omit, you can simply provide the null primitive. This is demonstrated in Listing 7-12.

Each line within the setCookie function relies on what is known as a tertiary operator to determine whether an empty string or a supplied value is to be appended to the cookie. A tertiary operator, which is simply a condensed if . . . else statement determines if a parameter has been provided an argument to append. If the parameter has not been supplied an argument, an empty string is assigned as the value for the specified cookie attribute.

Image Note  It is the responsibility of the user-agent to set values for any attribute value that is not valid. Attributes that possess empty strings will be replaced with a default value.

Listing 7-12. The Function setCookie Has Been Created to Help in the Provision of Cookie Attribute Values

setCookie("ourFourthCookie",                  //name
          "That would bring us back to Doe",  //value
           new Date("Jan 1 2016 12:00 AM"),   //expires
           "/",                               //path
           null);                             //secure

Listing 7-12 utilizes the setCookie function to create a cookie that will persist until January 1, 2016. The attribute’s values can be viewed within the cookie jar, as demonstrated within the Developer Tools Console, as shown in Figure 7-3.

9781484202036_Fig07-03.jpg

Figure 7-3. Developer Tools Console displaying the configured cookie attribute values for the currently viewed URL

While document.cookie is the entry point to the method that controls the storage of cookies, it can also be used to obtain the many name/value pairs that have been stored, provided their domain attribute matches the domain from which they are being requested. In order to read from the cookie jar, we simply reference the cookie property of the document, without providing it an assignment, as demonstrated in Listing 7-13.

Listing 7-13. Retrieving All Persisted Cookies for the Scoped Origin and Path via document.cookie

console.log(document.cookie); // "ourFourthCookie=That would bring us back to Doe"

The code within Listing 7-13 simply logs out the returned value from document.cookie and sends it to the console for inspection. What is outputted is the name/value pair that has continued to persist. This is assuming you are running this code prior to January 1, 2016. Otherwise, because the expires attribute would be explicitly set to a date that occurred in the past, it would be removed from memory, and nothing would appear.

Image Note  Running the preceding code after January 1, 2016,12:00 AM would inform the browser that it no longer is required to store the cookie.

What you may recognize immediately is that the product returned from document remains unaltered from what we initially supplied in Listing 7-12. Unfortunately, document neither separates the supplied key from its assigned value for ease of use, nor does the document possess a method that can separate them for us. Therefore, in order to extract the value from the string returned, we will have to separate the value from the key ourselves. Listing 7-14 accomplishes this with simple string manipulation.

Listing 7-14. Separating the Value from the Supplied Key from a Singularly Returned Cookie

 1  var returnedCookie = "ourFourthCookie=That would bring us back to Doe";
 2  //15 characters in is the = sign
 3  var seperatorIndex = returnedCookie.indexOf("=");
 4
 5  //extract the first 15 characters
 6  var cookieName  = returnedCookie.substring(0,seperatorIndex);
 7
 8  //extract all characters after the '=' 15th character
 9  var cookieValue = returnedCookie.substring(seperatorIndex+1, returnedCookie.length);
10
11  console.log(cookieName);  //"ourFourthCookie"
12  console.log(cookieValue); //"That would bring us back to Doe"

Listing 7-14 begins by searching for the first occurrence of the equal (=) token (line 3), as that is the token that separates the key from its value. Once this index is made known, we can consider everything up to that index the “key” and everything beyond it the “value.” Utilizing the implicit method of the String Object, we can extract a sequence of characters within a numeric range. We begin with the range of characters from 0 up to the 15th character being the = token for Name (line 6). The next set of characters, which begins at the 16th character, ranges through the remaining characters of the string, thus successfully extracting the value.

You may also notice that the string returned does not supply us with any of the attribute-value pairs that it was initially assigned. This is strictly due to the fact that the cookie-av values are intended to be utilized by the browser alone. It is the browser’s job to ensure that cookies are being supplied to the necessary domain, path, and over the proper transport protocol. Our application merely requires informing the browser, at the moment the cookie is set, how it is necessary to handle the storage and access to the cookie.

While Listing 7-14 outputted only one cookie, this will not always be the case. In the event that numerous cookies are stored and requested from that of a matching origin/path, each persistently stored cookie will be concatonated and returned by the document. Each name/value pair is separated from another by way of the semicolon (;) token, as demonstrated in Listing 7-15.

Listing 7-15. Multiple Cookies Are Concatenated and Delimited by a Semicolon (;)

setCookie("ourFourthCookie",
          "That would bring us back to Doe",
           new Date("Jan 1 2016 12:00 AM"),"/",null,null);

setCookie("ourFifthCookie",
          "Doe a dear a female dear,
           new Date("Jan 1 2016 12:00 AM),"/",null,null);

console.log(document.cookie);
//"ourFifthCookie=Doe a dear a female dear; ourFourthCookie=That would bring us back to Doe"

By identifying the tokens of the grammar that make up the cookie syntax, we can separate the name/value pairs from one another. Additionally, we can separate the value from the specified name. This can be achieved by searching the provided string for the semicolon (;) and equal sign (=) tokens.

Listing 7-16. Extracting the Value from a Specified Key Among Many

 1 function getCookie(name) {
 2     var regExp = new RegExp(name + "=[^;]*", "mgi");
 3     var matchingValue = (document.cookie).match(regExp);
 4     console.log( matchingValue )   // "ourFourthCookie=That would bring us back to Doe"
 5     for(var key in matchingValue){
 6         var replacedValue=matchingValue[key].replace(name+"=","");
 7         matchingValue[key]=replacedValue;
 8     }
 9     return matchingValue;
10 };
11 getCookie("ourFourthCookie");  // ["That would bring us back to Doe"]

The function getCookie within Listing 7-16 utilizes a regular expression to seek out any name/value pairs from the string returned by document.cookie. The pattern name+"=[^;]*", as highlighted on line 2, defines a pattern to match all sequences of characters within a string that is found to possess a specified name immediately followed by the = token. From there, any valid ASCII character is considered to be a match, as long as that character is not a semicolon (;) token. Should the string returned by the document.cookie possess any sequences of characters that match this pattern, they are captured, respectively, within an array and returned for reference (line 3).

At this point, if a match has been made, what will be indexed within the returned array are the name/value pairs that match the cookie name supplied to the method. If we were to log out the results found within the array at this point, we should view the following: "ourFourthCookie=That would bring us back to Doe" (line 4). In order to separate the value from Name and the equal sign, we iterate over all matched occurrences and replace the found name and = token with those of an empty string (line 6), thereby exposing the value. The value is then reassigned back to the key to which it is referenced within the matchingValue array (line 7). Last, the getCookie function returns the array of all found values (line 9).

Thus far, you have learned how to successfully write and store persistent values by way of HTTP cookies. Utilizing our new functions, setCookie and getCookie, let’s revisit the Person object from the previous chapter and store its serialized JSON text within a cookie (see Listing 7-17).

Listing 7-17. Pairing the JSON Object and the Cookie to Store objects

 1  function Person() {
 2        this.name;
 3        this.age;
 4        this.gender;
 5  };
 6  Person.prototype.getName = function() {
 7        return this.name;
 8  };
 9  Person.prototype.getAge = function() {
10        return this.age;
11  };
12  Person.prototype.getGender = function() {
13        return this.gender;
14  };
15
16  //instantiate new Person
17  var p = new Person();
18      p.name = "ben";
19      p.age = "36";
20      p.gender = "male";
21
22  var serializedPerson = JSON.stringify(p);
23  setCookie("person", serializedPerson, new Date("Jan 1 2016"),"/","sandboxed.guru",null);
24  console.log( getCookie( "person" )); "{"name":"ben","age":"36","gender":"male"}"

Running the preceding code within a browser will create a cookie, as previously, only this time, the cookie created possesses JSON as the supplied value. Also as before, by opening up the developer consoles provided by modern browsers, we can view all stored cookies within the cookie jar for the current origin.

As you can clearly see from Figure 7-4, our person cookie, like the others, has been added to the cookie jar. It will remain available to all JavaScript code from within any directory of the scoped domain sandboxed.guru, as well as any and all subdomains.

9781484202036_Fig07-04.jpg

Figure 7-4. Developer console exhibiting the persistence of our person cookie and its JSON value

To further illustrate this point, simply navigate to http://json.sandboxed.guru/chapter7/cookie-test.html and create your own person cookie to store. After you submit your cookie to the document, either refresh the page to find the person column populated or navigate to http://sandboxed.guru/cookie-test.html to find that this top-level domain has access to your new person cookie. Now hit Delete, to remove the persisted cookie, and generate another, this time with different data. Once more, visit the subdomain http://json.sandboxed.guru/chapter7/cookie-test.html, and you will see that new cookie pre-populated.

For all of its benefits, the cookie does come with a few limitations. Sadly, the cookie can only store a maximum amount of bytes. In fact, it can only store roughly 4KB, which would be roughly 4,000 ASCII characters. While 4,000 characters is a lot, it can add up quickly, depending on what you are storing. Furthermore, Base64 characters can require up to three times more bytes per character than ASCII.

You learned that document.cookie does not provide any information beyond the stored name/value pair. This is problematic, because there is no way to truly know how many bytes are available to us. Another issue that cookies face is that they are scoped to the browser, which means that the preserved state is only available to the specific browser that preserves it. Last, because the cookie was originally crafted to help maintain a visitation between a server and a browser, cookies are automatically sent with every request made to the server that possesses the allowed origin by the cookie. The issue here is that the more cookies that are used, each occupying x number of bytes is sent to the server with every single request. Essentially, unless your server is utilizing the cookie, you are needlessly transmitting 4KB for each cookie stored for every request.

While the cookie has its advantages, it is also archaic. It was just a matter of time before another front-end technology came along. That tool is HTML 5’s Web Storage.

Web Storage

HTML5 introduced the concept of Web Storage to pick up where the cookie had left off. While Web Storage may be considered to be the HTTP cookie successor, it would simply be a matter of the context in which you can make that statement. A better way to view Web Storage is simply to look at it as cookies’ counterpart. Its creation is not necessarily to replace the cookie. The cookie itself serves a very important purpose, which is to maintain the session between a browser and a server. This is something that Web Storage does not intend to replace, because it exists to meet the growing needs of the times in a way that the cookie is simply incapable of fulfilling, when it comes to the persistence of client-side data.4

It strives to reduce the overhead of HTTP requests and offers an incredibly large amount of storage per origin. In fact, the allowed capacity ranges about 5MB. Similar to its predecessor, the Web Storage API enables state to be stored via JavaScript, either indefinitely or solely for the duration of a session. Much like the cookie, Web Storage concerns itself with the persistence of name/value pairs. Because each value supplied to the storage object must be in string form, it can quickly become cumbersome to deal with a plethora of string values, thereby making JSON data the ideal candidate.

Web Storage is accessible to JavaScript, by way of Window Object and can be accessed as Window.localStorage and Window.sessionStorage. Because the window object is global and can always be reached from within any scope, each storage object can be referenced without the explicit reference of the window object, shortening each reference to localStorage and sessionStorage.

Both forms of the aforementioned storage objects, whether they be local or session, allow for the storage of state through a similar API. However, as you may have already surmised, the difference between the two regards the contrast among the durations for which the state of data is retained. The sessionStorage, as the name implies, allows data to persist only as long as the session exists. Whereas the data stored via localStorage will persist indefinitely, either until the state is deleted by the application or user, by way of the browser’s interface. Unlike the cookie, all data stored within localStorage will not be set to expire.

Web Storage Interface

Web Storage allows for the storing of data, the retrieval of data, and the removal of data. The means by which we will be working with data and the storage object is via the Web Storage API. As Table 7-2 outlines, there are six members that make up the Web Storage API, and each provides a specific need for working with data persistence.

Table 7-2. Six Members of the Web Storage API

Members

Parameter

Return

setItem

string (key), string (value)

void

getItem

string (key)

string (value)

removeItem

string (key)

void

clear

 

void

key

Number (index)

string (value)

length

 

Number

Unlike the singular interface of the HTTP cookie, which is used to store, retrieve, and delete data, Web Storage possesses an API to make working with the persistence of data all the more practical. Furthermore, regardless of the storage object you intend to use, whether it’s local or session, the API remains uniform.

setItem

The Storage Object method setItem possesses the signature of Listing 7-18 and is the method that we will use to persist data. As was mentioned previously, much like the HTTP cookie, Web Storage persists data in the form of name/value pairs. However, while the cookie itself did not distinguish the name from the value it retained, Web Storage does. Therefore, setItem does not merely accept a singular string but, rather, requires two strings to be provided. The first string represents the name of the key, and the second string will represent the value to be held.

Listing 7-18. Signature of the setItem Method

setItem( key , value )

When a value is set, it will occur without providing a response back to the invoker of the method. However, if a value is unable to be set, either because the user has disabled the storage or because the maximum capacity for storage has been reached, an Error will be thrown. It’s as they say, “no news is good news.” In other words, if an error does not occur on setItem, you can rest assured the data has been set successfully.

Because a runtime error can cause your script to come to a halt, it will be imperative to wrap your call to setItem with a try/catch block. Then, you can catch the error and handle exceptions gracefully.

Listing 7-19. Storing Our First Item

localStorage.setItem("ourFirstItem,"abc123");

As with the key/value pairs of a JavaScript object, each key must possess a unique label. If you were to store a value with the name of a key that currently exists, that value would effectively replace the previously stored value.

Listing 7-20. Replacing the Value Possessed by the ourFirstItem Key

localStorage.setItem("ourFirstItem","abc123");
localStorage.setItem("ourFirstItem","sunday Monday happy-days");

At this point in time, if we were to retrieve the value set for ourFirstItem, we would witness that the previous value of "abc123" had been replaced with the theme song from the television sitcom Happy Days.

Image Tip  Because an error will be thrown if the user has disabled Web Storage, it would be wise to wrap every call to the Storage Object API within a try/catch block.

getItem

The Storage Object method getItem (see Listing 7-21) is the counterpart to the setItem method. It, like our getCookie method from Listing 7-16, allows us to retrieve the persisted state that corresponds to the key provided to the method (see Listing 7-22).

Listing 7-21. Signature of getItem

getItem( key )

Listing 7-22. Obtaining a Value for a Specified Key

console.log( localStorage.getItem( "ourFirstItem" ) );   //sunday Monday happy-days
console.log( localStorage.getItem( "ourSecondItem" ) );  //null

The key is the only expected parameter, as indicated in Listing 7-22, and will return the corresponding state for the supplied key. If, however, the name of the key supplied does not exist on the Storage Object, a value of null will be returned.

removeItem

The Storage Object method removeItem is the sole means of expiring the persistence of an individual key/value pair. Its signature is similar to that of getItem, in that it accepts one parameter, as shown in Listing 7-23. This parameter is the key that pertains to the data that you no longer wish to persist (see Listing 7-24).

Listing 7-23. Signature of removeItem

removeItem( key )

Listing 7-24. Utilizing removeItem to Expire a Persisted State

console.log( localStorage.getItem( "ourFirstItem" ));    //sunday Monday happy-days
             localStorage.removeItem( "ourFirstItem" );
console.log( localStorage.getItem( "ourFirstItem" ));    //null

clear

As indicated in Listing 7-25, the method clear does not require any parameters. This is because this method is simply used to instantly purge each and every key/value pair retained by the targeted Storage Object.

Listing 7-25. Signature of the clear Method

clear( )

key

The Storage Object method key is used to obtain the identities of all stored keys that possess accompanying data retained by the given Storage Object. As the signature outlined in Listing 7-26 demonstrates, the method can be provided with that of an index, which will return in kind with the member at the supplied index. If a value does not exist for the provided index, the method will return a value of null.

Listing 7-26. Signature of the key Method

key( index )

length

As it will not be beneficial to supply indexes that are beyond the boundaries of stored keys, the Storage Object provides us with access to the length of all values stored by the Storage Object in question. This total can be obtained via the length property. The length property, when used in conjunction with a loop, as demonstrated in Listing 7-27, provides us with the ability to remain within the boundaries of the values stored.

Listing 7-27. Obtaining the Stored Keys from a Storage Object Is Simple with a Loop

var maxIndex= localStorage.length;
for(var i=0; i<maxIndex; i++){
        var foundKey = localStorage.key( i );
}

Reusing the key/value pair used by our first cookie, we will demonstrate the ease of the Web Storage API.

Listing 7-28. Utilizing Web Storage to Persist the Value Supplied to Our Person Instance

 1 function setItem( key , value ){
 2    try{
 3         localStorage.setItem( key , value );
 4    }catch(e){
 5         //WebStorage is either disabled or has exceeded the Storage Capacity
 6     }
 7 }
 8 function getItem( key ){
 9    var storageValue;
10    try{
11        storageValue= localStorage.getItem( key );
12    }catch(e){
13        //WebStorage is disabled
14    }
15    return storageValue;
16 }
17
18  function Person() {
19        this.name;
20        this.age;
21        this.gender;
22  };
23  Person.prototype.getName = function() {
24        return this.name;
25  };
26  Person.prototype.getAge = function() {
27        return this.age;
28  };
29  Person.prototype.getGender = function() {
30        return this.gender;
31  };
32
33  //instantiate new Person
34  var p = new Person();
35      p.name = "ben";
36      p.age = "36";
37      p.gender = "male";
38
39  var serializedPerson = JSON.stringify(p);
40  setItem( "person" , serializedPerson );
41  console.log( getItem( "person" )); // "{"name":"ben","age":"36","gender":"male"}" ||     "undefined"

Listing 7-28 revisits our person example from Listing 7-17 to point out how the Web Storage API and cookie interface vary. The examples similarly use their component to store and retrieve the same value. However, the use of the API provided by Web Storage simplifies things greatly. Unlike in our cookie example, Web Storage requires less work for setting—and especially retrieving—data. Line 41 of Listing 7-28 simply requests the data of the supplied key and logs it for inspection to the developer console. The reason why the value returned may be either what is stored or (signified by the || operator) "undefined", is due to the fact that Web Storage may be disabled, which will prevent the variable storageValue (line 9) from being set. Unlike its cookie counterpart, getItem handles the management of key/value pairs for us, so that we don’t have to manipulate the returned string. Could you imagine performing a JavaScript search over 5MB worth of ASCII characters? The application would become nonresponsive.

What you may have also noticed is that we never specified a domain or path at any point in time during our review of Web Storage. This is because, unlike the cookie, the Storage Object strictly adheres to the same-origin policy, meaning that resources can only be shared/accessed from the same document origin, if the two share the same protocol, hostname, and port. You will learn more about the same-origin policy in Chapter 9.

Summary

The HTTP cookie and Web Storage are extremely useful client-side tools for storing and persisting JSON data. They can be utilized to retain the state of a user’s engagement with a web site, web app, or even a game. As cookies and Web Storage are stored on the user’s browser, each visitor can potentially possess different information, which can further add to the benefit of local persistence. Such benefit would be personalization/optimization. However, for all their benefits, the cookie and Web Storage are not without their limitations.

The first and foremost concern surrounds security. As both the cookie and a Storage Object can be set and retrieved with JavaScript, it’s best practice to store information that is not particularly sensitive. While it may not be understood by the average visitor of your site how data is being utilized between your application and their browser, those who are seeking to exploit these technologies do understand. As this data is accessible to JavaScript, by utilizing the same techniques covered in this chapter, a user or a site hijacker can manipulate or alter persisted state at any point in time, for malicious or benign intent. This, of course, will vary, based on the data as well as the nature of the application that makes use of it.

As I previously indicated, the HTTP cookie and Web Storage are scoped to a visitor’s browser. Data that may have been set to persist, whether by cookie or Storage Object, is dependent on the browser the visitor previously used to interact/view your application. This means the persistence of state has the potential to vary from one browser to the other, each time a user visits your application. This inconsistency may prove to be problematic, depending on your application’s needs. Last, as the data that is being retained will persist on the visitor’s file system and not the server’s, it can easily be removed by the visitor at any point he or she chooses, through the interface provided by the browser.

These aforementioned issues can be avoided when used in conjunction with a server-side database, which will be the topic of discussion in Chapter 12. In the next chapter, I will discuss how to transmit JSON to and from our applications via JavaScript.

Key Points from This Chapter

  • Data persistence is the continued existence of state after the process that created it.
  • HTTP/1.1 is a stateless protocol.
  • Cookies and Web Storage are used to retain state.
  • Cookies are sent with every HTTP/1.1 request.
  • Session data will cease to exist after the session exits.
  • Sessions do not necessarily end when a browser is closed.
  • Cookies are exchanged via HTTP and HTTPS, unless flagged as secure.
  • Cookies can only store 4KB worth of ASCII characters.
  • Cookies can be shared among subdomains.
  • Web Storage can store 5MB of data.
  • Each origin possesses its own Storage Object.
  • Web Storage strictly adheres to a same-origin policy.

_________________

1Wikipedia, “Persistence (computer science),” http://en.wikipedia.org/wiki/Persistence_%28computer_science%29, 2014.

2Wikipedia, “Stateless protocol,” http://en.wikipedia.org/wiki/Stateless_protocol, 2014.

3Wikipedia, “Mutator method,” http://en.wikipedia.org/wiki/Mutator_method, 2014.

4W3C, W3C Recommendation, “Web Storage,” http://www.w3.org/TR/webstorage/#introduction, July 30, 2013.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.54.136