Home     Products      Support      Corporate     Sign In 
Support Knowledge Base, Article 670
Product
General
Title
Maintaining Session State & Sharing Data Across Servers, Domains & the Entire Internet
Solution

Maintaining Session State & Sharing Data
Across Servers, Domains & the Entire Internet

By Tim Sullivan

Wow…now that’s a mouthful!

In this article, I have decided to take the “Mission Impossible” approach. Basically, take a very difficult situation and develop a solution for it, describing the tools and showing the techniques I have used in the past. Be forewarned, there will be several plugs for 3rd party products, but I have always been of the mind, why write a spreadsheet when I can buy one for a heck of a lot cheaper? The following symbol indicates the use of a 3rd party component:

I will show this scenario with many surprise twists and turns. I can’t give away the whole farm in these notes can I? (Here’s a hint: You’ll see how to run remote VBScript using data shared in these examples.)

The Mission

You have been tasked with developing the data delivery and session maintenance infrastructure for a virtual mall. Some of the vendors are quite large and have an enormous amount of traffic, while others are small and can share web servers very efficiently.

The difference between this virtual mall and a mall you get sore feet at (and an empty checking account), is that you must provide a centralized point of checkout. That’s right…the user needs to be able to shop at any vendor and when they want to check out, all of the items purchased at any of the stores must be consolidated into a single shopping cart.

Now, here’s the tricky part (as if the first part isn’t): The shipping departments for each of the vendors are located in geographically disparate locations. Some vendors have dial-up accounts for email, while others have dedicated T1 or ISDN lines. Finally, to throw a huge monkeywrench into the equation, one of the bigger vendors has decided that they want instant notification when a user moves onto their support page (as you wonder “Why?????”). Now the hair pulling commences.

The Hardware Configuration and Network Infrastructure

When making the decision on how to maintain session state or share data, the two main factors are, you guessed it, the hardware configuration and network infrastructure. Why you ask? Well, if you’re dealing with a single machine, one approach may make perfect sense. Throw in an another machine or a network link and that first idea may go right out the window.

On the following page is the configuration of our virtual mall.

You’ll notice that management has all sorts of treats in store (no pun intended) for us:

  • Two web farms
  • Multiple vendors on a single machine
  • T1 connection for the shipping department of one vendor
  • Dial-up connections for certain vendors
  • ISDN connections for other vendors
  • T1 connection for the tech support department of one vendor
  • Before you have second thoughts about taking this mission, keep reading on.
Maintaining Session State across Physical Servers
or Web Farm Frenzy

Let’s first tackle probably the most common problem you will face: Maintaining session state on a web farm.

There are two facets to maintaining session state:

  • Insuring uniqueness during the user’s session

  • Storing the user’s data
Insuring Uniqueness

When developing on a single machine, it’s easy to identify the user:

X = Session.SessionID

Session.SessionID is a browser-based cookie (it’s never written to disk) named “ASPSESSIONID.” Every time your users access a new virtual directory with a Global.ASA file, or access an ASP page for the first time, ASP assigns a new Session.SessionID. In a web farm scenario, however, your web application is spread out across multiple servers. It will conceivably hit the virtual home directory, or access an ASP page on each of these individual web servers. The potential is a Session.SessionID being possibly assigned as many times as you have servers. As a result, there are two issues: Getting a unique identifier and sharing that across the web servers.

Getting the Unique Identifier

First off, did you know that Session.SessionID is not necessarily truly “unique?” Since it’s based on a random number generator created with the same “seed,” it is possible to actually repeat the randomness sequence after a web server has been restarted. For that reason, I recommend one of the following two methods to creating a unique ID:

Time Date Stamp – On a low volume site, you can actually get away with an identifier such as :

X = “ID” & Month(Now) & Day(Now) & Year(Now) & Hour(Now) & Minute(Now) & Second(Now).

GUID (“Globally Unique Identifier”) – The GUID is a composite number that can be generated by a single call to the NT (and Windows 95) OLE subsystem. Made up of information such as date, time and other data (MS doesn’t tell us everything) the GUID is supposedly guaranteed to be unique…worldwide. You cannot generate a GUID directly from ASP. For that you will need a component.

A great freeware product for generating GUIDs is GUIDMaker from ServerObjects. You can download this free component from: http://www.serverobjects.com.

Sharing the Unique Identifier Across Servers

Again, there are two methods to sharing the unique identifier you create:

  • Cookies – You can create a cookie that is read on every page access and written on the first page the cookie does not exist on. The advantage to this approach is that everytime your www.whatever.com page is accessed, the cookie will be passed from the browser to the server (just like an ASP Session ID). Here is an example of the ASP code to do this:

    <%
    MySessionID = Request.Cookies("SessionID")
    If MySessionID = "" Then

    '--- This example uses ServerObjects GUID Maker
    Set MyGuid = Server.CreateObject("GuidMakr.GUID")
      Response.Cookies("SessionID") = MyGuid.GetGUID
      Set MyGuid = Nothing
    End If
    %>

  • Part of the URL – You can pass the unique identifier as part of the URL on every query. The major disadvantage to this approach is that you need to construct every hyperlink and form post to pass this value, regardless of whether or not the referenced page will be using it.
Storing the User’s Data

OK, now you have a unique way to represent the user. Since the web farms are physically separate, you need to be able to access that data in a method that is independent of each server.

Cookies – Again, you can use cookies to achieve this goal. The primary problems with cookies, however, are privacy and browser limitations. Many individuals really don’t want information written to their hard drive. They can typically stomach some unique number, but to write back the contents of their shopping cart or, heaven forbid, their own address, upsets many users. Additionally, some browser versions (there are too many differences to list here) have limitations on the size and number of cookies that can be stored on a user’s hard drive.

File based – You can store the data on a common “file share.” Using the FileSystem object, you can read and write the session variables to a text file. Since there would be no real “structure” to the data, however, you would be required to read up and then write back the entire file. Another disadvantage to this approach is that threads under IIS 3.0 and above run under the SYSTEM account (by default) and you would have to use a user impersonation component to access the remote file share. If you are intent on taking this approach, I do recommend the following.

    MagicINI – MagicINI from Dana Consulting permits you to read and write data to INI files (from your 16 bit days). The advantage of this approach is that you could at least have “keyed” access to your data. http://www.dana-net.com/.

    NTAccess.User – NTAccess.User from Zaks Software will permit you to validate and impersonate a logged on user to access the common file share. http://www.zaks.demon.co.uk/code/

Database – You can also store the session information in a database. This has the primary advantage of being client/server should you choose a database such as Microsoft SQL Server. The disadvantage, of course, is the connection sequence if you reconnect on every access, or the overhead of an additional logged in user if you log in only once. If you take the “logged in only once” approach, a single user could be logged in as many times as you have servers! An additional disadvantage with this approach (as well as the file-based approach) is that the data must be periodically “cleaned out.”

3rd Party Solutions

Yes, there are several 3rd party solutions to this vexing problem. All of these components handle both issues of generating the unique ID and storing the user’s data.

Microsoft Site Server – Microsoft’s offering utilizes a database-style approach to session state management. It is especially useful if you want to maintain session information between sessions. Below is some sample code of how to use Site Server:

<% ... Set UPD = Server.CreateObject("MSP.PropertyDatabase")%>

<%
If UPD.Item("BeenHere") Then
  Response.Write "Welcome back " & UPD.Item("UserName")
  UPD.Item("LastHere") = Now
Else
  Response.Redirect("Register.asp")
End If
%>

http://www.Microsoft.com/siteserver/default.asp

SessionPro (formerly “Xsession”) – SoftArtisans approach is takes the file-based approach. It also maintains persistent data, but you will also have to insure that the IUSR_machinename account has access to the shared disk space. An example of SessionPro code :

<%
Set oXS = Server.CreateObject("SMUM.XSession.1")

'--- Assign Values
oXS("Email") = "N/A"
oXS("Date") = Now()
oXS("Number") = 1
%>

<HTML>
<BODY>
Values<P>
Email : <%=oXS("Email")%><BR>
Date : <%=oXS("Date")%><BR>
Number : <%=oXS("Number")%><BR>
</BODY>
</HTML>

http://www.softartisans.com

SemaphoreServer – Automated Solutions Group’s offering utilizes a completely different methodology. First, it is true client/server, so your web servers can be in geographically different locations so long as they can be accessed via TCP/IP. Second, the data is truly transitory; when SemaphoreServer is shut down, so is the session data (although you do have the option of writing “semaphores" to disk). Data can also be timed out after a certain period of time. The only disadvantage to this software is that data is stored only in string format. If you do not mind converting data between strings and numbers/dates then that won’t be an issue for you. (Note: You will be seeing a lot more of this product on following pages as it solves many of the problems the mission has tasked us with) Some example code :

<%
Set SemObj = Server.CreateObject("SemClient.Control")
xWeb = SemObj.WebSessionID
'--- Not necessary, but is faster with multiple updates as it opens a thread
'--- on the server and you can have multiple streams open that way
r = SemObj.OpenStream("127.0.0.1"54320)
SemKen = xWeb & "-SESSIONDATA"
SessionData = Name & Chr(253) & Address
NumSecsTimeout = 60 * 20
ChannelName = ""
ForceAdd = 1
r = SemObj.AddSemaphore("127.0.0.1", SemKey, SessionData, NumSecsTimeout, _
  ChannelName, ForceAdd)
SessionData = SemObj.GetSemaphoreContents("127.0.0.1", SemKey)
Name = SemObj.ExtractVal(SessionData, Chr(253), 1)
Address = SemObj.ExtractVal(SessionData, Chr(253), 2)
SemObj.CloseStream("127.0.0.1")
Set SemObj = Nothing
%>

http://www.active4.com

Maintaining Session State and Sharing Data across Domains

How to Share A Cookie

The next major task in our mission is to share the contents of our shopping cart across the vendors individual stores. Again, I recommend a cookie-based approach to store the unique identifier, but this time you have the extra hurdle of passing that cookie between domains.

Take the following code snippet:

Response.Cookies(“myCookie”) = “Test”

That cookie will only be transmitted to domain that called it. For example, if that code was in www.timsullivan.com, that cookie won’t be transmitted to any web pages in www.sullivantim.com. To get around this issue, we have to “share a cookie.”

The ASP-based approach

Sharing a cookie is not all that difficult in ASP. Basically, you have to have a “central ID generation” site that will create the new cookie and all sites that need the cookie must redirect to there if they do not detect the cookies existence.

In our infrastructure model, we will choose www.1800fowlers.com as our “cookie master.” So on www.1800fowlers.com you would create the following ASP code snippet:

<%
'--- PageName : GetCookie.ASP
  GUID = Request.Cookies("VirtualMailID")
  TheQS = Request("QUERY_STRING")
If GUID = "" Then
  Session("REFERER") = Request("HTTP_REFERER")
  '--- This example uses ServerObjects GUID Maker
  Set MyGuid = Server.CreateObject("GuidMakr.GUID")
  Response.Cookies("VirtualMailID") = MyGuid.GetGUID
  Set MyGuid = Nothing
'--- NOW we need to redirect back to ourself to ensure that the calling
'--- routine is correct.
  Reponse.Redirect("GetCookie.ASP?" & TheQS)
Else
  XRefer = Session("REFERER")
  If XRefer = "" Then
    XRefer = Request("HTTP_REFER")
  End If
  If The QS = "" Then
    TheQS = "COOKIEVAL=" & GUID
  Else
    TheQS = TheQS & "&COOKIEVAL=" & GUID
  End If
  Session("REFERER") = ""
  Response.Redirect(Xrefer & "?" The QS)
End If
%>

Now on the www.noblebarney.com server, you would create the following ASP page:

<%
'--- PageName : Default.asp
GUID = Request.Cookies("VirtualMailID")
TheQS = Request("QUERY_STRING")
If GUID = "" Then
  GUID = Request("COOKIEVAL")
  If GUID <> "" Then
    Response.Cookies("VirtualMailID") = GUID
  Else
    Response.Redirect("http://www.1800fowlers.com/GetCookie.ASP?" & TheQS)
  End If
End If
%>

An ISAPI Approach

Included on the conference ftp site is Automated Solutions Group’s freeware ShareACookie (“SAC”) suite of ISAPI dlls. (If for some reason it doesn’t make it onto the site, you will be able to download it from http://www.active4.com after the conference.)

SAC emulates the ASP approach, but instead uses the registry to store “communities.” A community is defined as a series of domains (and it’s smart enough to understand high level and low level domain names) with a controlling “master” domain. You have the choice of using the built-in GUID creator or you can supply a web page with your own unique ID generator. You can also have multiple communities located on a single machine. Finally, you can set the name of the shared cookie. The downside to this approach is that the registry information must duplicated on every server in the community. (The upside is that you can share cookies across the Internet this way.) An example of using SAC:

<%
'--- PageName : Default.asp
GUID = Request.Cookies("VirtualMailID")
TheQS = Request("QUERY_STRING")
If GUID = "" Then
  Response.Redirect("/cgi-bin/sac.dll?/default.asp?" & TheQS)
  '--- Yes, the above statement is legal and works
End If
%>

A Component-based Approach

In the previous section, I demonstrated some ASP code using Automated Solutions Group’s SemaphoreServer product:

<%
Set SemObj = Server.CreateObject("SemClient.Control")
xWeb = SebObj.WebSessionID
%>

If the “communities” are setup as stated in the ISAPI section above, it will perform the redirection for you automatically because the ShareACookie technology is embedded directly within SemaphoreServer.

How to Share the Data

Now that you have a unique identifier across domains, you still need to share that data across the web servers themselves. (Or, in the case of multiple domains on a single server, on the same web server.)

Your available alternatives to write the software yourself is pretty much the same:

File-based – Same advantages/disadvantages as with the same web domain.

Database – Same advantages/disadvantages as with the same web domain.

Notice “Cookies” is missing from this list. The reason is that maintaining the synchronization of cookie data across domains would be virtually impossible. As a user jumps from domain to domain, your ASP page would have to backtrack to the previous domain, pick up the cookie data, pass it to the new domain and finally re-post with the cookie data.

3rd Party Alternatives

ActiveProfile – Uses a database approach (Access databases). Requires that the a user be defined, however. You could conceivably create a user named after the GUID and store their data in that fashion.

SemaphoreServer – I told you that you would see this product again. Since SemaphoreServer really does not care where the data comes from or who reads it (although you can secure access to and from the server), you can store information in one location and have it read from multiple sites (or domains on the same server). The key to using SemaphoreServer successfully is the unique value you assign to SemaphoreServer data. For brevity, I refer you to the code written in the previous section. (Note: ASG also offers a shopping cart plug-in for SemaphoreServer that makes that particular task much easier.)

Sharing Data Across The Internet

Now that you have successfully shared the user’s shopping cart amongst all the stores in our virtual mall, checkout can then take place. (Again, I’m covering e-commerce in another session.) Now that you’ve got the customer’s money, however, you have to ship the product. As our infrastructure diagram shows, all of the shipping departments are located remotely. So how do you get the orders successfully sent?

Since the sites are remotely located, file-based and database solutions are pretty much out of the picture. You could “POST” the information to the remote sites, but that effectively leaves out the dial-up clients.

You actually do have quite a few alternatives, each requiring a differing level of programming expertise:

  • Email

    The order can be bundled into an email message and sent using one of the myriad of ASP SMTP components out there (there are too many to list but some that come to mind are Microsoft’s SMTP component, ASPMail, BambooSMTP and many, many more).

    For simple needs, the email could simply be read and processed manually. For more complex needs, once the data is received by the email server, it could then be processed. How this is done is dependent upon your email server. I Mail from IPSwitch, for example, lets you assign a program to the name of an email user. You could write a Visual Basic program that reads and then processes the mail passed from the I Mail server.

    You could also write a Visual Basic program that scanned a POP3 mailbox (again using one of the many POP3 components on the market) and when mail is found, perform whatever processing is required.

    A final solution using email is to have an ASP page build a MDB file (or whatever extract file is necessary) and then email that packaged file to the user. When the user receives the file, they could then run an application on their desktop that merged the data into their shipping database. This is a great solution for the dial-up shipping departments.

  • FTP

    This is the least elegant of the solutions. The order could be written to a text file (or even an Access MDB file) and then sent via FTP (yes, once again using one of the many FTP components available) to the remote server. A program, however, must be run on the remote server to scan the directories on the “shipping” server and then process the information. The flip side of this solution is that a program running on the “shipping” servers can periodically scan a centralized server for their “orders” and then download and process the orders.

  • A Unique Approach

    Imagine this scenario :
    The user successfully checks out and wants their product. Your ASP page passes all of the information to a remote server. After sending the data across the Internet, your ASP page then instructs the remote server to execute a snippet of VBScript or run a Visual Basic program, all in a fire-and-forget mode. With SemaphoreServer from Automated Solutions Group this is entirely possible.

    On the local IIS server :

    <%
    '--- Customer has checked out
    '--- Now we need to notify the shipping department and process
    Set SemObj = Server.CreateObject("SemClient.Control")
    xWeb = SemObj.WebSessionID
    r = SemObj.OpenStream("207.212.18.130",54320)
    SemKey = xWeb & "-SHIPPINGDATA"
    SessionData = Name & Chr(253) & Address & Chr(253) & OtherInfo
    NumSecsTimeout = 60 * 20
    ChannelName = ""
    ForceAdd = 1
    r = SemObj.AddSemaphore("207.212.19.130", SemKey, SessionData, NumSecsTimeout, ChannelName, ForceAdd)
    '--- Now note... I did not need to send the data across
    '--- The remote machine could have read from my SemaphoreServer
    '--- I am telling it here to run in a separate thread on the remote machine...
    '--- I could also instruct it to run while I wait for a response
    RunInThread = 1
    r = SemObj.RunRemoteScript("207.212.19.130",_
    "C:\Scripts\SHIPIT.VBS", "VBScript", "",_
    "ShipIt(" & Chr(34) & xWeb & Chr(34) & "), RunInThread)
    SemObj.CloseStream("127.0.0.1")
    Set SemObj = Nothing
    %>

    On the remote server under “C:\Scripts\SHIPIT.VBS”

    Sub ShipIt(byVal WebID)
      Set SemObj = CreateObject("SemClient.Control")
      r = SemObj.OpenStream("127.0.0.1",54320)
      SemKey = WebID & "=SHIPPINGDATA"
      SessionData = SemObj.GetSemaphoreContents("127.0.0.1", SemKey)
      If SessionData = "" Then
        '--- Uh-oh error... better send an email somewhere
      Else
        Name = SemObj.ExtractVal(SessionData, Chr(253),1)
        Address = SemObj.ExtractVal(SessionData, Chr(253),2)
        '--- Get the rest of the data
        '--- ADO is fully available
        Set DataConn = CreateObject("ADODB.Connection")
          '--- Update the database
          r = SemObj.DeleteSemaphore("127.0.0.1", SemKey)
        SemObj.CloseStream("127.0.0.2")
        '--- Delete it from the remote machine... I am not opening a stream
        '--- but rather am going direct
          r = SemObj.DeleteSemaphore("206.1.131.1", SemKey)
          Set SemObj = Nothing
    End Sub

The MonkeyWrench

Well, the Tech Support department just ruined your day. You thought you had all your problems handled, but now they come along and tell you “Well, we want to know every time the user navigates to our support page.” Not your position to ask why, but just get the job done.

There are a couple of approaches we can take to handle this:

Session Sharing – Session sharing is the ability to view intra-session information from your ASP pages. To implement this technology, you can either use one of the data sharing techniques described previously or use utilize LowTide Software’s ASPBroker.

The ASPBroker component utilizes two objects that allow individual ASP sessions to interact with one another. These objects, ASPSession and ASPBrokerManager, expose Session level variables to other sessions.

In your Global.asa file you would insert the following code:

<OBJECT RUNAT=Server SCOPE=Session ID=myBroker
  PROGID="ASPBroker.ASPSession">
</OBJECT>
Sub Session_OnEnd
  myBroker.ReleaseSession
  Set myBroker = Nothing
End Sub

In the first page on your web site you would add:

<%
c = myBroker.SessionCount
myBroker.SetKeys "TS-ON-DATE", "TS-ON-ID"
%>

Your session would now be “brokered” and ready for cross-session interaction.

On your tech support web page add the following code:

<%
OnD = "" & Session("TS-ON-DATE")
If OnD = "" Then
  Session("TS-ON-DATE") = Now
  Session("TS-ON-ID") = myGlobalUniqueID
End If
%>

Now create one more HTML page for your Tech Support users:

<HTML>
<HEAD>
<META HTTP-EQUIV="REFRESH" CONTENT=30>
</HEAD>
<BODY>
<TABLE>
<%
sessions = myBroker.SessionList
For i = 1 To UBound(session)

  onDate = myBroker.Value("TS-ON-DATE", sessions(i))
  onID = myBroker.Value("TS-ON-ID", sessions(i))
  Response.Write "<TR>"
  Response.Write "<TD>" & sessions(i) & "</TD>"
  Response.Write "<TD>" & onDate & "</TD>"
  Response.Write "<TD>" & onID & "</TD>"
  Response.Write "</TR>"

Next
%>
</TABLE>
</BODY>
</HTML>

You can get ASPBroker at http://www.inxpress.net/~kenyon/aspbroker/.

Server Push Approach – Automated Solutions Group’s SemaphoreServer product (there it is again) offers another method of accomplishing this task. SemaphoreServer supports the concept of “channels.” When data is added to the server, you can optionally specify a channel name. All data is grouped under that channel (which is really just another semaphore). The user can also “subscribe” to the channel and a link is opened to the server. Any data added or changed on the channel is then “pushed” via an ActiveX event to the client.

On your tech support web page :

Set SemObj = Server.CreateObject("SemClient.Control")
xWeb = SemObj.WebSessionID
beenHere = "" & SESSION("BEENHERE")
If beenHere = "" Then
  SemKey = xWeb & "=ON-TS-WEB"
  SessionData = Now
  NumSecsTimeout = 50 '--- Timeout after one minute
  ChannelName = "TECHSUPPORT"
  ForceAdd = 1
'--- Yes, I know I did not open a stream
'--- For one-shot connections, there is no need
  r = SemObj.AddSemaphore("127.0.0.1", SemKey, SessionData, NumSecsTimeout, ChannelName, ForceAdd)
  Session("BEENHERE") = "Y"
End If

Now, we need to create a VB application to access the data:

  1. Start Visual Basic and create a new product.

  2. Open the components window (Ctrl-T) and select the SemClient control.

  3. Add it to the default form.

  4. Create a button on the form and set the caption to "Connect".

  5. Add the following code to the button's click event (replace the IP Address with the server address):

    r% = semclient1.SubscribeToChannel("127.0.0.1","TECHSUPPORT")

  6. Create a button on the form and set the caption to "Disconnect."

  7. Add the following code to the button's click event (replace the IP Address with the server address):

    r% = semclient1.CloseChannel("127.0.0.1","TECHSUPPORT")

  8. To the semclient1's ChannelIncoming event code, add:

    Private Sub SemClient1_ChannelIncoming(ByVal ChannelName As String,
    ByVal ChannelData As String)
      MsgBox("Got this data " & ChannelData)
    End Sub

Run the program! Now anytime someone accesses the web page, a message box appears for the user.

In Conclusion

I really don’t expect you to have the need to actually build a site as complex as the one I’ve described. What I do hope you get from this article, is perhaps the tidbit of information or the idea planted in your mind on next how to solve your data sharing and session maintenance requirements.

Created : 7/7/2003 2:44:56 PM (last modified : 7/7/2003 2:44:51 PM)
Rate this article!
Comments