Producer & Consumer Message Handling

Audience Level

Intermediate to advanced

Summary

Two functions to implement basic message passing between ASP pages using a store of text files. A worked example follows the source. Recently I discovered file-based PHP Sessions and the functions below can be used in pretty much the same way but for ASP.

Source Code

/*
Function: ServerMessageProducer()
Description:
Returns:
History:
20040928 1859BST    v1      Andrew Urquhart     Created
20050127 0247GMT    v1.1    Andrew Urquhart     Modified to load and save files using ADODB.Stream
*/

function ServerMessageProducer() {
    try {
        // PRIVATE METHOD DEFINITIONS
        function getRandomString(intLength) {
            try {
                if (!intLength) {
                    throw new Error(1, "Required parameter \"intLength\" was not defined");
                }

                var strRand             = [];
                var strRandAlphabet     = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
                var strRandAlphabetLen  = strRandAlphabet.length;
                for (var tloop=0; tloop<intLength; ++tloop) {
                    strRand.push(strRandAlphabet.charAt(Math.floor(Math.random() * strRandAlphabetLen))); // Produce chars in ASCII range 0-9a-zA-Z
                }
                return strRand.join("");
            }
            catch (err) {
                propError(err, arguments);
            }
        }


        // PUBLIC METHOD DEFINITIONS
        function getId() {
            try {
                return strId;
            }
            catch (err) {
                propError(err, arguments);
            }
        }

        function set(strFieldName, strFieldValue) {
            try {
                if (typeof strFieldName != "string" || objReBadInput.test(strFieldName)) {
                    throw new Error(1, "String expected for parameter \"name\" or string contained unsuitable characters");
                }
                if (typeof strFieldValue != "string") {
                    throw new Error(2, "String expected for parameter \"value\"");
                }
                if (blnSaved) {
                    throw new Error(3, "Unabled to set new values after message has been saved");
                }

                objMessage[strFieldName] = strFieldValue;
            }
            catch (err) {
                propError(err, arguments);
            }
        }

        function save() {
            try {
                if (blnSaved) {
                    throw new Error(1, "Message could not be saved - it has already been saved once before");
                }

                var objContent  = [];
                for (var i in objMessage) {
                    objContent.push("[name]");
                    objContent.push(i.replace(objReEncode, "\\$1"));
                    objContent.push("[value]");
                    objContent.push(objMessage[i].replace(objReEncode, "\\$1"));
                }
                var strContent = objContent.join("\r\n");

                var objStream = Server.CreateObject("ADODB.Stream");
                objStream.Open();
                try {
                    objStream.CharSet = "UTF-8";
                    objStream.WriteText(strContent);
                    objStream.SaveToFile(strFilePath, adSaveCreateNotExist);
                }
                catch (err) {
                    throw err;
                }
                finally {
                    objStream.Close(); // Always close the stream regardless of what happens
                }

                blnSaved = true;
            }
            catch (err) {
                propError(err, arguments);
            }
        }

        // PRIVATE VARIABLES
        var objMessage      = [];
        var strId           = getRandomString(32); // The identifier for this message
        var blnSaved        = false;
        var objReBadInput   = RegExp().compile("^[\\s\\n\\r]*$", "gm");
        var objReEncode     = RegExp().compile("(\\[|\\])", "g");
        var strFileRoot     = Application("SERVERMESSAGES_PATH");
        var strFilePath     = strFileRoot + strId + ".dat";

        // PUBLIC METHODS
        this.getId  = getId; // Only provide a method rather than a public property as this prevents people from externally setting the strId
        this.set    = set;
        this.save   = save;
    }
    catch (err) {
        propError(err, arguments);
    }
}


/*
Function: ServerMessageConsumer()
Description:
Returns:
History:
20040928 1858BST    v1      Andrew Urquhart     Created
*/

function ServerMessageConsumer(init_strId) {
    try {
        // PRIVATE METHOD DEFINITIONS
        function clearUp() {
            try {
                if (blnDeleted) {
                    throw new Error(1, "Message already deleted");
                }
                if (!blnLoaded) {
                    throw new Error(2, "Unabled to delete a message that has not been loaded");
                }

                var strFilePath = strFileRoot + strId + ".dat";

                var objFSO = Server.CreateObject("Scripting.FileSystemObject");
                if (!objFSO.FileExists(strFilePath)) {
                    throw new Error(3, "Unable to delete message. Message does not exist for id=\"" + strId + "\"");
                }

                objFSO.DeleteFile(strFilePath, true);

                blnDeleted = true;
            }
            catch (err) {
                propError(err, arguments);
            }
        }


        function load(param_strId) {
            try {
                if (typeof param_strId != "string") {
                    throw new Error(1, "String expected for parameter \"id\"");
                }
                if (blnDeleted) {
                    throw new Error(2, "Message no longer exists");
                }
                if (blnLoaded) {
                    throw new Error(3, "Message has already been loaded");
                }

                var strNewFilepath  = strFileRoot + param_strId + ".dat";


                var objFSO = Server.CreateObject("Scripting.FileSystemObject");
                if (!objFSO.FileExists(strNewFilepath)) {
                    throw new Error(4, "Message not found for id=\"" + param_strId + "\"");
                }
                objFSO = null;

                var objStream = Server.CreateObject("ADODB.Stream");
                objStream.Open();

                try {
                    objStream.CharSet = "UTF-8";
                    objStream.LoadFromFile(strNewFilepath);

                    var strFieldName    = "";
                    var strFieldValue   = "";
                    var intState        = 0; // Means expecting fieldname delimiter next line

                    while (!objStream.EOS) {
                        var line = objStream.ReadText(adReadLine);

                        if (intState == 0 && objReFieldName.test(line)) {
                            intState = 1; // Means expecting fieldname next line
                        }
                        else if (intState == 1) {
                            if (objReBadInput.test(line)) {
                                throw new Error(5, "Saved message contained invalid characters. Message may be corrupt.");
                            }
                            strFieldName = line.replace(objReDecode, "$1");
                            intState = 2; // Means expecting fieldvalue delimiter next line
                        }
                        else if (intState == 2 && objReFieldValue.test(line)) {
                            intState = 3; // Means expecting fieldvalue next line
                        }
                        else if (intState == 3) {
                            strFieldValue += line;
                            intState = 4; // Means expecting more input for fieldvalue or fieldname delimiter
                        }
                        else if (intState == 4) {
                            if (objReFieldName.test(line)) {
                                // Reached end of input, save name value pair to message object and reset for next pair
                                objMessage[strFieldName] = strFieldValue.replace(objReDecode, "$1");
                                strFieldName = strFieldValue = "";
                                intState = 1; // Already seen fieldname delimiter line, expect field name next
                            }
                            else {
                                // More input for field value (NB: add newline that was consumed by ReadLine())
                                strFieldValue += "\r\n" + line;
                            }
                        }
                    }
                    if (intState == 4) {
                        // At end of file the save condition doesn't fire, cope with it here
                        objMessage[strFieldName] = strFieldValue.replace(objReDecode, "$1");
                    }
                }
                catch (err) {
                    throw new Error(err.number, "Loading failed:\r\n" + err.description);
                }
                finally {
                    // Loading a file overrides the default instantiated id and filepath values
                    blnLoaded = true;
                    strId = param_strId;
                    strFilePath = strNewFilepath;

                    objStream.Close(); // Always close the stream regardless of what happens
                }
            }
            catch (err) {
                propError(err, arguments);
            }
        }


        // PUBLIC METHOD DEFINITIONS
        function get(strFieldName) {
            try {
                if (typeof strFieldName != "string" || objReBadInput.test(strFieldName)) {
                    throw new Error(1, "String expected for parameter \"strFieldName\" or string contained unsuitable characters");
                }

                return objMessage[strFieldName];
            }
            catch (err) {
                propError(err, arguments);
            }
        }

        function getAll() {
            try {
                return objMessage;
            }
            catch (err) {
                propError(err, arguments);
            }
        }


        // PRIVATE VARIABLES
        var objMessage      = [];
        var strId           = null;
        var blnLoaded       = false;
        var blnDeleted      = false;
        var objReBadInput   = RegExp().compile("^[\\s\\n\\r]*$", "gm");
        var objReDecode     = RegExp().compile("\\\\(\\[|\\])", "g");
        var objReFieldName  = RegExp().compile("^\\[name\\]$");
        var objReFieldValue = RegExp().compile("^\\[value\\]$");
        var strFileRoot     = Application("SERVERMESSAGES_PATH");
        var strFilePath     = strFileRoot + init_strId + ".dat";

        // PUBLIC METHODS
        this.get = get;
        this.getAll = getAll;


        // CONSTRUCTOR LOGIC
        if (init_strId) {
            if (typeof init_strId != "string") {
                throw new Error(1, "String \"id\" parameter expected in constructor");
            }

            try {
                load(init_strId);

                // If load successful, 'strId' is no longer null
                if (!strId) {
                    throw new Error(2, "Unable to read message id \"" + init_strId + "\" - unknown error");
                }
            }
            catch (err) {
                throw err;
            }
            finally {
                // Always delete the message even the message wasn't read
                clearUp();
            }
        }
        else {
            throw new Error(3, "Constructor expects parameter");
        }
    }
    catch (err) {
        propError(err, arguments);
    }
}

Download

Download the source directly.

Example Usage

Here's an example from my contact form. When a user submits an invalid response (e.g the contact form message is blank) I save a message on the server and transfer processing from the form submission script back to the form display script like so:

var objServerMessage = new ServerMessageProducer();
objServerMessage.set("strSubject",      strSubject);
objServerMessage.set("strName",         strName);
objServerMessage.set("strEmail",        strEmail);
objServerMessage.set("strMessage",      strMessage);
objServerMessage.set("strErrorMessage""[ YOUR ERROR MESSAGE TEXT HERE ]");
objServerMessage.save();
Response.Redirect("/contact/?errorMsgId=" + objServerMessage.getId());

After the redirect the form display script notices the querystring messageId, looks for the saved message, reads it then deletes it:

var errorMsgId  = trim(Request.QueryString("errorMsgId").Count ? Request.QueryString("errorMsgId").Item(1) : "");
if (errorMsgId) {
    var objServerMessage = null;
    try {
        objServerMessage = new ServerMessageConsumer(errorMsgId);
    }
    catch (err) {
        // Message didn't exist or has expired
    }

    if (objServerMessage) {
        // Re-populate form values.
        // NB: Before Response.Write()'ing these variables you must Server.HTMLEncode() them first!
        subject = objServerMessage.get("strSubject");
        name    = objServerMessage.get("strName");
        email   = objServerMessage.get("strEmail");
        message = objServerMessage.get("strMessage");

        var strErrorMessage = objServerMessage.get("strErrorMessage");
        Response.Write("<h2>Submission Errors!</h2><div class=\"error\">" + strErrorMessage + "</div>");
    }
}

Since there is a risk of messages not being automatically consumed I also include the following in the global.asa's Application_OnStart() to remove messages older than one day:

// DELETE CONTENTS OF SERVERMESSAGES FOLDER THAT ARE OLDER THAN ONE DAY
var objFSO          = new ActiveXObject("Scripting.FileSystemObject");
var objFolder       = objFSO.GetFolder(Application("SERVERMESSAGES_PATH"));
var intExpiryDate   = new Date(new Date().valueOf() - 24*3600*1000).valueOf();

for (var fc=new Enumerator(objFolder.files); !fc.atEnd(); fc.moveNext()) {
    var objFile = fc.item();
    var intDateCreated = new Date(objFile.DateCreated).valueOf();
    if (intDateCreated <= intExpiryDate) {
        objFile.Delete();
    }
}

Notes

If you encounter syntax errors with the ADO constants (i.e. “adReadLine”) then you need to add the ADODB Metadata Type library declaration to the top of your “global.asa”, e.g.:

<!--METADATA NAME="Microsoft ActiveX Data Objects 2.5 Library" TYPE="TypeLib" UUID="{00000205-0000-0010-8000-00AA006D2EA4}"-->

Advertisement

Feedback

Voting Panel
Is this useful?
or
Did you find any bugs?
or
Did it solve your programming problem?
or
Rate this script: (0=poor, 5=very good)
Answers are anonymous, only the combined totals are stored. Uses cookies.