Satimage Previous | Next
Serving cgi and xmlrpc requests
Home Documentation Smile Server Serving cgi and xmlrpc requests  
The basics
Satimage-software provides a special cgi program, able to handle both cgi calls sent from an Internet browser by a html form, and xmlrpc requests. The cgi program is provided with the Smile Server examples in the /Applications/Smile/Smile Server/ folder.
We assume that your machine is a Web server (you have activated Personal Web Sharing). To have Smile handle the requests that the cgi receives, you first open a server port in Smile. You configure the cgi to forward the request to Smile.
The reply that Smile returns will then be sent back by the cgi to the Apache server in your machine, which will forward it to the client Internet browser, or to the caller of the xmlrpc request.

All the information about the commands described in this page is in the Smile over IP Suite in Smile's dictionary.

Configuration of the cgi
The cgi program (cgismile) comes with a folder named cgismiletemp. You can rename the cgi program (for instance, to mycgi), provided you rename the folder accordingly (to mycgitemp). You have to rename the cgi if you want to install several of them.
Copy the cgi program and its attached folder into /Library/WebServer/CGI-Executables. Then set the file permission of the attached folder to "Read & Write" for all users (or at least for the "www" user).
The folder contains a file named config.plist. Edit the plist (double-click it to have Smile open it). Enter the IP address of the machine where Smile is running (this may be any machine on the local network or on the Internet) as the remote address. You have to provide a port number for Smile server: use any positive number up to 65,535, but numbers smaller than 1024 are not recommended. In any instance, do not use a port number which is already open on your machine. Enter this port number as the remote port. Save config.plist.
Open the port

To open a port on the server Smile's machine, call install server port. Quitting the server Smile will close any port that it may have opened. You can also close a port with the remove server port command.

Open the port with the same number as declared in config.plist.

install server port 20000 subroutine "handle_cgi_request"
The name of the handler ("handle_cgi_request" in the example) should be the name of a handler available in the context: a handler which was compiled in an AppleScript Terminal window, or a handler belonging to a library which was included with add library, or a handler belonging to a library stored in Smile's Context additions folder (see Using AppleScript libraries if you are not familiar with AppleScript libraries in Smile). This handler will be called on every request.
Handling a request by script: request from a HTML form

Let us consider first the situation when the cgi was called from an Internet browser in a HTML form. The handler specified for the server port will receive two parameters: the inputs that the user entered in the form, as a Unicode string formatted as a plist, and the ID of the request. (You must use that ID if you perform an asynchronous handling of the request.)

Apache expects an html page, prefixed with a valid HTTP header. Your handler will return this page as text.
There are other options, in particular if you have to handle download requests: this is beyond the scope of this page, and is documented in many places - not at Satimage-software.

on handle_cgi_request(theplist [, theID])
    [...]
    set lf to ASCII character of 10
    set theheader to "Content-type: text/html" & lf & lf    
    set thehtml to "
<html>
    <head>
        [...]
    </head>
    <body>
        [...]
    </body>
</html>"
    return theheader & thehtml
end handle_cgi_request
The input parameter, theplist, is a Unicode string defining a plist that is a dictionary containing the following keys:
  • CGI-env: itself a dictionary, contains the cgi environment variables (client IP address, etc.)
  • HTTP_COOKIE: itself a dictionary, contains the cookies list (if applicable.)
  • the data entered by the user, as pairs (key, value) where the key is the input's name and the value is the quantity entered by the user.
    If the input is a file, the value is itself a dictionary with two keys: filename, the name of the original client's file, and tfilename, the local POSIX path of the file. When the cgi receives the reply, it will delete the file tfilename: it is your responsibility to process the file or to copy it in the meanwhile.
For instance, the following html input element:
<input name="user_age" type="text" value="26"/>
results in the following entry in the plist:
<key>user_age</key>
<string>26</string>
Note that the values are strings: coerce them if necessary.
set p to PlistOpen theplist
set theAge to (PlistGet (PlistChild p key "user_age")) as integer
or (more legible if the data are small):
set p to PlistOpen theplist
set r to PlistGet p
set theAge to r's user_age

To process the plist efficiently, use the commands in XMLLib.osax, and do not forget to apply PlistClose to the plists you may have created: the handler will not dispose of them when it terminates.

Handling a request by script: from a XMLRPC request
If the cgi was called by an XMLRPC request, the plist contains the XML input, a Unicode string, as its xml_rpc key. You may want to use the following handler as a model to handle an xmlrpc request.
on handle_cgi_request(theplist, theID)
    set p to PlistOpen theplist
    set s to PlistGet (PlistChild p key "xml_rpc")
    PlistClose p
    set theinputs to XMLOpen s
    set thehandler to XMLGetText (item 1 of (XMLXPath theinputs with "//methodName"))
    set params to XMLGetText ((XMLXPath theinputs with "//param/value/*")) -- a list
    XMLClose theinputs
    callhandler params subroutine thehandler -- calling a handler by name
    set lf to ASCII character of 10
    set theheader to "Content-type: text/xml" & lf & lf    
    set thexml to "
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><string>reply here</string></value>
</param>
</params>
</methodResponse>"
    return theheader & thexml
end handle_cgi_request
There are various ways of sending an xmlrpc request. From AppleScript, you can use the following syntax, but do not send the call from the server Smile itself, as the call is blocking.
tell application "http://127.0.0.1/cgi-bin/cgismile"
    set theresult to call xmlrpc {method name:"msg", parameters:{"hello world"}}
end tell
Specifying non-global subroutines
In the examples above, the handler called on the server side is available in the server Smile's global context. There are cases where you do not want to clog up the global context, and you prefer to call a non-global routine, namely a routine belonging to a script. The routine has to belong to the script of an object: an object's object script (script of theObject) or an object's class script (class script of theObject). (You cannot pass a script object.) Pass a record as the subroutine parameter (instead of a string) and use the following syntax.
install server port 20000 subroutine {name:"handle_client_request", script:script of window "answering window" as integer}
Filtering ports
For security reasons or else, you may want to allow only a specific set of machines to call your server Smile. To that effect, provide a list of IP numbers (as strings) as the (optional) for parameter of install server port.
install server port 20000 subroutine "handle_client_request" for {"82.231.243.102", "17.254.3.183"}
Asynchronous request handling
There are cases when the server Smile may not return the reply immediately. An example is when the task is long, and you do not want to block the server Smile: you would have it send the (long) job to perform to another Smile, in order that it be able to process other requests while that one gets handled.
To that effect, the handler on the server side must send error number -1718, which specifies to the client that the content of the reply has not arrived yet, it will arrive later. This will unblock the server Smile.
When it is time to send the reply, the server Smile must then call resume server task, which will provide the client Smile with the reply. In order to specify what request it is replying to, resume server task passes the request's ID. The server Smile received the request's ID as the second parameter of the handler called by the request.
The example below sketches a chat session.
on handle_cgi_request(theRequestData, theRequestID)
    set my requestData to theRequestData -- store the request into a persistent variable
    set my replyID to theRequestID -- store the ID into a persistent variable
    error number -1718 -- special error code, means "reply is deferred"
end handle_client_request
To reply to the request, the server Smile have to call resume server task:
resume server task (get my replyID) with data theData

In a real program you would not use a global (my requestData and my replyID) to store the request's ID, particularly if you intend to forward (in asynchronous mode) a long request to another server Smile.

Port accessibility issues
Whether a port may or not be called from the outside depends on your configuration. If both machines are on the same local network, you just have to make sure that the Mac OS X' firewall enables it. Over the Internet, you may have to setup your configuration specifically. For instance if you use an Airport station, you have to set the port mappings in AirPort Admin Utility in order to redirect the incoming requests to a specific machine.
Copyright ©2009 Paris, Satimage