<previous | contents | next> | Pyro Manual |
More specifically, when your client detects a problem with the network
(ConnectionClosedError
) it can use a special 'hidden' method
of the internal Pyro protocol adapter object to request it to reconnect
to the server:
yourpyroobject.adapter.rebindURI()
tries
and
wait
. The first is the number of retries that is performed
(default: sys.maxint), the second is the delay time between each retry
in seconds (default: 1).
connectPersistent
method of the PyroDaemon. Otherwise the reconnect feature does not work because the client tries to access an invalid URI.
The connectPersistent
method reuses the object's URI that is still known in the Name Server.
It's no problem to always use connectPersistent
instead of the regular connect
,
but there is a naming lookup overhead per call.
rebindURI
method of the object's
protocol adapter itself if this is the case.
Examine the example code provided in the "autoreconnect" example, and Pyro's internal adapter code, to see how the rebind feature works.
ImportError
. However, Pyro intercepts this and returns a NoModuleError
.
Unless you enable PYRO_MOBILE_CODE
. Now, Pyro internally returns a special code, which makes the client submit the missing Python code to the server. Pyro then proceeds as if nothing was wrong, i.e. it
can now load the previously missing module and call those objects! This happens automatically, you only have to enable mobile code on the server by setting the PYRO_MOBILE_CODE
config item to 1!
There is one more thing: loading and running arbitrary code is dangerous. A client might supply a hazardous or buggy program. Therefore you can set a codeValidator for each Pyro object that might load mobile code.
This codeValidator is a function (or callable object) that takes three arguments: the name of the module, the code itself, and the address of the client (usually a (IP,port) tuple). It should return 0 or 1, for 'deny' and 'accept'.
Pyro.core.ObjBase
, the base class of all Pyro objects, has a
setCodeValidator(v)
method that you must call with your custom
validator function (or callable object). You can set a different validator
for each Pyro object that your server has.
There are a few important limitations with the current mobile code implementation:
__main__
will not work!
It is perfectly ok to put your agent module in a Python package. Have a look at the "agent2" example to see how this all works.
In the past, there was only one built-in check; the number of connections was limited to a certain amount.
Since version Pyro 2.0 this check is now done by the default connection validator Pyro.protocol.limitingConnValidator
.
The fun is that you can supply your own validator object, and that you can therefore implement much more complex access checks. For instance, you might want
to check if the client's site is authorized to connect. Or perhaps you
require a password to connect.
Notice: currently the connection validators do not directly support a conversation-like validation scheme. Therefore it is not possible to implement a user-password based validation using the validators (at least, not easily). If you want this, perhaps the easiest way is to build an application level authorization scheme. Let your Pyro objects handle a user-password protection, instead of depending on Pyro itself to do it in the network layer. Maybe a future version of Pyro will have more advanced validators.
accept(self, connection)
method. The connection
is a Pyro.protocol.TCPConnection
object. The client's address is in connection.addr
.
When you want to derive from the default validator Pyro.protocol.limitingConnValidator
, keep in mind that the constructor needs the
Pyro Daemon object as argument.
setNewConnectionValidator
method. Supply an instance
of your validator class as an argument.
accept
method must return (1,0)
if the connection is accepted (or call another validator's accept
).
It must return (0,code)
when the connection is refused, where code
is one of the following:
Deny Reason Code | Description |
---|---|
0 | unspecified |
1 | server too busy (too many connections) |
2 | host blocked |
3 | security reasons (general) |
ConnectionDeniedError
on the client when you deny a new connection. On the server, you'll have to log the reason in the Pyro logfile yourself, if desired. When you accept a connection, the daemon will log an entry for you.
Of course, a little overhead is introduced. You can see this quite clearly
when you are running the "benchmark" example in single- and multithreaded mode. The singlethreaded mode is quite a bit faster.
Still, Pyro will default to the multithreaded mode if your system supports it,
because usually you'll need Pyro to be able to accept new invocations while
others are still in progress. If you want high performance, use the
PYRO_MULTITHREADED
config item to switch to singlethreaded
mode (set it to zero). Pyro will default to single threaded mode if Python
threads are not available. Switching to multithreaded mode if Python
threads are not available, by setting
PYRO_MULTITHREADED
to 1, will crash Pyro.
Please use Pyro.util.supports_multithreading()
to test whether or
not your system is able to use multithreading.
The "multithread" example shows what's going on, and how multithreading can help to improve performance and response times.
Usually Pyro will locate its servers and objects using fixed IP numbers
encoded in the Pyro URIs. This may or may not be appropriate.
For instance, when a machine has an IP number that is only temporary, such
as DHCP-leased IP numbers. The machine can get a new -different- IP number while
Pyro is still running. URIs with the old IP number are now invalid!
Therefore it is possible to tell Pyro to use symbolic DNS hostnames
in URIs instead of raw IP numbers. Pyro will then use DNS to look up the
actual IP number of the specified host (by name). You can enable this by setting
the PYRO_DNS_URI
config item to 1
. However note
that each time Pyro has to look up a host, there is a DNS lookup delay.
If your machine has multiple IP addresses (for instance, when it has
multiple network cards), you have to decide on what IP address your
Pyro servers reside. When you create a Pyro Daemon, use the host
argument to specify the hostname/ip address to bind the server on (defaults to '' - the default host).
The Name Server can be started with a -n hostname argument, to
specify the hostname/ip address to bind on.
Another issue is when you're using Pyro behind a firewall.
There is one specific form of firewalling that is addressed in Pyro:
simple Network Address Translating firewalls using port forwarding.
Let's say you're at 192.168.0.1 (a private address) behind a NAT
gateway that's 192.1.1.1. You have port forwarding on the NAT, so
that Pyro requests go to the private box. However, with the way that
Pyro is set up by default, the daemon will publish URI's as though they come
from 192.168.0.1 -- an address unavailable to the rest of the Net.
There is no way to have 192.168.0.1 publish URI's as though
it were actually 192.1.1.1. Were it not for the extra publishhost
parameter for the constructor of Pyro.core.Daemon
.
When constructing a Pyro Daemon, you can give it a special hostname or IP address that it should use when publishing URIs, via the publishhost
parameter. The host
parameter still is the "real" hostname
of the machine the daemon is running on. When publishhost
is
not specified (it isn't by default) the value for host
is taken.
If that isn't specified either (it isn't by default) the hostname of the
machine is queried and that is used. In our little example, host
should be 192.168.0.1
(or just empty/omitted) and publishhost
should be 192.1.1.1
, the address of the firewall/gateway.
Your client must publish a callback object that is a true Pyro object. In fact, for the callback part, your client must act as a true Pyro server. So you have to code your client as both a client and a server.
Be very aware of threading issues. Callbacks occur in their own thread. A callback may even occur while your client is still busy processing the previous callback. Your server should be even more prepared for callbacks. Usually you have a method that "registers" a client as a callback object. The client will call this method and has to pass a proxy to the callback object, not the object itself! (see usage rules, below).
Your server usually has a list of callback objects that it should call. Be very careful here:
a client can unexpectedly disappear. You have to handle ConnectionClosedError
exceptions and you must then remove the defunct callback object from the list.
But you have to do this in a thread-safe way (the list must be under a thread-lock),
because the server may be multithreaded!
The server also has to handle any exceptions that occur in the callback object on the client.
Don't trust it. Catch any exception that occurs, otherwise your server dies.
Please consider using the Pyro Event Service, instead of custom callback objects. It will handle all those nasty things for you, at the cost of some control. But it's very easy to use.
Oneway calls: To make life somewhat easier, Pyro 2.4 introduced Oneway Calls. These are remote method calls that do not expect any answer, so they return immediately after sending the remote call to the remote object. The call does not wait for the remote method to finish. At a lower level, it doesn't even wait for a protocol reply, so performance is much better too. There is no way to find out what the result of your request was - wether it succeeded or failed. No result nor any exception is ever returned. You're still quite sure that the call is performed though, because the request is sent using the regular PYRO protocol, but there is no guarantee (nor is there with regular calls by the way).
Oneway calls are very nice to have in a callback scenario. The Event Service also makes heavy use of them. Why? Your server is freed from the burden of handling exceptions that may occur in the remote method, and it doesn't block on slow or buggy clients. It just sends out the method invocations and continues on happily while the callback clients process the incoming method call.
You have to specify at runtime in your program which methods of what objects have this Oneway semantics. You do this by calling a special method on the Pyro proxy object:
obj._setOneway(methods)where obj is your proxy and methods is a list or tuple of method names that have to be called Oneway. It may also be a single method name. Currently there is no way to specify from within the remote object itself, or the creating process, that a method has to be called oneway. The calling party has to set this property. Ofcourse you could build some sort of inquiry method that has to be called first and that tells the caller what methods can have this property, and maybe this will become automatic in a future version, but it's not yet there.
Assume the remote Pyro object raises a ValueError
exception.
Your calling code will receive this and crash wit the same ValueError
exception.
However, the stacktrace in the traceback info is from your local
code, not from the remote code. If you're just catching and processing exceptions,
and don't want to deal with stacktrace/traceback info, there is no problem with this.
But if you want to print the stacktrace, it is meaningless! It is a stacktrace from
within the bowels of the local Pyro code! It provides no clue what piece
of remote code caused the problem.
To help you with this, Pyro puts the remote stacktrace inside
the exception that travels to your calling code. It can be obtained from
the special attribute that is defined in Pyro.constants.TRACEBACK_ATTRIBUTE
,
but it's probably more convenient to use the utility function
Pyro.util.getPyroTraceback
. You pass the exception object and
the function returns a list of lines that contain the remote traceback
(if available) and the local traceback. For example;
try: print thing.method() # thing is a Pyro proxy except Exception,x: print ''.join(Pyro.util.getPyroTraceback(x))See the "simple" and "exceptions" examples.
pickle
protocol to pass calls to remote objects.
There is a big security problem with pickle
: it is possible to execute arbitrary
code on the server by passing an artificially constructed pickled string message.
The standard Python Cookie
module also suffers from this problem.
At the moment of writing, the Python documentation is not clear on this subject.
The problem is known to various people.
Using Pyro over the internet could expose your server to this vulnerability!!!!
Currently, there is no solution. Using the (safe) marshal
module is no option
for Pyro because we lose the ability to serialize user defined objects.
Writing another serialization module is possible but is a lot of work, and the cPickle
module is likely to be much faster.
If anybody has any suggestions, please let me know.
__init__
method. You should use a regular initialization method that you must call explicitly after
binding to the remote object. The __init__
method will only be called on the server side when the object is created.
object1==object2
for instance. This is because the
proxy class needs to hijack the rich comparison mechanism to be able to compare two proxy classes with each other.
Package.module
').
Pyro.naming.NameServerProxy
. When you use the Locator, you're safe.
.py
source files that contain the code of the objects that are used as parameters in remote method calls, must be available on the client and on the server. Otherwise the server cannot load the implementation code of an object that arrives in a remote method call. Since Pyro 2.0
this is no longer necessary if you enable the mobile code feature.
See the "agent2" example.
Pyro.core.ObjBase
.
You could define a new (probably empty) class in the server that inherits
both from Pyro.core.ObjBase
and the class that is made remotely
available. This approach is used in the example in the next chapter. See also the next item.
Pyro.core.ObjBase
. This works as follows: create an object of your remote class, create a Pyro.core.ObjBase
object, and tell the latter to use the former as delegate:
... impl = MyRemoteClass() obj = Pyro.core.ObjBase() obj.delegateTo(impl) ...and you then connect
obj
to the Daemon.
__init__
from the base class in the __init__
method -if you have one- of your remote class: def __init__(self): Pyro.core.ObjBase.__init__(self) ...
while obj:
.
Like the limitation with direct member access, this is also caused by the
way the proxy object is currently implemented. It intercepts each and every
method call. And testing for zero-ness or nonzero-ness or coercion are also method calls! (__nonzero__, __coerce__
etc.)
Pyro.core.ObjBase
, or use the delegation approach,
and call Pyro.core.ObjBase.__init__(self)
from your own __init__
URI = self.getDaemon().connect(object) # no name, not in NS URI = self.getDaemon().connect(object,'name') # registered in NS with 'name'
return object
that you would otherwise use, you do:
return object.getProxy() # regular dynamic proxy return object.getAttrProxy() # dynamic proxy with attribute support
self.getDaemon().disconnect(object) del object
<previous | contents | next> | Pyro Manual |