I'm writing this as an introduction to PerlScript. Many people have asked me if a document like this is available and my answer always had to be "No.". I think I should change that, so here is my effort.
It is my hope that this document will bring more people to PerlScript, and away from having to use that dreaded VBScript.
Active Server Pages are a way of creating dynamic content on the web server. They work much the same way as CGI, or ISAPI applications - by running some code on the server and returning the information to the browser. Writing ASP code is a lot like writing a CGI application using a scripting language. The script you write however can be embedded within HTML. Microsoft see this as an advantage because it allows you have script and code become one. I see it as a very bad thing. See my previous tutorial for my reasons, but basically application developers are not designers - and I think a template based system is better.
ASP code looks like ordinary scripting code, written in your language of choice
(of course this will be PerlScript - right?), but to denote the start and end of
script you use the delimiters "<%" and "%>". Outside of those
delimiters you can use any text or html, and it will be rendered as such in the
browser. If your HTML or text is outside of delimiters, but inside a loop
construct, that text will be output as many times as the loop goes around:
1 : <% @LANGUAGE = PerlScript %>
|
1 : <font size="1">Font Size 1</font><br>
|
That's pretty much all you need to know. The rest is just Perl. All Perl functions are supported - including things like backticks, system, open, sockets. There are some issues in build 502 with opendir and backticks, but these should be sorted out with build 503.
Anyone who is reading this who has come from VBScript might want to ask me "Why should I use PerlScript - VBScript works adequately?". I think the key there is "Adequately". Where VBScript works if you just want to use the built in object model, and do some simple database stuff, what if you want more power. Can you, in VBScript, do the following (without resorting to a server side OLE object):
Of course not everyone reading this article will be coming from a VBScript background. Perhaps you've come from a Perl background, and have used CGI, or mod_perl or some such solution. What can PerlScript offer you? I'll cover exactly what it can offer in detail, but the key thing is the object model. This is the built in system that allows access to: Inter-process communication, session management, form and query string values, cookies, timeouts, and many more things. Hopefully you will be used to CGI.pm handling all these things for you, but as you'll see there are some things that ASP does that even CGI.pm doesn't do (and probably shouldn't)
Before we dive right in there are a few basics to learn. Most of these are
based around how Perl deals with OLE objects. The ASP object model is based
around OLE modules that are created for you in the main namespace at runtime.
The key here is to know your object model inside out. There are unfortunately
some simplifications that Microsoft have hard coded into VBScript that makes
using OLE objects a lot simpler, however these simplifications don't fit well
into the Perl way of doing things (in fact they would look broken in most
languages). For example, some of the VBScript OLE syntax looks like you are
assigning something to a function call:
Session('UserId') = myUserId
|
Method calls are implemented just like Perl function calls on objects (see
the perlobj help page for details):
$filename = $Server->MapPath('/index.html');
|
OLE doesn't make any distinction between accessing properties and
performing function calls, they all look like:
Object.Name
|
Perl makes a distinction here, which I think is beneficial. Luckily the
documentation for the ASP object model (the documentation gets installed on
your server when you set up IIS4 under /iishelp) always states which are
methods, which are properties and which are collections. Properties are
accessed in Perl just like a normal Perl object's properties, with a hash
dereference:
$Response->{ContentType} = 'image/gif';
|
The ASP object model contains a number of what are called "Collection Objects". Collection objects are just ordinary OLE objects that support a number of special properties and methods. They all have a Count property and an Item method. Some Collection objects also support the Key method. Collections are sort of like Perl hashes, but also like Perl arrays. This is because the Item method supports access by either a Key (like a hash) or via a numeral (like an array). PerlScript supports both methods of access, although it doesn't use the appearance of accessing either a hash or an array - it simply uses the method access, as used by OLE. Note that hash access was implemented in earlier versions of Win32::OLE (specifically Activestate perl prior to release 500), and still exists but in a depracated manner (disabled under "use strict").
Win32::OLE
also provides support for what is known as "default method call". This means that
objects have default methods, and these default methdods can be called by simply
referencing the object as though it were a function. One minor problem here is that
this can't pass all the way up the tree to the first object. For example, the
following is valid in VBScript:
1 : Application.Contents.Item('One') = 1
|
1 : print $Application->Contents->Item('One');
|
print $Application('One');
|
Also note that some Collections return an object, not a value. For example, the
Cookies collection in the Request object returns a RequestDictionary object, so you
need to dereference the object to get at the underlying value - this is something
VBScript does automatically, but that you have to do manually in PerlScript:
1 : $currUserId = $Request->Cookies->Item('UserId')->{Item};
|
In the code above, Cookies is the name of the collection object. This code would
have looked like this in VBScript:
1 : currUserId = Request.Cookies.Item('UserId')
|
Note that if we had written the above as:
$currUserId = $Request->Cookies('UserId')
|
if ($currUserId == 1) { ... }
|
$Response->Write("$currUserId");
|
The next thing to know is that only the cookies collection returns an OLE
object.
All the other collections in the ASP object model are VariantDictionary's, which
are
simple scalars, so only when using Cookies do you have to dereference. However be
aware that other OLE objects may return an OLE object when accessing a Collection,
for example the ADO RecordSet Field collection:
my $ColValue = $RS->Fields('ColName')->{Value};
|
The Win32::OLE module is quite changed since the Activestate Perl build 316. There were many changes implemented to ensure future compatibility, and yet remain backwards compatible. Perl's "use strict" option disables many of the backwards compatibility features, but also stops certain features from working. Currently Jan Dubois is working to enable those features under use strict, but until then all the code below is clearly marked as to whether or not it works with "use strict".
Jan has now completed most of this work with the advent of build 503. Make sure you are using that version otherwise some of the code below won't work. Specifically, this is anything that uses the method SetProperty (only used below for setting collection objects, which luckily doesn't happen very often as most collections are read only in this object model).
Also note that it is not possible to "use strict" with PerlScript without
using either my Win32::ASP module with the :strict option enabled:
1 : use strict;
|
1 : use strict;
|
The object model is the key to ASP, as VBScript users will know. Basically the object model is a set of OLE objects that control various things to do with your web environment.
The application object is responsible for sharing information between the whole web application, this can be your whole web site, or one area that you mark off as being a separate application (using the virual directory system).
If you are using the Frontpage extensions, this would be called a new Web.
Because of the issues of multiple users, it is advisable to use the application object for information sharing as little as possible. There is a lock/unlock method provided to prevent concurrency issues, but this has a performance impact.
Sharing information with the application object is done through the use of application variables. These are like global variables to your application. Application variables are stored in the two collections Contents and StaticObjects. Contents are "normal" application variables, and StaticObjects is read only and contains just those objects you created with the <OBJECT> tag (using SCOPE=APPLICATION).
The contents collection, as stated above, stores all the application variables. Use it carefully.
Syntax
1 : # Get value
|
my $value = $Application->Contents->Item($key)
|
For iteration:
1 : use Win32::OLE qw( in );
|
You can store a scalar, an array or an OLE object (also called a server component in ASP terms) in Application variables. If you want to store a hash you need to flatten it into an array first, or flatten it into a scalar using Data::Dumper.
The StaticObjects collection is much the same as the above, only it contains the objects created with Application scope using the <OBJECT> tag. (details of this are PerlScript independant and available in the ASP docs). This collection is read only.
Syntax
my $value = $Application->Contents($key);
|
The Lock method is there to allow you to get around concurrency issues when accessing global variables. Be very careful with this It's very easy to get yourself into a deadlock situation. Read a book about concurrent programming. This is the advice I give you on using Lock/Unlock:
Syntax
$Application->Lock;
|
An example of using this is you might want to maintain a count of how
many accesses your page has had:
1 : $Application->Lock;
|
We just saw the unlock method in action, but here's the syntax in case
you missed it:
$Application->Unlock;
|
The request object is used to get the information passed to the server from the browser. This information includes cookies, form values, the query string (anything after a '?' in a URL), client certificates and the server environment variables (because these aren't available in %ENV).
Client Certificates are used for security as specified in the X.509 standard. Browsers send a client certificate if using the SSL3.0/PCT1 protocol (URL's starting with https:// instead of http://), and the server requests a certificate to prove identity. Requesting of client certificates is setup in the server configuration (i.e. the IIS Management Console).
Syntax:
1 : my $cert = $Request->ClientCertificate('KeyName');
|
Example
1 : <%
|
1 : <%
|
The cookies collection in the request object is used to retrieve cookies. Setting cookies is done by the Response object (see below). If you don't know what cookies are, or what they are used for I'm not going to tell you, and you are probably on the wrong planet... ;-)
use strict;: Cookies now work with "use strict;" when using ActivePerl build 503 or greater. Thanks to Jan Dubois for that. However I still recommend using Win32::ASP for its greater flexibility in setting cookies.
Syntax:
1 : my $cookieVal = $Request->Cookies('CookieName')->{Item};
|
1 : foreach my $f (Win32::OLE::in ($Request->Cookies)) {
|
1 : foreach my $f (Win32::OLE::in ($Request->Cookies('CookieName'))) {
|
The Form collection is used to retrieve values from forms of method="POST". You may not find it neccessary to use the Forms collection if using Win32::ASP as I've wrapped up getting form values into an easy to use function that figures out whether it's a GET or POST request.
Syntax:
my $formVar = $Request->Form($name)->Item($index);
|
Parameters:
$Request->Form($name)->{Count}
|
You can iterate through all form values in much the same way as you can with cookies. See above for details on that.
To make life a lot simpler, I recommend using Win32::ASP for form processing. This has the advantage of allowing you to invisibly switch between GET and POST, and also uses Perl's context return system, so if you ask for an array when the form element was a multiselect or a set of checkboxes, you get an array of values.
For example, say we had a form like this:
1 : <form>
|
1 : my ($Comp, $Read, $Other);
|
1 : use Win32::ASP;
|
The QueryString collection object is used to get at the values normally associated with the QUERY_STRING environment variable (under CGI applications). This is everything after the "?" on a URL line. Normally these are the results of form submission with the GET method, however it can also result from a normal href: <a href="http://www.url.com/test.asp?query=string">
This collection acts in an identical way to the Form collection, so I won't
go into detail, but this is the syntax:
$Request->QueryString($name)->Item($index)
|
Win32::ASP is again useful here, and provides a more Perl-like interface for you. It also figures out whether you are using GET or POST (i.e. QueryString or Form) and does the right thing automatically, which is great for debugging. I recommend you use GET for debugging/development and switch to POST for release.
The ServerVariables collection is what would normally be %ENV in a CGI script (or any normal web server programming environment), however VBScript is even limited to not being able to access the environment, so it got implemented as another OLE collection.
I'm not going to explain all the environment variables that the server
returns here, see either some documentation on CGI, or the ASP documentation
that comes with IIS. The syntax for getting individual ServerVariables is:
my $value = $Request->ServerVariables($name)->{Item};
|
1 : foreach my $env (in ($Request->ServerVariables)) {
|
The TotalBytes property is mostly undocumented in the ASP documentation, but it is basically equivalent to $ENV{CONTENT_LENGTH} in CGI terms. It is the total number of bytes sent to the client in a POST request, and is retrieved with BinaryRead (see below).
This is useful if you wish to do any sort of file upload using ASP, in fact it is soon going to be possible to use the file upload facilities in CGI.pm with Win32::ASP. This will be achieved with the tie mechanism, to tie STDIN into the Win32::ASP class.
The BinaryRead method is useful for the above in processing file uploads
with
ASP. Here is a translation of the example code that comes with IIS4:
1 : use Win32::OLE::Variant;
|
The response object is responsible for sending output to the client's web browser. Any type of information can be sent, although HTML is the default. You can also send any type of HTTP header with the response object, such as cookies, redirection and status.
The response cookies collection is used for setting cookies, (whereas the request cookies collection views the contents of current cookies). Be careful not to assume any dualism between when cookies are set, and reading them again (i.e. don't assume that once set with the response object, that you can immediately read them again in the same script with the request object - either use a global variable, or wait for the next request).
Warning: Cookies do not get set if you try and send them after sending any output to the browser whatsoever. You can avoid this problem by turning on buffering in your page. See the Buffer property to do this.
Syntax:
1 : $Response->Cookies->SetProperty('Item', 'Cookie2', "Test");
|
Cookies also have other properties, I think these are probably self
explanitory:
1 : $Response->Cookies('CookieName')->{Expires} = "December 31, 2005";
|
Example
1 : $Response->Cookies->SetProperty('Item', 'Type', "Chocolate Chip");
|
We are sorry about the verbosity of the SetProperty function, when compared to VBScript, but anything else doesn't quite fit with the lanaguage. For a good argument about it (I've had plenty) contact Jan Dubois (jan.dubois@ibm.net), and have it explained why it has to be that way. (sorry Jan!)
Finally I would just like to mention that Win32::ASP has a more elegant interface to setting the cookies, which does it a more perl-like way. It uses a hash to set things like the Expires and Path, and does it all in one function call. It also allows you to set cookies that expire in a definite time from now, such as "+3h" for expiring in 3 hours time.
This is a simple boolean value that says whether or not your output is cached before being sent to the browser. This allows you to send headers after you have used one of the output functions ($Response->Write or $Response->BinaryWrite).
Buffer cannot be set after any output has been sent to the client. For this reason it should be set as the first line of your script (or possibly after including any modules using "use ModuleName;".
Syntax
$Response->{Buffer} = 1;
|
The CacheControl property enables you to override the default value of
"Private" which means that proxy servers won't cache ASP output by default.
This
is probably a good thing because most ASP pages will be dynamic, nevertheless,
I
won't judge you on using it, so here's the syntax:
$Response->{CacheControl} = "Public";
|
This controls the character set that is sent as part of the Content-Type
header. This is rarely used in PerlScript because Perl doesn't yet support wide
characters very well (Perl 5.006 will, but that's a long time off).
$Response->{CharSet} = "ISO-LATIN-7";
|
The server does not check if your charset is valid - it simply inserts the string.
The ContentType property allows you to send different types of data back to the browser (such as a GIF, or a PDF file for example). The default ContentType is "text/HTML".
Syntax
$Response->{ContentType} = "image/GIF";
|
1 : use Win32::OLE::Variant;
|
The Expires property allows you to force an expire of the page. This is to prevent a browser cache from keeping your page too long (or prevent the browser from caching your page totally)
Syntax
$Response->{Expires} = $minutes;
|
The ExpiresAbsolute property is similar to the Expires property, only you specify a date and time in the VBScript date/time format.
Syntax
$Response->{ExpiresAbsolute} = "Month dd, YYYY HH:MM:SS";
|
This read only property tells you if the browser is still waiting for results from the server, or if they gave up and went somewhere else. This is quite useful for long scripts, although why IIS doesn't kill the script itself is beyond me.
Syntax
if ($Response->{IsClientConnected}) { ... }
|
PICS is the Platform for Internet Content Selection or something like that. It stops little kiddies seeing dirty porn on the internet, anyway if you need to use PICS, you'll know what it's all about.
Syntax
1 : $Response->{PICS} = '(PICS-1.1<http://www.rsac.org/ratingv01.html> \
|
The Status property allows you to modify the status line returned by the server. See the HTTP spec for valid status headers.
Syntax
$Response->{Status} = "401 Unauthorized";
|
The AddHeader method allows you to add arbitrary HTTP headers to your output. Some of the functionality of AddHeader is duplicated (for example Cookies), and the IIS docs recommend you use the other methods if available, however I think you're probably smart enough to figure out what your needs are.
Syntax
$Response->AddHeader('My-Header', 'Value');
|
As with the other things that output HTTP headers, call this before normal output, or turn on buffering.
This allows you to write data into the IIS log file. Strikes me as a dumb idea, but if you want to do it, go ahead... (oh, and why isn't this a member function of the Server object!)
Syntax
$Response->AppendToLog("This is a silly idea");
|
There was a brief example of using BinaryWrite above (under ContentType), so I won't go into detail. Needless to say it outputs binary instead of converting from wide characters to ASCII.
Syntax
$Response->BinaryWrite($variant);
|
The Clear method deletes all buffered output. It will not clear any output if you didn't turn on buffering (see $Response->{Buffer} above), in fact it will create a runtime error. It also won't clear any headers you sent.
Syntax
$Response->Clear;
|
When exiting a PerlScript prematurely it's not sufficient to just 'die' or 'exit', you have to actually tell the server to stop processing ASP as well. Luckily for you I've overloaded both "die" and "exit" in Win32::ASP so as long as you include my module you don't have to worry about this.
Syntax
$Response->End;
|
The flush method makes the server go to the toilet for you. Damn, that's not it. Instead it flushes all buffered output. As for the Clear method, this only works when buffering is turned on
Syntax
$Response->Flush;
|
This method tells the browser to go to another URL.
Syntax
$Response->Redirect($URL);
|
Unless you are using Win32::ASP, you'll get to know the Write method quite well. Simply put it sends ASCII output to the web browser.
Syntax
$Response->Write($output);
|
<TABLE WIDTH=100%>
|
$Response->Write("<TABLE WIDTH=100%\>");
|
A good way to use $Response->Write is with Perl's heredoc format:
1 : $Response->Write(<<EOF);
|
The Server Object provides access to methods and properties on the server.
This is the maximum amount of time a script will run before the server terminates it automatically. It cannot however terminate a running server component (an OLE object).
Syntax
$Server->{ScriptTimeout} = $secs;
|
This is the same as Win32::OLE->new (see the docs for Win32::OLE for more on this). It creates an instance of an OLE object (the ASP docs calls these Server Components).
Syntax
my $component = $Server->CreateObject($progID);
|
Server created objects have page scope. This means they will be destroyed at the end of the page - but make sure you clean up the object first. For example, make sure you close ADO.Connections, otherwise you end up with dangling connections.
To change the scope of the object, the ASP docs detail storing the created object in either a Session (see below) or Application variable. This doesn't work with PerlScript at the moment - some work needs to be done investigating how this would be achieved.
You can however use a <OBJECT> tag with RUNAT=Server and SCOPE=[APPLICATION|SESSION]. This should work, although I've not personally tested it (anyone?).
You can destroy the object just as you can with Win32::OLE, by undef'ing it:
undef $component;
|
The HTMLEncode method converts characters that would normally be unprintable as HTML to their HTML equivalents. These characters include "<", ">", "©" etc. This is extremely useful if you need to print out as HTML something you got from another source (e.g. a database or another file).
Syntax
<%= $Server->HTMLEncode("This is an XML tag: <p/>"); %>
|
This method is quite useful. Those of you who read my first tutorial (which promised a "Modularised PerlScript" tutorial this month - sorry ;-)) will have seen my use of it there. What this method does is read the server configuration and map HTTP server relative paths to machine relative paths.
Syntax
my $file = $Server->MapPath("/users/matt/index.asp");
|
MapPath will also map paths that don't exist, in doing so it will figure out the path as far as it can go using the virtual directories defined on the server, and then just append the path to it (converting "/" to "\" as it goes).
To use a module that exists in the current file's path, you can do this:
1 : my $path = $Server->MapPath($Request->ServerVariables('PATH_INFO'));
|
This escapes the characters in URL encoding format for the given string.
Syntax
my $encodedString = $Server->URLEncode($string);
|
Sessions are determined to be a particular user's time on this application. Because of the statelessness of HTTP (i.e. connections are dropped at the end of the request), sessions need to be defined by the server. It does this by two methods:
Session objects/variables exist in the same way that Application
objects/variables exist, and the access method is the same:
1 : # Read a session variable:
|
One nice thing you can store is an array. However you can't store a hash without
flattening it into an array first:
$Session->Contents->SetProperty('Item', 'Matt', [1,2,3]);
|
Contents is the collection that stores all session variables that weren't created using the <OBJECT> syntax. It can be accessed using either the Item method, or by simply using as a function (because Item is the default method). You can also iterate over the collection using "in".
Syntax
my $sessvar = $Session->Contents('MySessVar');
|
my $sessvar = $Session->Contents->Item('MySessVar');
|
To set a session variable (which is all a member of the Contents collection
is) you need to use the SetProperty method:
$Session->Contents->SetProperty('Item', 'MySessVar', 'My Session Value');
|
This collection contains all objects created with the <OBJECT> tag with a scope of SESSION. This is so you can iterate over all the static session objects you have created. This collection is read only.
Syntax
my $sessobj = $Session->StaticObjects('MyObj');
|
Sets or retrieves the codepage that will be used to display dynamic content. I guess this is in the session object so that a user can select a language for his session, and have it remain that way.
Syntax
$Session->{CodePage} = $codepage;
|
LCID is the Locale Identifier. It is an international standard. If you need to use it you probably know how it's formatted.
Syntax
$Session->{LCID} = $localeid;
|
This is the internal identifier for the session that the ASP dll uses to compare with the cookie sent. I think this value is read only, but the docs don't state it clearly (and I've never had a need to use this).
Syntax
my $sessId = $Session->{SessionID};
|
The timeout property is the length of time the session runs for in minutes. Unlike the script timeout property, this can be set lower than the default which is 20 minutes.
Syntax
$Session->{Timeout} = 60 * 8; # 8 hours
|
This will abort the current session, just as a timeout would. All objects stored in session objects are destroyed.
Syntax
$Session->Abandon;
|
This document, and the Win32::ASP module, and many other things Perl, are archived on my web site at FastNet Software Ltd. Click on Perl for all my Perl things (or call me if you have contract work available!).
I really like to distribute things under the Artistic Licence (the licence that Perl is distributed under), but I put a lot of work into this document, and so I followed Tom Christiansen's views on this:
This document is Copyright © Matt Sergeant. All Rights Reserved.
This document is not public domain. I hope someday that this might go into a book, or other publication (such as a magazine article) in some modified form. While it is copyright by me with all rights reserved, permission is granted to freely distribute verbatim copies of this document provided that no modifications outside of formatting be made, and that this notice remain intact. You are permitted and encouraged to use its code and derivatives thereof in your own source code for fun or for profit as you see fit. But so help me, if in six months I find some book out there with a hacked-up version of this material in it claiming to be written by someone else, I'll tell all the world that you're a jerk. Furthermore, your lawyer will meet my lawyer over lunch to arrange for you to receive your just deserts. Count on it.
Many thanks to the following people for their help: