<mailto:neumann@s-direktnet.de>
With E it is very easy to create distributed (client/server) applications,
which automatically (you don't need to do anything) communicate through an encrypted channel.
The advantage of E in comparison to other distributed architectures like COM/DCOM, CORBA or Java-RMI, is
the ease of use and the cryptographic security.
E is interpreted and because the interpreter is written in Java very portable, but which results in a lesser
efficiency.
However, advantageous is that programs written in E can easily use Java classes, so that
there are many libraries for your own usage.
Of course, E is free of charge and open source.
To get access onto a "far" object (an object which maybe resides on another computer), E uses so called capabilities, a concept which is used in secure-relevant operating systems, like EROS (Extreme Reliable Operating System), KeyKOS (predecessor of EROS) or Amoeba. You can consider a capability as a key for an unique object, which grants access onto this object. It is only possible to call methods of an object if you have the correct capability of that object. A capability is cryptographic secure, i.e. it is (almost) impossible to find the correct capability with brute-force or any other attack.
A capability for object A can only be created by object B if A is visible to B, i.e. both objects must be declared
at least in the same program.
Therefore object B can decide, which objects it grants access to object A by "sending" the capability of object A only
to those objects.
How a capability find it's way to the other objects is not part of E, that is the task of the programmer.
A capability in E is an URI (Uniform Resource Indentifier) which is comparable to an URL, i.e.
a capability is nothing more than a character string.
Here is a short example for a capability in E:
cap://192.168.0.5:1150/0BsXMeAG14TPw_=tYTCWTN7Q9zV3/x4dgCDy3O3NpMSn_2yUFOD.
You could now send this capability for example in an email encrypted by PGP or you could write it into a file, which
can be read by others, or if there is already an encrypted connection you could easily send the capability by invoking
a method with it as parameter.
If you access a "far" object through a capability, you cannot call it's methods like you do it with local objects,
no you have to make so called "eventual sends".
"Eventual sends" are distinguished by the calling-syntax of method calls, e.g. anObject method(1,2)
is a local method call where the method method of object anObject with parameters 1,2
is called and anObject <- method(1,2) is the comparable "eventual send".
The preculiarity of "eventual sends" is that it do not wait until the execution of the "far" method finishes (i.e. returns),
but immediately executes the next statement of the program.
Because "eventual sends" cannot return the value of the far method call, because it is maybe not yet calculated,
they return a so called "Promise" object. This object contains the state of the call, i.e. if the far method call
has been finished with execution and if the returned value is available on the local computer.
If this is the case (return value is available), you can determine the value of the far method call.
For that a special syntax exists
when (promiseObject) -> done(resultValue) {
execute_when_done
} catch error {
execute_when_error
}
done and catch (execute_when_done), when a local
value (which is stored in resultValue) for promiseObject gets available.
Like "eventual sends" this statement immediately branches to the next statement (below the catch block), that is
it do not wait until a local value gets available.
If an error occures (e.g. loss of connection) the catch block will be executed.
Now we come to the practical part. In the following example we'll create a client/server application, where the server calculates the addition of two numbers and returns the result to the client which then prints it onto the screen. The capability of the server object is made accessibly to the client by writing it to a file.
Server sourcecode:
# at first, the client/server mechanism must be initialized
introducer onTheAir
# define an object, which will later be made accessibly to the client
define ComputeService {
# method "add"
to add( x, y ) : any {
x + y
}
}
# function which returns a capability (URI) for the
# object given by the parameter "obj"
define makeURIFromObject( obj ) : any {
introducer sturdyToURI( sturdyRef( obj ) )
}
# create capability (URI) of the object ComputeService
define URI := makeURIFromObject( ComputeService )
# open file test.cap
define file := <file:test.cap>
# write capability into file
file setText( URI )
# indicate that the server is ready
# now the client can be started
print( "ready" )
# endless loop
interp blockAtTop
And now the client sourcecode:
# client must also initialize the client/server mechanism
introducer onTheAir
# returns the object which is represented by the given URI (capability)
define getObjectFromURI( uri ) : any {
introducer sturdyFromURI( uri ) liveRef
}
# read capability from file test.cap
define URI := <file:test.cap> getText
# create the "far" object from the URI
define remoteComputeService := getObjectFromURI( URI )
for a in 0..10 {
for b in 0..10 {
# the block within "when" is executed when the local value is available,
# but the program do not wait and executes the next statement (next iteration
# of the for-loop)
when ( remoteComputeService <- add( a, b ) ) -> done( res ) {
# print "a+b=res" onto screen
println( "" + a + "+" + b + "=" + res )
} catch problem {}
}
}
# endless-loop because otherwise the program will terminate before
# the calculation from the server returns (because "when" do not waits)
interp blockAtTop
References