26.3. CGI ApplicationA 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 simply fetching 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 sends it on as the reply to the client that made the request. Before Mac OS X, the communication between a web server and a CGI application was conventionally performed on Mac OS 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 AppleScript applet could be used as a CGI application; such, indeed, was the traditional approach. Terminology for the WebSTAR event is defined in the StandardAdditions scripting addition; it is the handle CGI request event. So in the past, you would write an applet implementing an event handler for handle CGI request, and point your web server at that applet as the CGI application for certain web page requests. With the coming of Mac OS X, however, all this has changed. Of course if you're still using WebSTAR or some other web server that implements CGIs in the traditional Mac OS manner, you can continue using it directly with an applet. However, you are more likely to be using the web server that comes with Mac OS X, namely Apache, which 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. If you really want to, you can still use an AppleScript applet as a CGI application with Apache, but in order to do so, 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 translate a CGI request from Apache into an Apple event and forward this Apple event to the appropriate applet; when the result comes back from the applet, it must then translate that result into the form Apache expects, and pass it back to Apache. So, where will you get this intermediary application? Mac OS X Server, I believe, comes with one; but ordinary Mac OS X does not. Instead, you can use James Sentman's acgi dispatcher utility. Here is a description of how to write and implement a basic CGI applet in AppleScript using acgi dispatcher. (What's documented here is version 2.5, the latest version at the time this was written; these instructions will not work for earlier versions.) For purposes of the example, we'll write an "echo" CGI, whose job is to return a web page simply describing the original request. This is always a valuable thing to have on hand when you're doing CGI, because it can be used for testing and debugging, and in any case 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 code is straightforward. The main requirements are that we implement the handle CGI request event as defined in StandardAdditions, and that we return a valid page of HTML preceded by some minimal HTTP headers. (acgi dispatcher provides an extra parameter, a list of URL-decoded form elements, saving your applet the tedious job of parsing the form information; you can capture this by adding an extra parameter, given «class TraL», to the parameters, but this example doesn't.) 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 using action action 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) addLine("action", action) 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. As long as this returns a correct HTTP header followed by some reasonably legal HTML, it should work. Save the script as a Stay Open applet. (Chapter 27 will explain the implications of this designation.) Let's name it echo.acgi. (After saving the applet, you may have to use the Finder's Get Info dialog to remove the .app suffix appended by your script editor application. It is crucial that the file extension should be .acgi, not .app.) Now let's set up Apache to use acgi dispatcher as a CGI process. This is easy because acgi dispatcher automates the configuration for you. Start up acgi dispatcher and provide your admin password when requested, and the configuration will be performed. Go into the Sharing pane of System Preferences and confirm that Personal Web Sharing is turned on (turn it on if not). Confirm that Apache is serving by opening a browser and asking for the default web page. For example, from the same machine you would ask for http://localhost; from another machine on the same network you would use the machine's Bonjour name (so, for example, my test machine is called tangerineScream.local, so I would ask for http://tangerineScream.local). Now move echo.acgi into /Library/WebServer/CGI-Executables, and double-click it to start it up. Take a deep breath and, in your browser, ask for it by appending /cgi-bin/echo.acgi to the working URL of your Apache server. For example, I would ask for http://localhost/cgi-bin/echo.acgi in a browser on the same machine, or http://tangerineScream.local/cgi-bin/echo.acgi in a browser on a different machine. Presto! You should get a web page in response, containing such information such as your IP number and what browser you're using, and showing the current date and time at the bottom. That's the script in echo.acgi doing its thing! You've successfully written an AppleScript CGI script. 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 handling a pending request. Your web server and your CGI dispatcher may be multithreaded, but AppleScript is not, so Apple events that arrive during execution must be queued and then handled in some definite order. Starting with Panther (AppleScript 1.9.2), that order is FIFOfirst in, first out. This means that CGI 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 wait its turn and will be taken as soon as all currently pending requests are handled. (This is a big improvement over past systems, where the order was LIFOlast 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 used to mean that if many requests arrived close to one another, it was quite possible for the earliest ones to be edged out of the queue by newcomers; if this went on for long enough, then from the client's point of view, the web page request would time out, which was thoroughly unfair.) |