HTTP (HyperText Transport Protocol) is a connection-based protocol, but it uses one connection per request. Consecutive connections from the same tab of the same browser are not related as far as HTTP itself is concerned.
This poses a problem because how can an online merchant know which customer just clicked on "buy now" of an item? The entire concept of authentication also seem pointless because the authentication and identification of an account only lasts for one connection, subsequent clicks are logically not connected to the identified user.
One way to create the illustion that subsequent clicks (HTTP requests) are related is to add a parameter to the GET requests that identifies the continuity. However, this approach has several problems. First, if the continiuity ID is leaked, someone else can easily hijack an identity. Second, this requires all links to include the parameter which is tedious.
A session is the maintenance of the pretence of continuity between HTTP requests. As such, a session may include various kinds of information, including but not limited to user identity, shopping carts, and etc.
The Express framework include modules to handle sessions in a secure and convenient manner.
Like most web scripting environments, Express utilizes cookies to identify a session. Let us examine cookies.
A cookie has several parts. Of importance to us are the name, URL path, and a value. Typically, upon the response of the first request to a server, the server specifies a cookie. When the client (browser) receives the response, it creates the cookie on the client side (managed by the browser).
Once a cookie is created, the client (browser) transmits the cookie and its value every time a request is made to an URL that matches the URL path of the cookie. This is how continuity is maintained between HTTP requests to all the URL paths that match the URL path specification of a cookie.
However, a cookie can only contain a small amount of data, and it is not secure to store any sensitive data on the client side. As a result, the value of a cookie is only used to identify a session, but the actual data associated with a session is stored on the server side.
To this end, the express-session module is sufficient. However, this module implements the simplest "store", in-memory. This means that when a server (just the Express script) is restarted, all previous sessions would have been forgotten.
A more robust method that allow sessions to persist server reboots is to maintain data in a database (like MariaDB). This is implemented by the node module express-mysql-session.
This discussion is not only because of the use of sessions, but the use of databases, in general.
Unless precaution is made, a production app and a sandbox app using the same code base use the same database and the same tables in the database. This can be an issue because it is unwise to use production data to test code in the sandbox! Furthermore, it is also common practice to track several datasets (tables and their contents) for testing purposes.
Another limitation is that in most environments, both the production app and the sandbox app need to use the same database. Given this restriction, one way to keep the production and sandbox separated is to use a prefix for the tables.
Whether non-session tables in the database can be shared or not, session tables cannot be shared. Otherwise, sessions of the production app and sessions of the sandbox app can cross over.
When the credential object is used to create the "store" of Express sessions, it can use an optional member to determine the prefix of the tables used to store data related to sessions. We will examine how this can be done in the next section.
You can download this script.
This is a fairly complex program. This program makes use of the GET parameters of a HTTP request as well as the concept of session to maintain continuity between clickes. At the lowest level, the continuity is maintained by the use of cookies that are stored on the client side.
In addition to the "typical" iniitalization of an Express script, this program also makes use of the database. This initialization start with reading the file that contains credential to connect to the database.
This step does not actually do anything with the database, it merely reads the content of a file
Next, based on what is read from the credential file, a new member schema is added. The schema itself can have multiple members, but the one that we need here is tableName.
Because we are running both the sandbox and production apps on the same server, using the same database, the tables used to track session must have different names. In this case, the use of the backquote expands the value of portNumer within the string. This means that if the port number is 41220, then the actual name of the table is s_41220_session.
This works because the sandbox and production apps use different port numbers.
The express-session module is a "middle-ware" in the sense that it gets to read/parse/process a request before an Express end-point handler is eventually called. However, at this point, the module is simply loaded, but it is in no way connected to either the database or the Express framework.
To maintain modularity, express-session explicitly leaves out how information tracked by sessions is stored. This is because different server environment may have different methods to maintain session information.
This brings us to the following code.
This code loads the express-mysql-session module, which is specifically developeed to interface with express-session and utilize a MySQL/MariaDB data to maintain session information.
In case you are wondering, internal to express-mysql-session, callback functions are utilized to handle asynchronous operations. This has zero impact, whatsoever, to any Express end-point handlers because all database operations related to the maintenance of sessions occur before and after the execution of Express end-point handlers.
In this step, the module of express-mysql-express is loaded, and it is linked to the express-session module. However, there is no database operation performed.
Here is the step that initializes database connection for session information maintenance.
This step utilizes the object mdbSpecs to initialize the connection to the database. If the credential data is incorrect, this step fails. At this point, the connection to the database is made, and the modules are properly loaded and initialized. However, the Express framework is completely unaware of the session "middleware". This step creates an object that, in return, is used in the next step.
This is where the initialization completes from the perspective of setting up Express to handle sessions. This code is long because it also specifies many of the parameters related to how session cookies are created. These parameters are captured by the object (note the use of open brace { and close brace } to specify the object).
The key is used to name cookies uniquely on the client side. Imagine that when your professor is grading, the app of each student sets a cookie on the professor’s browser. If the cookie name is not unique, then the browser can end up cross talking across the apps of different students, leading to massive confusion.
This is why portNumber is expanded and becomes a part of the identifier of the cookie.
The secret, on the other hand, do not need to be unique from the perspectives of making use cookies from different apps do not cause confusions. The secret is also a key of sorts, but it is the key of encryption. This parameter makes it difficult for a hacker to spoof cookies to attempt to hijack a session.
The store is where we utilize the rather long initialization process. This is where the session module understand how session information is maintained. The rest of the parameters are of less importance and therefore not explained in any further detail (the comments explain in a brief manner).
The logic of the end-point handler is more complex than our previous sample code. First of all, it illustrates the use of nested statements where one statement becomes a part of another one. Secondly, it utilizes sessions and query parameters at the same time to determine what to do.
The only end-point is /, and the call-back (handler) is epRootHander.
An end-point handler actually has more than two parameters, but only two is in use in this example. req is an object representing the request, and res is an object representing the response.
The main logic to take into consideration of the session-maintained values (wait) and the request parameter (haveEnough) is from line 29 to 64.
The analysis of code start with the outermost layer because the outermost statement starts first. In this case, the following is the outermost logic.
The purpose of this statement is to make sure there is a session before continuing to process. The condition (also known as a boolean expression) ('session'in req) checkes to see if session is a mmeber of req.
At this point, we expect session to be a member because Express was initialized to use session as a middleware. However, just in case, if session is not a member of req, an error is thrown as an exception on line 63. When an exception is thrown, unless it is "caught" by a catch statement, the script terminates its execution and crashes. In a production script, crashing may not be an apprepriate action. The handling of exceptions is a topic for a different module.
If session is a member of req, as expected, then the script proceed with "normal" processing from line 32 to line 58.
The normal flow itself is a nested conditional (if-then-else) statement.
This is an extension to the usual if-then-else statement. The first condition to check is ('query'in req && 'haveEnough in req.query).
This condition first checks to make sure that the request object has a member called query. If this is true, then it also checks to make sure that haveEnough is a member of req.query. The symbol && is conjunction, otherwise known as "and".
Otherwise, the script checks to see if wait is a member of req.session.
If the user is not saying having enough, and the session is new and therefore wait is not a member of req,session, then the wait member is initialized to 0.
It is important to understand that in this if-then-elseif-else statement, the conditions are check in sequence. Furthermore, Only one of the three branches (lines 36 to 41 is the "then" branch, lines 47 to 49 is the "else-if" branch, and lines 55 to 57 is the "else" branch) execute.
The following code corresponds to the then branch, it further checks the value of haveEhough. If the value is 1, then the wait member of the session is reset to 0 (because the user has had enough).
The script only gets to the else-if branch if and only if
As a result, the "else-if" branch has the following code.
This branch is merely delaying the continued processing by awaiting the delay. The delay specifies the amount of time in milliseconds, and that is why the value of the wait session member is multiplied by 1000. The req.session.wait++ expresssion is called "autoincrement." It increments (adds one to) the wait member of the session.
Finally, the "else" branch executes if and only if
This means this is a fresh session and the wait amount has not been specified. In that case, the following code initializes the wait amount of 0.
Once the logic from line 29 to 64 finishes, the end-point handler continues to generate the response (to the HTTP request). There are three points of interest here.
Line 68 uses the backquote notation to expand the value of req.session.wait so that it becomes a part of the reply.
Line 71 specifies a hyperlink anchor that adds the parameter and value haveEhough=1. The question mark ? is necessarily because in HTTP, the parameters start after a question mark in the URL.
Line 74 specifies a hyperlink just to refresh the page without any parameters, this automatically delays the response by the specified amouunt of delay.
The node module express-session is, as mentioned, a "middleware" component. It is along a stack of middle components that are given opportunities to process a request before an end-point handler is called back, as wel as after an end-point handler completes.
In this case, before an end-point handler is called, the session middleware examines the cookies passed along from the client (as a part of the HTTP header) and check those cookies again cookies of ongoing sessions. If there is a match, then the values associated with that session are retrieved from storage and such values become members of the session member of the request object (the parameter usually has a name of req).
After an end-point handler completes, a different method of the session middleware component is called by Express. This time, the members of the req.session is checked to see if any has changed. If so, the session is resaved to storage.
The "storage" implementation depends on what storage module is loaded. In our sample program, the storage is provided by the express-mysql-session module, utilizing MariaDB as the back end to store session related data. This approach has a little bit of overhead due to database access. However, it can easily handle a large amount of session-related data. The most important advantage, however, is that session data is persistent even if the system reboots.