# Managing Clients of Perspectives

## Overview

As we've watched several applications build on top of `twisted.cred` and Perspective Broker, we've seen several models of interaction between `Perspective`s and the clients which connect to them. In the future, these patterns may be codified by support classes in the framework, but for now we shall just document them here. They are:

Clientless Perspective

  in which the Perspective remains oblivious of the fact that clients attach to it.

Single Client

  in which no more than one client is attached to a Perspective. There are two sub-categories, based on how this limit is enforced:

  - excess connections from clients are refused (seen in CVSToys).
  - new connections replace the old (seen in `twisted.words`).
Multiple Client

  in which the Perspective keeps a list of clients instead of a single one. The clients all share this Perspective, the actions of any may effect the perspective for all. (Seen in `twisted.manhole`.)

Anonymous Clients

  where any number of clients may connect to the service using a particular perspective name, but clients may not effect one another and any changes to the perspective do not persist.

explain or point to how clients get attached to perspectives (pb.connect, guard(?)).

## Clientless Perspective

clientless.py

Needless to say, the `ClientlessPerspective` is not ideal for all applications. A common model for network applications is to have the client functioning as an observer of messages distributed by the server (i.e. chat services, build failure notification, etc.). For this purpose, the server needs maintain a list of observers on reachable clients. The Perspective class provides facilities for this, offering `attached`() and `detached`() methods which are called with references to clients connecting or disconnecting from the service.

## Single Client

single.py

Here's a more complex example, using the more specialized `brokerAttached` method of Perspective Broker.

single-pb.py

Here's an example which attempts to enforce the single-client limit in a different manner:

single-kick.py

What do we learn?

- `twisted.words`, the only system that ships with Twisted which uses Perspectives in Perspective Broker, is a lousy example. ;)
- `detached` is not sufficient to kick a client off.

In fact, it turns out to be hard to kick a client off a Perspective, because there's no way you can force them to lose the reference they hold. The best you can do is define a goodbye method on the client interface and hope they honor and implement it correctly. If the client is talking to you over a transport (as is the case with Perspective Broker), you can kick them off somewhat forcibly by closing the transport, but this is bad practice for several reasons. First, it requires knowing how to access and shut down the transport, which breaks some abstractions. Second, it's a damned inconsiderate thing to do if that transport may have been also carrying traffic for other services.

I can think of several lines along which you could develop from here:

- Add a way to mark references as invalid to Perspective Broker. Then when the client tried to call a method on the perspective, it would get an exception saying it wasn't allowed to talk to that object any more. Obviously, this only works for Perspective Broker, and not for other systems which might access your Service.
- Forget about the whole idea of kicking clients off. Anyone who has gained a reference to the Perspective through the Authorizer has a right to keep it. If another client wants to attach before the first one is done, you'll just have to accommodate them both. Which leads us to...

## Multiple Client

multiple.py

## Anonymous Clients

Last item on the list: Anonymous perspectives. One way to do it would be to use a `ClientlessPerspective` or `MultipleClientPerspective` and promise to not have any methods that stored state on or otherwise modified the perspective instance so no client can interfere with any other. Another way to do it, without that restriction, would be to use disposable Perspectives:

unattachable.py

If you wanted to have all access to a Service be anonymous, you could make a service like this:

    class AnonymousService(service.Service):
        class perspectiveClass(UnattachablePerspective):
            disposablePerspectiveClass = MyAnonymousPerspective
            # XXX: does this lazy subclassing work, or do you end up with a class
            # that isn't persistable because it's not module-level or something?

But to make only certain log-ins anonymous:

    theService = Service(serviceName)
    anonymousPerspective = UnattachablePerspective("anonymous")
    anonymousPerspective.disposablePerspectiveClass = MyAnonymousPerspective
    theService.addPerspective(anonymousPerspective)
    # Set anonymous's password to the empty string:
    anonymousPerspective.makeIdentity('')

## Feedback

That's all for today, thanks for playing. We'd like to hear about how you're using this code! Questions, comments, reservations? Please send them to twisted-python@twistedmatrix.com.