This article will show how to retrieve remote binary files, multiple files, and additional information with only ASP and XML. This is an incredibly powerful technique, and the implementation in this two part series will deal with automatically updating a deployed ASP web application.
Contributed by Justin Cook Rating: / 63 August 09, 2005
A file is available to download for this article here.
Introduction
One of the most popular articles I’ve written was “Getting Remote Pages with ASP.” It seems that people found it as much of a lifesaver as I did, and were able to extend it in some very creative ways. This series of two articles deals with the best ones I’ve heard yet.
The programmer behind the popular e-commerce Web application Mall23 (http://www.mall23.com/store/Default.asp?vf=14) contacted me, asking me if I’d be interested in writing about his application. He told me he was able to extend the process of retrieving remote pages, to actually be able to retrieve remote binary files. This was his dilemma, to which you as a developer can no doubt relate:
“After developing several applications deployed to the public, I kept running into the same problem time and time again –- how to deploy updates. If you have ever used a third party ASP application, you know what I’m talking about. You install the software on your server and it sits, and sits. Six months later you find out that there have been three updates and tons of bug fixes. Problem is that no one told you about the updates, and now you have to figure out how to apply three different patches without destroying your site.”
“On the other hand, as a developer, it gets to be mind-boggling after deploying 30 different updates. Which customer has which version? Which client has modified what in which version? In the past, I’ve always added a version identifier to my software and had my patches loop through and update to the current version. Nice, but doesn’t solve the problem of making it transparent for the site owner. Many times I would find small bugs (like a misspelled word), and I’d have to sit on it until I had enough to justify my creating a whole new deployment, announcing it to the public, and getting them to spend the time to download and install it. What a pain. For everyone.”
It seems that major software developers had already solved this issue for client-based applications, devising a system call “auto-update.” Unfortunately, ASP has no inherent way of transferring files, let alone binary files!
My previous article described how to use a free, Microsoft-provided XMLHTTP object to retrieve Web pages as XML, and save them locally. While this works great for the Web, XML, and even text, it seems it just can’t be done for binary files.
Well if there’s one phrase that most developers refuse to utter or believe, “it can’t be done” is it! With no small amount of programming ingenuity -- and one more free tool provided by Microsoft -- a clean (and extensible) solution was created.
Microsoft has built a Base64-to-XML converter, called Msxml2.DOMDocument. We’ll use it to encode and decode the files, XMLHTTP to transmit the files, and finally ADO to save the files.
Before we begin coding, I’d like to explain what we are going to be doing. There are two ASP applications we will be creating. The first one we’ll refer to as the client, though it technically will still be an application running on a Web server. The second we’ll refer to as the server, which is basically the master application, which will serve updates to the clients.
The client and server may be on the same domain, but in a real life deployment would probably be on completely separate servers and domains. If you do not have the ability to create multiple domains, you can put both on the same domain and put the client script in one folder and the server script in another. Or you can create two different Web servers on the same machine with the technique described here: http://www.aspfree.com/c/a/IIS/Creating-Test-and-Production-Sites- With-Only-One-IIS-Server/.
Client Script
The client script initiates the communication for our auto-update demonstration. From this script, in our example, we will send the server a fake license key as an identifier. The server will then send back the files for the newest version, and any messages (errors, actions, and so on). The files will then be saved to the client's system, and any messages will be parsed and acted upon.
Server Script
The server script will send the client a mixed format stream. The stream will consist of text files, text messages, and Base64 encoded binary files. Okay, technically it’s not mixed format, but we are intermixing binary files with text files and text messages –- all within the same XML tree.
Because we are transferring binary files there is often a security risk involved on the server side. And, with an auto-update schema, we would often like to authenticate the client with a license key to make sure that the software has actually been paid for and properly activated. Our server script will not actually perform any authentication, but in a real life situation, the server would do a database lookup to find the license key, check to see whether it is active, and return files associated with the version we are updating to.
The server may also return messages to the client. Examples of these messages may be: disabling or re-enabling the auto-update, letting the client know the software has not yet been paid for, turning settings on or off, and more.
The sServerURL is the full URL to the location you put the server script. The sGETData combines the client key and current version (which I suggest you store in a config file and read each time you do the update), and you can throw in whatever data you would like to send the server via the QueryString. This can be used by the server to validate licenses, learn information about the client, or modify the server itself.
Now we briefly display an "updating" message to the client while the update routines are called, and the work is being done. For some reason, it’s always comforting to see a progress bar, even if it’s only an animated gif!
Within the PerformUpdate() subroutine, we send the request to the server, dumb the results into one big string variable, read that into an XML document, and parse the updates and messages.
This if/then will fail in cases where the sServerURL is either unreachable or if the connection to the server times-out. The On Error Resume Next allows us to throw in an "else" statement, which is basically a way to hotwire ASP to allow us a try/catch block.
The first things we parse are the messages or text data passed from the server to the client. In our example, we check for three variables: MESSAGE, VERSION, and CODE. You can check for whatever you like. Just make sure that there are corresponding variables sending data in the server script –- you’ll see these three in the server code below. As I mentioned before, the config file that will store the current version and key, can be updated with the new version value once the update successfully completes.
set objXMLList = xmlDOMDocument.getElementsByTagName("MESSAGE")
If ( objXMLList.Length > 0 ) Then
iMessage = Trim(objXMLList.item(0).text)
End If
set objXMLList = xmlDOMDocument.getElementsByTagName("VERSION")
If ( objXMLList.Length > 0 ) Then
sVersion = Trim(objXMLList.item(0).text)
End If
set objXMLList = xmlDOMDocument.getElementsByTagName("CODE")
If ( objXMLList.Length > 0 ) Then
sCode = Trim(objXMLList.item(0).text)
End If
After we get the text data, we begin getting the files. We loop through the XML looking for PATH nodes. For each PATH node, there is a corresponding TRANSFERFILE node. The PATH node contains path information that can be used to tell the client where to store the file. In our example, it simply holds the file name. If you like, you can add the full or relative path to retain the folder structure. The TRANSFERFILE node is the file itself.
You’ll notice that within the loop, we do a check for a PATH with the word “UpdateScripts” in it (see the code below). This is how we are flagging our update script file. This is a file named UpdateScripts.asp on the server. Within that file is ASP code that will run on the client. The ExecuteGlobal() runs the actual code from this file. If the code that is sent from the server fails, an error is generated and trapped. If the file is not an update script, the system calls the WriteFile() subroutine with the name and type of the file to transfer.
An error in the process will set the iMessage variable as E_UPDATESCRIPTFAILURE and exit the For loop. There you would want to alert the administrator that the update failed.
If ( iMessage <> "" ) Then
Set fsoFileObject = CreateObject("Scripting.FileSystemObject")
Set objXMLList = xmlDOMDocument.getElementsByTagName("MALL23")
For iX = 0 to (objXMLList.length - 1)
sPath = ""
For iY = 0 to ((objXMLList.item(iX).childNodes.length)-1)
If ( objXMLList.item(iX).childNodes(iY).nodeName = "PATH" ) Then
sPath = objXMLList.item(iX).childNodes(iY).text
End If
If ( sPath <> "" ) And
( objXMLList.item(iX).childNodes(iY).nodeName = " TRANSFERFILE" ) Then
If ( InStr(sPath, "UpdateScripts") > 0 ) Then
If ( Trim(objXMLList.item(iX).childNodes(iY).text) <> "" ) Then
There is nothing fancy about how we tell if a file is binary or text. We just look at the extension. Remember that we passed the file name in the PATH XML node.
Notice that with binary files, we are using the XML nodeTypedValue rather than the Text property. This property returns the data in whatever type it was set as on the server side (see the server code below). Since the server code sets all binary files as “bin.base64”, the client will then read the data in as Base64 encoded. Remember that the XML that is being passed also holds status data, such as the type.
The WriteFile() subroutine uses ADO to write our binary files. We can use normal text-based FSO operations for the .txt and .asp files. Here’s the meat of the routine, doing the actual saving work:
If ( iType = C_BINARY ) Then
Set objBinaryStream = CreateObject("ADODB.Stream")
Set fileWriteFile = fsoFileObject.OpenTextFile(sDestPath & sFileName, 2, True)
fileWriteFile.Write vData
fileWriteFile.Close
Set fileWriteFile = Nothing
End If
IMPORTANT: Make sure the final destination on the client has WRITE rights granted to the IUSR_computername account or the write will fail. To make this more robust, you can test permissions with an ON ERROR trap. Write a dummy file to the destination path. If an error is generated, you can display a nice message to the client, and send a message back to the server, telling the administrator that the update failed.
Conclusion
In this first of two articles, we saw how to connect to a remote server, retrieve text and encoded binary files, and save them locally. In the next article, we will see how to process the request on the server side to actually send the files. Here’s a link to the full working client script, and the two images referenced: