Managing Multiple Services
One of the most powerful features of Twisted is its ability to run multiple services in a single process. You can write an application that shares objects and data with many different protocols at the same time. You can also manage the different services in an application at runtime, starting and stopping them individually without having to restart your application.
11.3.1. How Do I Do That?
Add more than one service to an application object and run the application using twistd. You can iterate through the current services in your application by wrapping the application object in the service.IServiceCollection interface. Call startService and stopService to start or stop one of the services. Example 11-4 creates an application that offers both web and Telnet interfaces to the same data, and a web administration interface that lets you selectively start and stop services.
Example 11-4. multiservice.py
from twisted.application import service, internet from twisted.internet import protocol, reactor, defer from twisted.protocols import basic from twisted.web import resource, server as webserver class Reverser: def _ _init_ _(self): self.history = [] def reverse(self, string): self.history.append(string) reversed = string[::-1] return reversed class ReverserLineProtocol(basic.LineReceiver): def lineReceived(self, line): if hasattr(self, 'handle_' + line): getattr(self, 'handle_' + line)( ) else: self.sendLine(self.factory.reverser.reverse(line)) def handle_quit(self): self.transport.loseConnection( ) class ReverserLineFactory(protocol.ServerFactory): protocol = ReverserLineProtocol def _ _init_ _(self, reverser): self.reverser = reverser class ReverserPage(resource.Resource): def _ _init_ _(self, reverser): self.reverser = reverser def render(self, request): if request.args.has_key("string"): string = request.args["string"][0] reversed = self.reverser.reverse(string) else: reversed = "" return """
Previous Strings
- %s
""" % (reversed, " ".join(["
- %s
- " % s for s in self.reverser.history])) class ServiceAdminResource(resource.Resource): def _ _init_ _(self, app): self.app = app def render_GET(self, request): request.write("""
Run multiservice.py using twistd. It will start a server that offers two different interfaces to a Reverser object, which reverses strings. You can connect to the server using telnet on port 2323:
$ telnet localhost 2323 Trying 127.0.0.1... Connected to sparky. Escape character is '^]'. this is a test tset a si siht another test tset rehtona quit Connection closed by foreign host.
You can also access the server using a web browser on port 8000. Figure 11-1 shows how the history data is shared between the Telnet service and the web service.
Figure 11-1. The multiservice.py web interface
Example 11-4 also runs a second web server on port 8001. This server provides the interface shown in Figure 11-2 for starting and stopping services.
Now, if you try to telnet to port 2323, you find that the service is no longer available:
$ telnet localhost 2323 Trying 127.0.0.1... telnet: Unable to connect to remote host: Connection refused
Figure 11-2. Stopping the Telnet service through the web administration interface
11.3.2. How Does That Work?
Example 11-4 creates three Resource objects: lineService, webService, and webAdminService. It uses setServiceParent to register each of them as a child of application. The ReverserLineFactory used by lineService is initialized with a Reverser object; this same object is passed to the ReverserPage that webService uses to handle web requests. Since both services are using the same object, they're able to transparently share data.
The webAdminService in Example 11-4 uses a ServiceAdminPage object to provide a web interface for starting and stopping resources. ServiceAdminPage is initialized with the application object so that it can access all available services. As a twisted.python.components.Componentized object, there's not much you can do with the application directly. Instead, you expose one of its internal objects by wrapping it in the interface you want to use. To loop through all the services, the ServiceAdminPage class uses the service.IServiceCollection interface, which returns an iterable object. ServiceAdminPage renders a form with an input checkbox for each service. When you submit this form, ServiceAdminPage loops through the services again and compares their current state with the checkbox value. If the service is running, but the box wasn't checked, it stops the service; if the service wasn't running, but the box was checked, it starts the service.
The web administration service in Example 11-4 even lets you stop it. That's probably not something you want to allow in a real application!
Категории