Socket, Process, and Stream Input and Output
SenseTalk provides a set of commands for reading and writing sockets, processes, and the standard input and output streams (i.e., stdin, stdout, stderr). Fundamentally, these forms can all be treated as streams of data. The read
and write
commands include many options to make it easy to read or write any type of data including numbers in many formats, text, and raw binary data. HTTP and XML-RPC messages are also supported directly to further simplify communication with many servers on the internet.
Although some details differ between the different types of streams, the basic process of working with all of them follows the same sequence: open
, read
and/or write
, close
. The main exception to this sequence is that the standard input, standard output, and standard error streams don't require opening or closing because they are considered to be open at all times.
The commands and functions that you use to work with sockets, processes, and stream data are similar to the commands and functions you can use for working with files and file system objects. Unlike a file connection, you cannot seek to a specified location on a socket, process, or stream because these connections are just an open communication channel between two processes. For information about working with files and file systems, see File and Folder Interaction.
The socket input and output commands (open socket
, close socket
, read from socket
, and write … to socket
) permit you to open a connection to a socket provided by another process and read and write data through that connection.
Similarly, the process input and output commands (open process
, close process
, read from process
, and write … to process
) allow you to launch an external process and communicate with it by writing to the standard input and reading from the standard output of that process.
The read
and write
commands can also be used to read from standard input and to write to the standard output or standard error streams (read from input
, write … to output
, write … to error
).
The openSockets
and openProcesses
functions return a list of all of the currently open sockets or processes, respectively.
The DefaultStringEncoding
global property controls the encoding format that is used when reading or writing text from a file or socket.
Open Socket
Command
Behavior: The open socket
command must be used to open a socket before anything can be read from or written to that socket using the read
or write
commands. When you are finished with a socket, it should be closed using the close socket
command.
Open socket
establishes a TCP socket connection to another process (program). The other process may be running on the same computer, or on some other computer on the network. It must already be running, and have registered a socket to which SenseTalk can connect. Once the connection is established, data may be transmitted in either direction in whatever manner both sides understand.
Syntax:
open socket socketIdentifier
The socketIdentifier must be of the form host:port where host is the name or IP address of the machine, and port is the port number on that machine of the socket to be connected to.
The socketIdentifier may optionally end with a pound sign #
(or a vertical bar |
) character followed by an arbitrary number or identifier string. This serves the purpose of allowing you to create multiple identifiers to establish more than one connection to the same host and port, and identify each connection uniquely – just use the appropriate socketIdentifier with the read
, write
, and close
commands to identify the correct connection.
If the socket connection cannot be established within the time specified by the readTimeout
global property, an exception will be thrown. Use the openSockets
function to get a list of all sockets which are currently open.
Example:
open socket remoteListener
Example:
open socket "192.168.1.4:22"
Example:
open socket "localhost:5900#2"
Open Process
Command
Behavior: Launches an external process and opens a connection through which the script may interact with that other process. If all that is needed is to run an external process and receive any output from that process when it completes, the shell
function provides a much simpler way to achieve that. The open process mechanism, on the other hand, provides much greater flexibility, allowing the script to conduct complex interactions with another process, or to start a lengthy operation without blocking the script and retrieve the results of that operation at a later point in the script.
Open process
launches another process (program) which may be (and most commonly is) a shell through which still other programs may be executed. Once the other process is launched, a connection is established and text may be transmitted in either direction – the script may write to the standard input and read from the standard output of the other process.
If the process cannot be launched, the result
function will be set to an exception (or the exception will be thrown, if the ThrowExceptionResults
global property is set to true). The openProcesses
function can be used to get a list of all processes which are currently open.
Syntax: open process processIdentifier {with {options} options}
The processIdentifier should be in the form processPath#identifier where processPath is the full path of the process to run. If processPath is omitted, a shell process will be launched (as specified by the shellCommand
global property). The #identifier portion is also optional – it merely serves as a way to make a processIdentifier unique, so that a script can open and interact with multiple processes at once that use the same processPath.
If options is used, it should be a property list that may include any of these properties:
parameters | a list of values to be passed as parameters to the process when it is launched |
folder or directory | the current directory where the process will be run |
environment | a property list specifying environment variables and their values |
Example:
open process preferredShell & "#myshell"
Example:
open process "/bin/sh" with options myOptions
Example:
open process "/usr/local/bin/mysql#2"
Close Socket
, Close Process
, Close All
Commands
Behavior: Closes a socket; or process; or all open files, sockets, or processes. The close all sockets
command closes all currently open sockets, regardless of which script or handler opened the socket. This behavior could be potentially problematic if sockets have been opened by other scripts, and are still in use. Use the openSockets()
function to get a list of all open sockets. The same applies to the close all processes
command.
The close socket
command closes a socket that was previously opened with the open socket
command. The socketIdentifier should be identical to the identifier used when opening the socket.
The close process
command closes a process that was previously opened with the open process
command. The processIdentifier should be identical to the identifier used when opening the process.
The close all sockets
and close all processes
commands can be used to close all of the currently open sockets or processes. SenseTalk automatically closes all open sockets and processes whenever it stops executing your scripts, but it is good practice to for your script to close them when it is done working with them.
Syntax:
close socket socketIdentifier
close process processIdentifier
close all sockets
close all processes
Example:
close all processes -- close all open processes
Example:
close socket "localhost:5900"
Example:
close process "#9"
Read from Socket
, Read from Process
, Read from Input
Commands
Behavior: Reads data (text or numbers) from an open socket or an open process, or from the standard input stream.
Use the read from ...
command to read data from a socket, process, or standard input. Data is read into the variable it
or into a destination container if specified (using an into
clause). When there is no more data to read, the destination container will be empty. Any time less data is read than was requested, the result
function will contain a value giving the reason (such as "time out").
The read
command can read a specified number of characters, words, items, lines, or bytes; can read until a specified delimiter is found; can read a list of one of several different types of numeric values; or can read an HTTP or XML-RPC message. Reading begins at the current position. The syntax of the read command is flexible, allowing the various options to be specified in any convenient order.
In order to read from a file, see the Read from File Command.
Syntax:
read Options
Source Options: One Required.
from socket socketIdentifier
from process processIdentifier
from [input | stdin]
Quantity Options: Optional. Only one can be used at a time.
until [{the} eof | {the} end {of {the} file} | terminator]
for quantity {dataType}
[a | an | quantity] dataType
{for} {a | an} http {message | request | response}
{for} {a | an} [xmlrpc | xml-rpc] {message | request | response}
Other Options: Optional. More than one can be used at a time.
at startPos
[ in | [timeout | time out] {after | in} ] timeoutDuration
into container
One of the three from options is required, to specify the source from which to read. All other options are optional, but only one of each type may be specified. If neither a for nor until option is given, a single byte is read. If into container isn't specified, the value will be read into the special it
variable.
You do not need to open the standard input stream – it is always open (you can refer to it as stdin
instead of input
if you prefer).
When reading from a socket, the socketIdentifier expression must yield the exact identifier used when a socket was previously opened with the open socket
command.
When reading from a process, the processIdentifier expression must yield the exact identifier used when a process was previously opened with the open process
command. The value that is read corresponds to the standard output from the process.
If into container is specified, the data that is read is put into the given container. If an into option is not specified, the data is read into the special it
variable.
If until terminator is specified, all of the characters from the starting position until the next occurrence of the specified character or string will be read. This is useful for reading one line at a time from the source (by using return
as the terminating character), or to read just until some other delimiting character (such as a tab
). The terminator can be more than one character in length, and will be returned as part of the value that was read. Specifying until eof or until end will read all the way to the end of the file, or to the end of input from a socket or stream. The standard input stream indicates it is at the end after a Control-D character is received. For sockets and processes, the until eof or until end option will wait until either some input is available, or the duration of the readTimeout
or in timeLimit (see below) has elapsed. This greatly simplifies reading when some input is expected.
If for quantity dataType is used, the number of characters or other data elements specified by quantity are read from the file. If dataType is a text chunk type (characters
, words
, items
, or lines
), text is read until the requested amount is available. The final delimiter (if any) is not included with the text that is read. If no dataType is given, bytes are assumed (and the word for
is required in this case).
The in timeoutDuration option gives the maximum time the read
command will wait for the requested data to become available. If a time is not specified, the value of the readTimeout
global property will be used instead. If the requested data is not read within the time specified by timeLimit or readTimeout, whatever has been read will be returned and the result
function will be set to indicate time out.
If you specify a numeric dataType instead of a text chunk type, the value stored into it
or container by the read will be a list of the data values that were read. The following numeric data types may be used:
DataType | Value |
---|---|
int1 or 8-bit integer | an 8-bit (or 1 byte) signed integer |
uint1 or unsigned 8-bit integer | an 8-bit (or 1 byte) unsigned integer |
int2 or 16-bit integer or short integer | a 16-bit (or 2 byte) signed integer |
uint2 or unsigned 16-bit integer | a 16-bit (or 2 byte) unsigned integer |
int4 or 32-bit integer or integer | a 32-bit (or 4 byte) signed integer |
uint4 or unsigned 32-bit integer | a 32-bit (or 4 byte) unsigned integer |
int8 or 64-bit integer | a 64-bit (or 8 byte) signed integer |
uint8 or unsigned 64-bit integer | a 64-bit (or 8 byte) unsigned integer |
real4 or 32-bit real or float | a 32-bit (single-precision) floating-point number |
real8 or 64-bit real or double | a 64-bit (double-precision) floating-point number |
Example:
read from input until return -- read text typed by user
Example:
read from process mysql until end -- read available text
Example:
read into numList from socket inStream for 3 unsigned integers
Example:
read 6 unsigned 8-bit integers from socket rfb into unitSales
Example:
read 10 chars from socket "192.168.1.4:22" in 15 seconds
Related:
Read from File Command
: Use this to read from a file.
Write to Socket
, Write to Process
, Write to Output
, Write to Error
Commands
Behavior: Writes data to a socket or process, or to the standard output or standard error stream. Data can be any valid SenseTalk expression. If as dataType is not specified, the value of the data expression is treated as a string of characters, which is written out to the specified socket or stream.
The socketIdentifier expression must yield the identifier of a socket that was previously opened with the open socket command.
The processIdentifier expression must yield the identifier of a process that was previously opened with the open process command. The data that is written will be sent to the standard input of the process.
If as dataType is specified, the data is converted to that binary format before being written. In this case, data can be a list of numeric values, which are all converted to the same data type. See the read
command for a list of the valid data types.
Syntax:
write data {as dataType} to socket socketIdentifier
write data {as dataType} to process processIdentifier
write data {as dataType} to [output | stdout | error | stderr]
Example:
write "ls -l" & return to process "#4"
Example:
write "Please enter your account id: " to output
Example:
write [2,3,5,9] as 8-bit integers to socket msock
OpenSockets
Function
Behavior: Returns a list of all sockets that are currently open as a result of the open socket
command.
Syntax:
openSockets()
the openSockets
Example:
repeat with each item of the openSockets
if it begins with "192.168.1.12:" then close socket it
end repeat
OpenProcesses
Function
Behavior: Returns a list of all processes that are currently open and available for interaction as a result of the open process
command.
Syntax:
openProcesses()
the openProcesses
Example:
repeat with each item of the openProcesses
if it begins with "/usr/bin/ssh" then
write "logout" & return to process it
end if
end repeat
Sending and Receiving HTTP Messages
When a socket is used for communication using the HTTP protocol, SenseTalk provides support for directly reading and writing HTTP messages. To read a message, use one of these commands (or a variation):
read an HTTP message from socket clientSock
read from socket myHTTPsock for an HTTP request
read an HTTP response into theReply from socket remoteService
The value read will be a property list with all of the information contained in the HTTP message that was read, including Method, Path, and Header properties, similar to this:
{Header:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8”,
"Accept-Encoding”:"gzip, deflate”,
"Accept-Language”:"en-us”,
Connection:"keep-alive”,
Host:"localhost:5991”,
"User-Agent”:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22”
},
HTTPMessageType:"Request”,
Method:"GET”,
Path:"/”,
Version:"HTTP/1.1”}
Similarly, an HTTP message can be sent across a socket using the write command. To write an HTTP message from a property list, use the HTTPMessage()
function or as http
operator to convert the property list to an HTTP message and use the write command to write that to the socket:
write HTTPMessage(status:200, body:htmlContents) to socket webClient
write replyMsg as http to socket customerSocket
HTTPMessage
Function
Behavior: Generates a message in HTTP format suitable for transmitting over a socket to an HTTP server or client.
Syntax:
HTTPMessage( messageProperties )
{the} HTTPMessage of messageProperties
messageProperties as http
The messageProperties should be a property list that can include these properties, depending on whether the message is an HTTP Request or an HTTP Response:
HTTP Request Message
- Method: GET or POST (if omitted, GET is assumed if no Body is supplied, and POST if a Body is supplied);
- Request: Path to the resource being requested on the server (required);
- Version: If not supplied, HTTP/1.1 is assumed;
- Header: If given, must be a property list of header keys and values;
- Body: The body of the message. The body might be text, or it might be a property list, in which case it is automatically encoded in key/value pairs.
HTTP Response Message
- Version: If not supplied, HTTP/1.1 is assumed;
- Status: A numeric status value to send. If not specified, defaults to 200;
- Reason: Text of the reason for the returned status; defaults to OK;
- Body: The content being sent. For a web server, this is typically the HTML code of a page. The body might be a property list, in which case it is automatically encoded in key/value pairs.
Example:
put HTTPMessage(status:200, body:"This is the contents of my HTTP message")
This example displays this message:
HTTP/1.1 200 OK
Content-length: 39This is the contents of my HTTP message
Sending and Receiving XML-RPC Messages
To send an XML-RPC request, the post xmlrpc
command is used:
set myRequest to {methodName:"system.listMethods"}
post xmlrpc myRequest to url serverURL
The message being posted must be a property list with these properties:
- MethodName: Required. The method name being called.
- Params: Optional. An array of parameter values. Specify "as boolean", "as string", "as date", etc., when needed to ensure individual values are encoded as the correct type for the method being called.
- Header: Optional. A property list of other header elements. If a "User-Agent" property is not specified here, the SenseTalk long version will be supplied.
The URL given in the post command may include a path to identify a particular service on the server. The response from the server will be stored in the variable it
.
To send an XML-RPC response, use either the XMLRPCResponse
function or the XMLRPCFault
function, along with the write
command:
write XMLRPCResponse(responseData) to socket rpcClient
write XMLRPCFault(33, "bad mojo") to socket rpcClient
To receive an XML-RPC message on a socket, use the read
command and specify xmlrpc
or xmlrpc message
(to read any type of XML-RPC message), xmlrpc request
(will throw an exception if the message received is not a request), or xmlrpc response
(will throw an exception if the message received is not a response or fault) as the type of value to read:
read an xmlrpc message from socket clientSocket into msgRecvd
read an xmlrpc request from socket clientSocket
The value read will be a property list with all of the information contained in the XML-RPC message that was received.
XMLRPCResponse
Function
Behavior: Generates an XML-RPC response formatted as an HTTP message in a format suitable for transmitting over a socket.
Syntax:
XMLRPCResponse( messageContent {, additionalHeaders} )
{the} XMLRPCResponse of messageContent
The messageContent is the value being returned in the response and is required. It may be any type of value, including a list or property list. If additionalHeaders is supplied it must be a property list containing any additional HTTP headers to be included in the message.
XMLRPCFault
Function
Behavior: Generates an XML-RPC fault formatted as an HTTP message in a format suitable for transmitting over a socket.
Syntax:
XMLRPCFault( faultCode , faultString {, additionalHeaders} )
The faultCode should be an integer value that identifies the fault. The faultString should be a string that describes the fault. Both the faultCode and faultString are required. If additionalHeaders is supplied it must be a property list containing any additional HTTP headers to be included in the message.
XMLRPCFormat
Function
Behavior: Returns a string representing any value in the format required for use within the body of an XML-RPC message. Specify "as boolean", "as string", "as date", etc., when needed to ensure individual values are encoded as the desired type.
Syntax:
XMLRPCFormat( valueToFormat )
{the} XMLRPCFormat of valueToFormat
Example:
put XMLRPCFormat(7) --> <value><int>7</int></value>
put XMLRPCFormat(7 as text) --> <value><string>7</string></value>
XMLContentEncode
Function
Behavior: Converts text into a safe format for including in the content of an XML document by replacing any occurrences of the characters "<" , ">", "&", and single and double quotes with their respective XML entity values.
Syntax:
XMLContentEncode( valueToEncode )
{the} XMLContentEncode of valueToEncode
XMLContentDecode
Function
Behavior: Decodes a string containing certain XML entity values, such as those generated by XMLContentEncode()
, and returns the original text.
Syntax:
XMLContentDecode( valueToDecode )
{the} XMLContentDecode of valueToDecode