DekGenius.com
[ Team LiB ] Previous Section Next Section

24.4 CGI Application

A CGI application (for common gateway interface, if you must know) is a process that supplements a web server. When a request arrives for a page, instead of producing the page as a copy of a file on disk, the web server can turn to a CGI application and ask it for the page; the CGI application is expected to compose the entire HTML of the page, including headers, and hand it back to the web server, which passes it on to the client that made the request.

On Macintosh, the communication between a web server and a CGI application has conventionally been performed through Apple events. In particular, an Apple event usually known (for historical reasons) as the WebSTAR event is sent by the web server to the CGI application, describing the page request. The CGI application hands back the page as the reply to this Apple event (see http://www.4d.com/products/wsdev/internetspecs.html).

This means that an applet can be used as a CGI application; and such, indeed, is the traditional Mac OS approach. If you're using WebSTAR or some other web server that implements CGIs in this manner, you can use it directly with an applet. However, the web server that comes with Mac OS X, Apache, doesn't work this way. Apache is a Unix web server, and Unix doesn't have Apple events. In Unix, environment variables, along with stdin and stdout, are used as the communication medium between the server and the CGI process.

Therefore, in order to use an AppleScript applet as a CGI application with Apache, you need some intermediary application that swings both ways, as it were. On the one hand, this intermediary application must behave as an Apache-style CGI process, so that Apache has someone to talk to. On the other hand, this intermediary application must know how to translate a CGI request from Apache into an Apple event, send this Apple event to the correct applet, and receive the result. It must then translate the result into the form Apache expects, and pass it on to Apache.

Such an intermediary is James Sentman's acgi dispatcher utility. (See http://www.sentman.com/acgi.) Here, then, is a description of how to write and implement a basic CGI applet in AppleScript using acgi dispatcher.

Let's start with the applet. We'll write an "echo" CGI, whose job is to return a web page simply describing the original request. This is a valuable thing to have on hand because it can be used for testing and debugging, and it exemplifies the two basic tasks of a CGI applet, namely to receive the Apple event and to respond by constructing and returning a web page.

The terminology for the Apple event that's going to be arriving is defined in the StandardAdditions scripting addition, as the handle CGI request command. In defining our handle CGI request handler we can take advantage of this terminology, but there are some slight hiccups. The terminology speaks of an action parameter, but acgi dispatcher fails to provide this, so we must omit it. And acgi dispatcher includes one parameter that isn't mentioned in the terminology, and therefore has to be included by means of a raw four-letter code. (This extra parameter is a list of URL-decoded form elements, saving your applet the tedious job of parsing the form information. The example script ignores it.)

Apart from this the code is straightforward; here it is:

property crlf : "\r\n"
property http_header : "MIME-Version: 1.0" & crlf & ¬
        "Content-type: text/html" & crlf & crlf
property s : ""

on makeLine(whatName, whatValue)
        return "<p><b>" & whatName & ":</b> " & whatValue & "</p>" & return
end makeLine
on addLine(whatName, whatValue)
        set s to s & makeLine(whatName, whatValue)
end addLine

on handle CGI request path_args ¬
        from virtual host virtual_host ¬
                searching for http_search_args ¬
                with posted data post_args ¬
                using access method method ¬
                from address client_address ¬
                from user username ¬
                using password pword ¬
                with user info from_user ¬
                from server server_name ¬
                via port server_port ¬
                executing by script_name ¬
                of content type content_type ¬
                referred by referer ¬
                from browser user_agent ¬
                of action type action_path ¬
                from client IP address client_ip ¬
                with full request full_request ¬
                with connection ID connection_id ¬
                given «class TraL»:form_elements
        -- using action action -- not implemented by acgi dispatcher
        set s to http_header
        set s to s & ¬
                "<html><head><title>Echo Page</title></head>" & return
        set s to s & "<body><h1>Echo Page</h1>" & return
        addLine("virtual_host", POSIX path of virtual_host)
        addLine("path_args", path_args)
        addLine("http_search_args", http_search_args)
        addLine("post_args", post_args)
        addLine("method", method)
        addLine("client_address", client_address)
        addLine("username", username)
        addLine("password", pword)
        addLine("from_user", from_user)
        addLine("server_name", server_name)
        addLine("server_port", server_port)
        addLine("script_name", script_name)
        addLine("content_type", content_type)
        addLine("referer", referer)
        addLine("user_agent", user_agent)
        addLine("action_path", action_path)
        addLine("client_ip", client_ip)
        addLine("full_request", "</p><pre>" & full_request & "</pre><p>")
        addLine("connection_ID", connection_id)
        set s to s & "<hr><i>" & (current date) & "</i>"
        set s to s & "</body></html>"
        return s
end handle CGI request

We start by defining the header that will precede our HTML. Then comes a pair of utility handlers that will make the code for generating each line of our HTML a bit less tedious; our plan for generating the HTML is to append line after line of this format:

<p><b>param_name:</b>param_value</p>

and these utilities make the job a bit more elegant. Finally we have the actual handler for the Apple event that will come from acgi dispatcher. In your own experiments, be sure to copy the parameters in the first line of the handler definition exactly as shown here, or the whole thing won't work! In the content of the handler you can do anything you like, but the result should be the header followed by some legal HTML.

Now let's talk about how to set up Apache to use this script as a CGI process. The directory where everything needs to go is /Library/WebServer/CGI-Executables. Save the script as a Stay Open applet into that directory, calling it echo.acgi. Start up echo.acgi. Now open the acgi dispatcher folder and move the dispatcher application into that directory as well. Start up dispatcher and provide your admin password when requested. Finally, go into the Sharing pane of System Preferences and turn on Personal Web Sharing. That's it! You're ready to test. Open a browser and ask for http://localhost just to make sure Apache is serving. If that works, then cross your fingers and ask for http://localhost/cgi-bin/echo.acgi. You should see a web page displaying your request; it will contain information such as your IP number and what browser you're using. At the bottom it will show the current date and time. You've just written your first AppleScript CGI.

A question that arises with CGI applets (or any applet, really) is what happens if a request arrives when the applet is already in the middle of running its handle CGI request handler in reaction to a pending request. AppleScript is not multithreaded, so execution of simultaneous requests must be taken in some definite order. In the latest version of AppleScript (1.9.2) and the System (Mac OS X 10.3), that order is FIFO—first in, first out. This means that requests are processed in the order in which they arrive; if a request arrives while another request is already being processed, the new request must simply wait its turn.

This is a big improvement over past systems, where the order was LIFO—last in, first out. Under LIFO ordering, if an Apple event arrives, all pending execution is put on hold until the execution triggered by this latest Apple event has finished. This means that if many requests arrive close to one another, it is theoretically possible for the first ones to be postponed a long time (so that, from the client's point of view, they time out).

    [ Team LiB ] Previous Section Next Section