Satimage Previous
Communicating between Smile's
Home Documentation Smile Server Communicating between Smile's  
The basics

When you establish communication between two Smile's, one Smile is the server, the other Smile is the client. The server opens a port on the machine where it belongs. The client can be located anywhere: on the same machine, on another machine in the local network, or on a remote machine connected to the Internet. The client Smile sends data to the server's port and may (or not) wait for a reply to its request.

The data
The data sent by one Smile to the other may be any AppleScript quantity: typically, a string, a list, a record. Though, it is a good idea not to send data which would make sense only on one end and not on the other: typically, do not send a quantity of the alias type.
Instructions
Basically, all the information you need is in the Smile over IP Suite in Smile's dictionary.
On the server side: to open a port on the server Smile's machine, call install server port. You have to provide a port number: 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.
install server port 20000 subroutine "handle_client_request"
The name of the handler ("handle_client_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).
In the example below, it is assumed that the data sent by the client Smile is an AppleScript list and that the server will count the items of the list.
on handle_client_request(aList)
    return (count aList)
end handle_client_request
On the client side: to send a request from the client Smile, call call remote port. Provide the port number, the IP number of the server Smile's machine, the data to send, and the empty string as the subroutine parameter.
call remote port 20000 at "10.0.1.2" with data {1, 2, "3"} subroutine ""
    --> 3
The call is synchronous: the call remote port command will return (and subsequent script lines will resume) only when the server sends the reply. (Obviously, do not issue a synchronous call if you are using the same copy of Smile as the client and the server.)
Asynchronous calls
In the example above, the server's task is short, and the reply is quasi-instantaneous. There are situations where you want the script to resume without waiting for the reply - for instance, you may want to launch a long task, yet not blocking the client Smile. To that effect, specify a non-empty string as the subroutine parameter.
call remote port 20000 at "10.0.1.2" with data {1, 2, "3"} subroutine "process_result"
The call is asynchronous: call remote port returns immediately, and subsequent script lines will execute without a delay. Whenever the server sends the reply, the returned data are sent to the specified handler ("process_result" in the example). That handler should be available in the context of the client Smile.
on process_result(x)
    quietmsg(x)
end process_result
Here, the client Smile will write the result (3) in the Console when the server sends the reply.
If you just want to have the server Smile perform some operation but you do not need a reply, do not specify any subroutine parameter: the call will return immediately, and the client Smile will never receive any reply.
call remote port 20000 at "10.0.1.2" with data {1, 2, "3"}
Closing the port
Quitting the server Smile will close any port that it may have opened. Though, it is better to explicitly close the ports you have opened with remove server port.
remove server port 20000
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 a chat session, where the server Smile's reply consists in the answer that the user located on the server side sends to the message sent by the user on the client side. Another 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 yet 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_client_request(theMessage, theRequestID)
    set end of text of window "SmileChat" to return & theMessage
    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
The client's call could be the following.
call remote port 20000 at "10.0.1.2" with data "hello, this is fred" subroutine "process_result"
When the client Smile runs the line above, the message "hello, this is fred" will print in the "SmileChat" window of the server Smile, and the server Smile remains responsive. When the user on the server side is ready to reply to the message, they have to call resume server task:
resume server task (get my replyID) with data "hi fred, how are you?"

Assuming the "process_result" handler is like above (it prints the reply to the Console), the reply from the user located on the server side will print in the Console of the client Smile.

In a real program you would not use a global (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.
Publishing ports
If both machines are on the same local network, you may use Bonjour (Apple's ZeroConf protocol) to publish the ports that you open (use register / unregister server port), so that other machines on the local network be able to know what server ports are available (using list registered ports).
Copyright ©2009 Paris, Satimage