TOP | UP: Applied Frontier | NEXT: Web Site Management |
The term CGI (for "common gateway interface") refers to a means whereby Web pages can be programmatically generated in real time in response to a browser request for a document.
When a Web browser requests a document (because the user clicks on a link, or asks for a URL), a Web server somewhere on the Internet receives the request and sends the text of the document back to the browser, which displays it. Typically, the Web server obtains this text by fetching it from a file on disk. But with a CGI, the Web server passes the document request on to another application (the "CGI application") and receives the text from that application. How the CGI application obtains the text is an open question; typically, it "calculates" the text in some way. The CGI application might also perform other actions before returning the text; for instance, it might write to a database. Thus, CGIs can provide a Web browser interface to the computational and data-processing power of a remote computer over the Internet.
An ACGI is an asynchronous version of a CGI. With a normal CGI, both the Web server and the CGI application are tied up in conversation with one another from the moment the Web server passes the document request to the CGI application to the moment the CGI application gives the Web server the text. The Web server cannot respond to any other requests from the outside world while this is going on. By contrast, with an ACGI, the Web server passes the CGI application a document request and then goes on about its business: it assumes that the CGI application will send a response whenever it is ready.
On Mac OS, the way a Web server and a CGI application speak to one another is with Apple events.
Frontier is a natural choice as an ACGI application. It is programmable; it can calculate text; it can read text from files on disk or from its own database; and it can drive other applications, so they can perform auxiliary functions such as storage, lookup, and calculation. It can respond to custom Apple events; it can send Apple events. It is multithreaded, so it can receive an Apple event while others are still being processed. It implements semaphores, so threads won't trip over one another accessing data. Plus, with NetFrontier, you can easily develop CGI scripts locally and then upload them to a remote copy of Frontier.
By convention, the name of a document whose text Frontier is to provide usually ends in .fcgi. Let's presume we're going to adopt this convention. The Web server must be told in advance that requests for documents named in this way should be passed on to Frontier. We want the Web server to see Frontier as an ACGI application, so an alias to Frontier should be placed where the server will find it1 and given the name frontier.acgi. A new action needs to be defined, called FRONTIER, which corresponds to the alias frontier.acgi. And a new suffix mapping needs to be defined, which associates the action FRONTIER, the suffix .FCGI, and the MIME type text/html.
Frontier itself also must be configured. When you choose the WebServer menu item from the Suites menu, the first item in the WebServer menu that appears contains an important configuration choice: it says Use Script-based Trap, and you can check or uncheck it. This determines whether a script or a UCMD will be placed at system.verbs.traps.WWWΩ.sdoc. In the typical situation, where just one Web server program will be calling Frontier, and it is WebStar or a WebStar-compatible Web server, you should check this item.2 You should also use the next two items in the WebServer menu so that Frontier knows what Web server you are using and where your Web site folder is.
Thereafter, whenever the Web server receives a request for a document whose name ends in .fcgi, it sends Frontier an Apple event of type 'WWWΩ' /'sdoc'. Frontier routes the Apple event to system.verbs.traps.WWWΩ.sdoc , which calls scripts in the webserver suite. There is a "dispatcher" architecture: a script in the webserver suite takes the name of the requested document, removes the .fcgi suffix, and puts "suites.webserverScripts" in front of it to get the name of a script, which is then called.3 That script - your CGI script - is handed the information that accompanied the document request; it does whatever it does, and returns a complete piece of HTML text, which is sent back to the Web server and from there across the Internet to be displayed in the requester's browser.4
One way to make a CGI script is to choose New Script from the Main menu and set the popup menu to CGI Script. This generates a script in suites.webserverScripts with some shell statements and information to jog your memory as you write the script. The script has one parameter, adrParams; this is the address of a table holding information from the document request, and the names of its entries are shown in a comment.5 The text to be returned has been named htmltext; the text's construction has begun with a call to webServer.httpHeader() , and a local handler, add() , has been provided so that you can continue its construction. All you do is call add() repeatedly until you've constructed the Web page, and return htmltext.
As a first example, we write a CGI script that makes a Web page consisting basically of nothing but the current time. We don't need to refer to any of the parameters in adrParams, so our script might look like the following.
To test the above script, ask your browser for http://yourServer.com/theTime.fcgi, substituting for yourServer.com the IP number or name of the computer where the Web server and Frontier are running. On Mac OS, if your computers don't have assigned IP numbers, you can fake a miniature Internet using two computers networked with AppleTalk: use the TCP/IP control panel to make both computers use TCP/IP networking and give them artificial IP numbers, such as 255.255.255.254 and 255.255.255.255. Open Transport lets you switch conveniently between a fake configuration like this and your normal configuration.
A brilliant feature of the webserver suite is its robust handling of errors. Errors do not cause execution to stop or put up an error dialog - it could be a serious problem if they did, since your server computer might be remote and unattended. Instead, errors are trapped and logged in a file, and also are reported back to the browser in an HTML page, which displays the error message that would have appeared in Frontier. This means that calling scriptError() in a CGI script is a good way to return a simple error message to the browser.
You can customize the HTML framework of the error page by editing user.webserver.errorPage ; you should probably leave the <!--#errorMessage--> tag alone, because this is what causes the text of the error message to be inserted into the page. (This expression is a "macro"; macros are explained in a moment.) Similarly, you can customize a page at user.webserver.fileNotFoundPage which is displayed if the Web server asks Frontier for a "document" that doesn't exist as an entry in webserverScripts. You can also increase the error information in the log file by setting webserver.preferences.verbose-Logging to true.
Errors often depend on what was in adrParams, which is a local variable and therefore has gone out of existence by the time you learn of the error. A useful debugging script appears at webserverScripts.samples.tellParams ; it returns to the browser a Web page showing all the entries in the adrParams table. To access it, have your browser request samples.tellParams.fcgi; or, alternatively, have the first statement of your CGI script simply be this:
return webserverScripts.samples.tellParams(adrParams)
(Later, we illustrate a trick which lets your CGI script either make this call, for debugging purposes, or perform its normal function.) Also, turning on debugging in the WebServer menu causes the adrParams table to be saved in the scratchpad table each time the Web server calls Frontier.
Example 40-1 works, but it represents an inelegant and impractical programming model. One will scarcely be motivated to create good HTML through a series of clumsy add() calls. A far more pleasant and flexible approach is to store the HTML as a wptext; an eligible place for it is user.webserver.utilities , which is open for customized storage.
But how will a call to string.timeString() take place from the middle of a wptext? The solution is to use a macro. A macro is an HTML comment, of the form <!--# xxx-->, where xxx refers to a database entry: if xxx names a string or wptext, its value will be substituted for the macro; if xxx is a verb call, its result will be substituted for the macro.
Here is the syntax for the verb that performs macro substitution:
webserver.utilities.processMacros (string, addrTable)
where addrTable is optional. This verb returns string, except that any macros within string are replaced by their substitute. Resolution of object references in a macro is by means of a nest of withs; Frontier looks first in the table pointed to by addrTable if present, then in webserver.macros, then in the database as a whole (we will illustrate this in a moment).
So, modifying Example 40-1, we could move the HTML to a wptext at user.webserver.utilities.timeHTML, as demonstrated in Example 40-2.
<html><head> |
<title>What Time Is It</title> |
</head> |
<body> |
<p>The time is:</p> |
<center> |
<h1><!--#string.timeString( )--></h1> |
</center></body></html> |
Then, webserverScripts.theTime() could look like Example 40-3.
This method presents the prospect of easily generating powerful, interesting Web pages through a CGI script. The bulk of our HTML text is now a wptext, so it can be developed with any convenient tool and copied into the database, or the wptext object itself can be edited externally with BBEdit or PageSpinner (Chapter 35, External Editors).
An HTML form requires a CGI application that will process and respond to the form when its Submit button is pressed; we now illustrate how Frontier's CGI framework can deal with a form.
Here is the HTML for a simple form; the idea is that the user registers by giving a name and an email address:
<title>Registration Form</title>
<form action="register.fcgi" method=post>
<p><input type=text size=40 name=name value=""></p>
<p><input type=text size=40 name=email value=""></p>
<p><input type=submit value="Register">
<input type=reset value="Reset Form"></p>
Notice the use of the post method; this is important because it causes Frontier to parse the form input items for you.
The following is a CGI script that responds when the user presses the Register button in the form. Because the post method was used, the form input items arrive as a subtable of the table pointed to by adrParams, called argTable.
We first check to see that both fields were filled out; if not, we cause an error message to be returned. If all is well, we append the data to a file; because each CGI request generates a new thread, a semaphore is used to make sure that two simultaneous instances of this script don't clash over access to the file. We also include a test for a secret code as the email value; this code causes the tellParams() page to be returned, for debugging purposes, instead of performing our script's normal functions.
Finally, we return a response. In this case, it's a Web page acknowledging the registrations; once again, the page is a wptext in user.webserver.utilities . The wptext is passed through a call to processMacros(), and this time the call includes a second parameter, namely @argTable; this enables the macros in the wptext to access the name-value pairs in argTable, as follows.
<html><head> |
<title>Registration Complete</title> |
</head><body> |
<p>You have been registered as |
<!--#name-->, email <!--#email-->.</p> |
</body></html> |
An important trick frequently used in form submissions (though not in this example) is to have one or more hidden type input items in the form. This allows information which the user doesn't see in the browser to be communicated from the Web page to the CGI script in argTable entries. For example, you might have several forms, all of which are submitted to a single CGI script: the script knows which form it is receiving by a hidden input whose value identifies the form.6
A CGI script, instead of returning HTML text itself, can ask the Web server to serve up a different document (e.g., another .fcgi "document," a document on disk, or a URL elsewhere on the Internet) as its response to the browser request. This is called redirection, and is done by returning the result of a call to webserver.httpHeader() with "302 FOUND" as the first parameter and the URL of the desired page as the second. For example, to return the text of a file on disk after registering the user in Example 40-4, one might put this in place of the last two lines:
return webserver.httpHeader("302 FOUND", "regDone.html")
This example uses a relative (partial) URL, but of course you can use a full URL if needed.
The entry in webserverScripts requested by the browser does not have to be a script. For one thing, it can be a binary, in which case it is simply returned, with its internal datatype used to determine the MIME type. A table at user.webserver.mimeTypes is consulted for correspondences between datatypes and MIME types; you are free to augment this table. Thus, for example, a PDF or a QuickTime movie could be served up from within the database.
Another possibility is that the entry might be a string object, a wptext object, or an outline object. In these cases, the object is coerced to a string, passed through processMacros(), and returned, as an HTML document if it contains <html>, as plain text if it does not. The call to processMacros() includes adrParams as its second parameter, so the entries in the parameter table are available to any macros within the object.
Thus, since Example 40-3 performs no actions of its own - it just passes user.webserver.utilities.timeHTML through processMacros() and returns the result - we can skip it altogether; instead, we can move the wptext of Example 40-2 to the webserverScripts table and name it theTime, and it will work all by itself !
We can use the same technique, though, even if a CGI does perform actions. In Example 40-4, the webserverScripts object was a script, which performed some actions and then called processMacros() to return a wptext. But it might have worked the other way round: the webserverScripts object might be a wptext, containing a macro which calls a script to perform some actions. This arrangement is illustrated in the next example.
When a CGI document request is submitted by a browser to a Web server, two kinds of ancillary argument can be passed along with it, by using this format for the URL:
myCGI.fcgi$pathArgs?searchArgs
The $ and ? function as delimiters telling the server that what follows, up to the other delimiter or the end of the URL, are path arguments or search arguments, respectively. Both types of argument are optional, but their order is fixed. With the Frontier CGI framework, such arguments arrive as adrParams^.pathArgs and adrParams^.httpSearchArgs, respectively.
To illustrate one way of taking advantage of this feature, we follow a suggestion given in various tutorial examples.7 Imagine that we have a FileMaker Pro database of first names, last names, and ages. The user fills out a form to define a search of this database; in a CGI script, we perform the search, and return an HTML page listing all the matching records. We would like this list to consist of live links, so that the user can click on one to see that individual record in full.
Here's the trick. When we generate the HTML for the list of matching records, we include in the <a> tags that make them live links a search argument which is the ID of the corresponding record. So, when the user clicks on a link, a CGI script examines httpSearchArgs, asks FileMaker for the record with that ID, and returns the information.
Here is the HTML for a search form that the user fills out in the browser:
<html><head><title>Search</title></head><body>
<form action="fm.showMatching.fcgi" method=post>
<p><input name="firstname" type=text size=40></p>
<p><input name="lastname" type=text size=40></p>
<p><input name="age" type=text size=40></p>
<p><input type=submit value="Search">
<input type=reset value="Reset Form"></p>
When the user clicks the Search button in this form, the CGI framework looks for webserverScripts.fm.showMatching and finds it is a wptext (as follows).
The real action is performed by user.webserver.utilities.showMatch-ing() . Recall that when the object in webserverScripts is a wptext, its macros have access to the entries in the parameter table; one of these is argTable, which contains the input from the form, so the macro passes along argTable's address in the verb call. showMatching() consults FileMaker Pro and generates a series of live links representing the records that match the user's request.
The comment at the end shows the sort of HTML that is returned; each link asks for the same .fcgi "document," but appends a different search argument, which is the ID of the record. These links are inserted into the wptext of Example 40-6, which in turn is returned to the browser. If the user then clicks on a link, the CGI framework encounters the following wptext.
In the call to user.webserver.utilities.showThisRec() , we pass the search argument, which tells showThisRec() which full record to retrieve.
When the get method is used and not the post method (because a form is not being submitted), there is no argTable parameter: the CGI script has to parse httpSearchArgs itself. If httpSearchArgs is in the standard form of name=value pairs, string.parseHTTPargs() can parse it (see Chapter 14, Strings and Chars); the most convenient method is to call webserver.parseArgs(), which passes its first parameter to string.parseHTTPargs() for parsing and then builds the result into a table at the address which is its second parameter, thus returning the exact equivalent of an argTable.
CGIs can be dangerous, and Frontier CGIs, although they do take some precautions, are no exception. Frontier has great power over your computer, and a CGI can cause it to exercise that power. To give an extreme example, if you were to install a script in webserverScripts containing this line:
evaluate( string.parseHttpArgs (adrParams^.httpSearchArgs ))
then someone could construct a Web page containing a link which, when clicked, would execute on your server computer absolutely any UserTalk script they desired. The possible results are positively unthinkable.
The key to security-mindedness is to assume that someone will figure out how your CGI scripts work. Do not imagine that you can keep the scripts secret; on the contrary, pretend that they are published for the world to see. Don't put into a CGI script anything which could be dangerous if misused.
Even if your CGI scripts contain no security holes, you should assume that if they perform interesting or useful functions of universal application, someone will attempt to "hijack" them. For example, a Frontier user once wrote a Web form containing an input item which told the CGI script what page to show next; the input item was called newPage, and the CGI script ended with a redirection to whatever URL newPage contained:
return (webserver.httpHeader ("302 FOUND", adrParams^.argTable.newPage))
As a result, other people who could not do CGIs on their own server machines put up Web pages of their own which implemented the same functionality by way of the Frontier user's server! The server, and Frontier, responded to these "foreign" pages just as they did to the Frontier user's own pages. No harm was done, but Frontier was kept very busy handling requests from a lot of other people's Web sites.8 The solution in a case like this is for your script to check adrParams^.referer to make sure that the request is coming from one of your own pages.
The UserLand folks have distilled the wisdom of years into some tips for keeping a Macintosh healthy and quick when it runs unattended with a Web server and Frontier. I've very little experience with this myself, so the following just summarizes advice from a UserLand Web page.9
Put an alias to Frontier, and nothing else, in the Startup Items folder.10 Have a Frontier startup script launch any other applications (such as the Web server) or open any documents (such as FileMaker databases), spacing out the timing of the commands with clock.waitseconds() or some similar device. Have an agent in Frontier maintain the Web server as the frontmost application.11 Don't open any unnecessary applications, and certainly not a Web browser, on that machine; avoid other client applications, such as email or FTP, if you can (NetEvents is fine). Turn off file sharing if possible. Don't edit live on the machine; edit elsewhere. Close all windows: Finder windows, the Web server's status window, everything; you could even hide the Frontier Main Window.
Make sure you're using good system software. Have the latest version of AppleScript. Have the latest version of QuickTime, as it includes important Thread Manager components. But don't use QuickTime MPEG (with the new Thread Library), QuickTime Conferencing, or QuickTime TV. Run Autoboot (restarts the computer in case of a crash) and OkeyDokey (clicks OK buttons in modal dialogs).12 Remove all unneeded system software.
Assign Frontier and the Web server plenty of memory. Have plenty of real RAM so you can do so; don't use RAM Doubler or similar. Avoid unnecessary plug-ins for the Web server, but you can use an image map plug-in (for older browsers that don't support client-side image maps). Turn off DNS lookup on the Web server.
Don't forget to back up the root now and then. And the same for the hard disk as a whole.
If you are not using the script-based trap, it may be unwise to save the database while a CGI thread is executing; a crash can result.13 If you find this to be the case, install as an agent the agent script at webServer.data.agentScript() . Notice the values in user.webServer on which it depends. In recent versions of the CGI framework, installation of the agent is taken care of through the menu interface (the Auto-Save submenu of the Webserver menu).
The material we have covered will suffice for the vast majority of CGI needs. Various readme wptexts are scattered about the webserver suite, giving further information about its use.
Earlier, we had the Web server define an action and a suffix mapping so that the suffix .FCGI would correspond to an action FRONTIER, which is passed on to frontier.acgi, an alias of Frontier. It is possible to define other actions corresponding to other suffixes, and pass these to frontier.acgi as well. In this case, Frontier looks for the requested "document" not inside suites.webserverScripts, but in user.webserver.actions. A script there should match the name of the action.
Your Web server may also come with certain built-in actions that don't depend upon the name of the requested document at all. For example, you can have Frontier preprocess every file that the Web server serves up, by configuring its built-in PREPROCESSOR action to be sent to Frontier.
The CGI framework includes a macro action which permits files served from disk to be handed to processMacros(). This, however, is largely superseded by the cgiMacros suite, which defines new macro commands that can be given from .fcgi files served from disk.14
Frontier includes a powerful tool for generating Web pages; this is the subject of the next chapter. A CGI script can make a Web page by means of the Web page generation tool, taking advantage of its various abilities: to generate HTML from an outline, to apply a header and footer that fit the look of the rest of the Web site, and so forth.
1. This might be, depending on the server, in the same folder as the server, or in the default folder of the Web site.
2. Unchecking Use Script-based Trap, so that the UCMD is used, restricts the flexibility of your CGI scripts and may cause some occasional bugs; still, in certain unusual situations it may be necessary to do so.
3. I'm simplifying here; as we shall see in a moment, the object in suites.webserverScripts doesn't actually have to be a script.
4. The computer with the Web server and Frontier running does not have to be your main Web server. Indeed, your main Web server can be an NT or UNIX computer, provided it can see your Frontier computer's Web server and route CGI requests to it; this lets you take advantage of Frontier for handling CGIs and NT or UNIX for handling Web traffic. One reason for keeping your CGI computer separate from your Web server computer is so that one can crash without taking down the other.
5. To learn more about the information passed to a CGI application in the Apple event sent by WebStar and WebStar-compatible servers, see http://www.biap.com/datapig/mrwheat/appleevents.html.
6. If you use this trick, bear in mind that certain characters cannot appear as the value of an input item. You can get around this by encoding the value with string.urlEncode(); the value is automatically decoded with string.urlDecode() before the CGI script gets hold of it.
7. Such as that by Preston Holmes, at http://siolibrary.ucsd.edu/preston/scripting/FilemakerDemo.html, or that by Brent Simmons, ftp://ranchero.com//software/filemaker/writingFilemakerCgis.sit.hqx.
10. Exception: if you're using Open Transport, it can be slow when your Web server talks to a slow modem client. The solution is an unsupported application, OTTCPSlowLinkTuneup2. Get it from http://www.macintouch.com/otslow.sit.hqx and drop it in your Startup Items folder.
11. My personal experiments suggest that Frontier CGIs run a lot faster if Frontier is frontmost, but these were not under real-world conditions, so perhaps the results are misleading.
12. As of this writing, the latest versions were http://hyperarchive.lcs.mit.edu/HyperArchive/Archive/gui/okey-dokey-pro-203.hqx and http://hyperarchive.lcs.mit.edu/HyperArchive/Archive/cfg/auto-boot-15.hqx.
TOP | UP: Applied Frontier | NEXT: Web Site Management |