|
This is an old document
describing how users of a ScriptEase Javascript/Ecmascript
product, or a product that is created with those libraries,
can include Distributed Scripting into their application.
Javascript users could use this protocol following the Distributed Scripting Protocol manual.
SE:DSP
was a technology created by no-longer-existing Nombas.
For more documentation, see Old Nombas User Docs.
DSP Link Library
Distributed Scripting Protocol is
implemented by the ScriptEase DSP link library as the DSP
object.
DSP Object
platform: All platforms except
Dos; All versions of SE
source: #link <sedsp>
The DSP object provides a
framework for implementing distributed scripting across a
variety of computers and networks.
Creating a DSP object
The Distributed Scripting Protocol
provides no internal method for managing a connection or
transporting packets. It is simply a framework, with the
physical transport method being supplied by the user. As such,
it is impossible to simply create a DSP object, because it is
incapable of doing anything by itself. The user must supply a
set of functions to manage the connection with the server. To
create a DSP object, you call new
DSP(myOpenFunction, myParameters). The
function that you supply must open the connection and return a
reference to it. It is possible in some instances that you do
not need to open anything special, and so you can ignore this
parameter. Here is an example of an open function for a DSP
connection, using internet sockets:
function idspOpen( host, port )
{
return new Socket( host, port
);
}
We will see this function
passed to the DSP constructor in a moment. First, to
accomplish sending/receiving packets, the user needs to define
two functions, dspSend
and dspReceive.
These functions must be inherited through the prototype chain,
because otherwise when DSP objects are copied implicitly
through reference construction (see below), the functions will
not get passed. Because we want to keep the DSP functions
(such as dspService), we need to preserve the original DSP
prototype, and a constructor looks like the following:
function iDSP( host, port )
{
var ret = new DSP( idspOpen,
host, port );
// Now we override the
._prototype to insert our functions
if( ret != null )
ret._prototype =
iDSP.prototype;
return ret;
}
// Here we set up the
iDSP.prototype to keep the DSP functions
// in the chain
iDSP.prototype._prototype =
DSP.prototype;
Once this constructor is
called, we have a valid DSP object, assuming we add the
transport functions. To do this, we must add dspSend
and dspReceive
to the prototype. The actual syntax of these functions is
similar to Clib.fread()
and Clib.fwrite(),
and a description can be found in the function reference. For
our iDSP example, they would look something like this:
function iDSP.prototype.dspSend(
conn, buffer, timeout )
{ // Ignore timeout
return conn.write(buffer);
}
function
iDSP.prototype.dspReceive( conn, &buffer, length, timeout)
{
return conn.read( buffer,
length );
}
Note that both these functions
ignore the timeout parameter and do not correctly handle
errors. A full-featured version of these functions can be
found in the file idsp.jsh.
The final function that we must provide is the dspCloseConnection
function, which is responsible for closing the connection.
This function looks like the following:
function
iDSP.prototype.dspCloseConnection( conn )
{
conn.close();
}
Once all of these transport
functions have been defined, new iDSP objects can be
instantiated with a call to new iDSP
and used as any other DSP object. Because the transport level
of DSP is separate from the core library, DSP can be adapted
to communicate between any servers in any way. In addition,
communication can be done during the call to the open
function. This allows for password authentication or any other
information to be shared.
Using a DSP object
Once a DSP object is created using
the method described above, every DSP object behaves in
exactly the same way. Once the functions are set up, the
transport layer of the protocol is hidden.
The basic idea is that all DSP
objects are in fact references to objects on the remote side,
and they will remain so except under certain circumstances
(described below). When a connection is first established, it
is a reference to the global object. Members of the remote
global object can be accessed as members of the connection.
But they remain references, so var
print = connection.Clib.printf will
not actually make a remote call to the server. At the
appropriate time, print
will be resolved into Clib.printf()
and sent to the server in the appropriate manner. The
circumstances which can trigger a de-referencing and remote
call are:
Calling functions
- When a DSP reference is called as a function, it gets
resolved into the appropriate path and the function is called
on the remote server. All parameters are converted to source
with ToSource()
and passed to the server, and set back afterwards (in case any
were passed by reference). The client waits for the return
value from the server and returns that as the result of the
function call. This makes calling functions transparent to the
client, so connection.Screen.writeln("hi")
will actually call Screen.writeln
on the server and print out "hi".
Setting a value
- When a value is put to a DSP reference, such as connection.globalCount
= 5, a remote call to the server is
generated, and the remote value is updated. The above case
acts just as if globalCount = 5
was executed on the server.
Implicitly
- When a DSP reference is converted to a primitive, then it
gets de-referenced. This implicit conversion happens mostly in
operator expressions, in which both values are converted to
primitives first. So var myCount =
connection.globalCount + 1 will get
the value of globalCount
from the server and add one to it. This can also be
accomplished explicitly with ToPrimitive(),
but the method below is more straightforward and
understandable. The explicit use of ToPrimitive() on DSP
references is discouraged.
Explicitly
- Any DSP reference can be explicitly de-referenced with a
call to.dspGetValue.
Once an object has been de-referenced this way, any subsequent
accesses will not cause a remote call, and changes will only
affect the local copy. Note that calling a function in this
way will result in the function being called on the local
client, not the server.
DSP object instance methods
DSP()
syntax:
|
new DSP( [openFunction[,
param1[, ...]]])
|
where:
|
openFunction - The
function to call to initialize the connection.
paramN - Additional
parameters to pass to the open function
|
return:
|
object - A new DSP
object, or null
on error. This is the object that will be passed as
the first parameter to all for most of the DSP methods
(dspReceive, dspSend). Those methods should use the
first passed parameter, and not the "this" variable,
for any connection-specific properties or
methods--because the dsp object is acting as a proxy
to another system to use the "this" variable would
instead be acting on the remote system.
|
description:
|
This function creates a
new DSP object, or returns null
on error. Note that calling this function itself
accomplishes very little unless you build up an
appropriate DSP object by adding open, close, and
transport functions. A new DSP object can be created
with just new DSP(),
but it will be unusable without transport functions.
See the introduction, under creating
a DSP object, for more
information about setting up a proper DSP object. The
first optional parameter is the open function to use.
Once the object has been created, this function is
called with any additional parameters passed to DSP().
The result of this call is set the dspConnection
member of the newly created object, and is only used
to pass as the first parameters to the dspSend,
dspReceive, and dspCloseConnection
methods. If openFunction
is supplied and returns null,
then it is considered an error and the DSP
construction fails.
|
see:
|
#link <sedsp>
|
example:
|
function fileOpen(
filename )
{
return Clib.fopen(
filename, "wb" );
}
var connection = new
DSP( fileOpen, "c:\tempfile.dat" );
// This will call
fileOpen and assign the result to
//
connection.dspConnection. If it was null,
// then the DSP
connection will fail
|
DSP dspCloseConnection()
syntax:
|
dsp.closeConnection(connection)
|
where:
|
connection - The
original connection that was created with the
openFunction passed to new
DSP()
|
return:
|
void.
|
description:
|
This function is
responsible for terminating the connection that was
opened at the time the DSP object was created. This is
an optional function, and if not supplied then nothing
will be done with the connection. See the
introduction, under creating
a DSP object, for an
example of how to implement this function.
|
see:
|
#link <sedsp>,
DSP()
|
DSP dspReceive()
syntax:
|
dsp.dspReceive(connection,
buffer, bufferLength,
timeout)
|
where:
|
connection - The
original connection that was returned from the
openFunction passed to new
DSP()
buffer - A buffer which
is to be filled with data. This variable must be
passed by reference (with the & operator).
bufferLength - The
maximum amount of data to read
timeout - The maximum
amount of time to wait (in milliseconds) for data to
be ready for reading on the connection
|
return:
|
number - The number of
bytes read, or -1 on error
|
description:
|
This function is
responsible for getting data from the connection. This
function should wait up to timeout
milliseconds for data to be available on the
connection. If there is no data available, then this
function should return 0. Otherwise, the function
should read up to bufferLength
bytes from the connection and put the data into buffer.
Note that this means that buffer
must be passed by reference. If there is some sort of
error, then this function should either throw an
error, or return -1. See introduction, under creating
a DSP object, for an
example of how to implement this function. Note that
the function need not wait for the entire buffer to be
filled, it should read only as much data as is
available to be read.
|
see:
|
#link <sedsp>,
DSP dspSend()
|
DSP dspSend()
syntax:
|
dsp.dspSend(connection,
buffer, timeout)
|
where:
|
connection - The
original connection that was returned from the
openFunction passed to new
DSP()
buffer - The buffer to
send
timeout - The maximum
amount of time to wait (in milliseconds) for data to
be ready for writing on the connection
|
return:
|
number - The number of
bytes written, or -1 on error
|
description:
|
This function is
responsible for sending data across the connection
(the one returned by the openFunction passed to the
DSP constructor). Its behavior is similar to that of
dspReceive(). It should wait up until timeout
for data to be ready, and then send as much as
possible along the connection (up to the length of buffer).
If the timeout expires, the function should return 0.
If there was some sort of error, then an error should
be thrown, or -1 returned. Otherwise, the number of
bytes written should be returned. Throwing an error is
often more descriptive than the generic failure
message. See introduction, under creating
a DSP object, for an
example of how to implement this function.
|
see:
|
#link <sedsp>,
DSP dspReceive()
|
DSP dspLoad()
syntax:
|
dsp.dspLoad(code)
|
where:
|
code - String of code to
load on the remote server
|
return:
|
void.
|
description:
|
This function loads the
specified code into the global context on the remote
server. Any code that you execute will remain on the
remote server. This function is designed to load
functions on the remote server so that they may be
called by the client. This function does not wait for
a return value from the host. As a consequence, remote
errors will not be immediately reported. They will be
reported next time a client routine (calling a
function, getting/putting a value) queries the server.
Note that if you wish to execute remote code and get a
return value, the global eval() method for the server
should be used, although the changes will not be
permanent.
|
see:
|
#link <sedsp>
|
example:
|
function foo() {
Screen.writeln("Hello!"); }
// This code will make
"foo = new Function(...)"
// to set up the
function on the remote server.
connection.dspLoad( "foo
= " + ToSource(foo) );
connection.foo();
// foo is now a global
function on the server
|
DSP dspService()
syntax:
|
dsp.dspService()
|
return:
|
boolean - A value
indicating whether the connection is still open.
|
description:
|
This is the main
server-side function. Although it can be used by any
DSP object, it is intended to be the server side of
the client-server model. When called, it will wait
until an incoming packet is received and then service
that packet appropriately. The method will return false
if the packet received was a close command, in which
case the connection has been closed, and an explicit
call to dspClose
is not necessary. It is designed to be called
repeatedly until the connection is closed.
|
see:
|
#link <sedsp>
|
example:
|
// Assume 'connection'
is a valid connection
while(
connection.dspService() )
;
// At this point, the
connection has been
// successfully closed
|
DSP dspClose()
syntax:
|
dsp.dspClose()
|
return:
|
void.
|
description:
|
This function closes the
DSP connection. First, it sends a close command to the
remote host, signaling that the connection is closing.
It then calls the dspCloseConnection
method if it exists, passing the original connection
variable returned by the open function when this
connection was created.
|
see:
|
#link <sedsp>
|
example:
|
connection.dspClose();
|
DSP dspGetValue()
syntax:
|
dsp.dspGetValue()
|
return:
|
variable - remote value
of the current DSP reference.
|
description:
|
This function provides
an explicit way to convert a DSP reference into a
value. Such conversion is done automatically when the
reference is converted to a primitive, or a value is
assigned to a reference. See the introduction, under creating
a DSP object, for more
information on DSP references and getting remote
values.
|
see:
|
#link <sedsp>
|
example:
|
var reference =
connection.globalValue;
var value =
connection.globalValue.dspGetValue();
reference = 5; // This
will change the remote value
value = 6;
// This will change the
local copy, not the remote
|
DSP dspSecurityInit()
syntax:
|
dsp.dspSecurityInit(secureVar)
|
where:
|
secureVar - private
storage for the DSP security. The member 'dsp' is
preset to the DSP object. Remember, the DSP object can
be seen by the running script, but not the secure
variable itself.
|
return:
|
void.
|
description:
|
The dspSecurityInit
function turns on security for a DSP object. This
means when the remote client tries to run a script on
your machine using DSP, it will be run with your
security manager in effect. In the case of DSP, each
security function (jseSecurityInit, jseSecurityTerm,
and jseSecurityGuard) has an exactly corresponding
function, i.e., dspSecurityInit, dspSecurityTerm, and
dspSecurityGuard. In the security initialization
function, you'll typically select some functions to be
allowed, and let all others be vetoed.
|
see:
|
#link <sedsp>,
DSP dspSecurityTerm(),
DSP dspSecurityGuard()
|
example:
|
function
iDSP.dspSecurityGuard( conn )
{
myfunc.setSecurity(jseSecureAllow);
myotherfunc.setSecurity(jseSecureGuard);
}
|
DSP dspSecurityTerm()
syntax:
|
dsp.dspSecurityTerm(secureVar)
|
where:
|
secureVar - private
storage for the DSP security.
|
return:
|
void.
|
description:
|
This function is
typically not needed, but you can use it to cleanup
anything you initialized in the DSP security
initialization function.
|
see:
|
#link <sedsp>,
DSP dspSecurityInit(),
DSP dspSecurityGuard()
|
DSP dspSecurityGuard()
syntax:
|
dsp.dspSecurityGuard(secureVar,
function,
params)
|
where:
|
function - the function
being called
secureVar - private
storage for the DSP security.
params - whatever
parameters are passed to the function
|
return:
|
void.
|
description:
|
If a DSP object is given
a dspSecurityGuard function (exactly like any of the
other DSP callback functions), when it tries to call
any function not part of the script (i.e. one of your
functions or a wrapper function), the security guard
is called for approval. You must provide a
dspSecurityInit for security to be activated. Only
those functions the security initialization function
marks as guarded will use this function.
|
see:
|
#link <sedsp>,
DSP dspSecurityInit(),
DSP dspSecurityTerm()
|
DSP object static properties
DSP.remote
syntax:
|
DSP.remote
|
description:
|
This global property of
the DSP object is used to make calls back to the
remote client from within a function. When the first
DSP object in a script is created, this gets assigned
to that value. From then on, whenever a packet needs
to be serviced, this value is set (and later restored)
to the object representing the incoming connection.
This allows for multiple connections, and lets the
function easily call back the appropriate client. Note
that within a dspLoad
call, the client does not wait for a response, and so
trying to call on the client will yield no result
until the server is queried again.
|
see:
|
#link <sedsp>
|
example:
|
// Assume the client
calls this:
serverConn.printRemote("hi");
// And the server side
looks like this:
function printRemote(
string )
{
DSP.remote.Screen.write( string );
}
// This will print out
"hi" on the client machine
|
|