Using Twisted resources in applets

Hanji uses two paradigms which are usually considered conflicting: threaded (blocking) and asynchronous (non-blocking). The threaded model is used in Django, the asynchronous is used in Twisted.

Simple cases (applets responding to standard http or ajax requests sent by a client browser and having database models) should not be affected: Django gives the guidelines for most aspects: request handing, database models, etc.

But for more networking capabilities, the asynchronous model might be a better choice (see this quick explanation, these slides or this conference topic). Hanji applets can take advantage of the asynchronous model with the underlying Twisted framework. Actually, this is the preferred method for handling networking resources, slow devices, etc.

This chapter describes how Hanji uses both models concurrently, and few points to consider for the developer.

Background

Twisted runs one main thread, which handles an event loop (called the reactor). Some core Hanji resources (like static files, websockets, specific protocols, etc) are handled by this main thread.

When it starts, the reactor also starts few threads handling the wsgi events, managed by Django. These might be blocked (waiting for some input/output like a network connection, a device, anything that does not reply in, say, few milliseconds). So there is no big deal if an applet blocks when serving one request.

Twisted Resources

For example, an applet would handle a specific protocol on a dedicated TCP port.

On activation, it should create a resource and tell the reactor to handle it, like in any other Twisted application. The only thing to remember is that this piece of code will run in a wsgi thread: interraction with the reactor must be done using the reactor.callFromThread function. For example:

from hanji.core import applet
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory

class MyProtocol(Protocol):
    ## Some stuff here
    pass


class MyClientFactory(Factory):
    protocol = MyProtocol


class Applet(applet.Applet):

    ## ... all applet code here ...

    def on_activate(self):
        my_factory = MyClientFactory()
        reactor.callFromThread(lambda: reactor.listenTCP(1234, my_factory))

Http resources

The previous chapter (Twisted Resources) covers the case when a new port is opened. Applets’ WebUI can also dynamically register asynchronously managed resources on the main http server, using the twisted_dynamic_resources attribute, which is a list of resources to be created when the application starts (the resource is attached to the /res URL path). The resources constructor gets the applet instance as argument. The hanji.core.utils.TwAppletResource class is a handful shortcut for that purpose.

Simple example:

from hanji.core import applet
    from hanji.core.utils import TwAppletResource
from twisted.internet import defer

class MyResource(TwAppletResource):
    ## This might be important to define this resource as leaf
    ## if the url path contains the "/" character after "/res/<applet_id>/":
    isLeaf = True
    path = "path_to_my_resource"
    def render_POST(self, request):
            ## If it is not too long, you can get the POST data with:
            body = request.content.read()
            ## Check if the request header contain something with:
                    if request.args['is_good'][0] == "yes":
                            return "All right"
                    else:
                            request.setResponseCode(500)
                            return "I'm sorry Dave, I'm afraid i can't do that"

Requests on a specific URL (/res/<applet_id>/<path>) are then handled by the reactor: developers must respect the basic principles of asynchronous programming (any long process must return NOT_DONE_YET).

Example:

from hanji.core import applet
    from hanji.core.utils import TwAppletResource
from twisted.internet import defer

class MyResource(TwAppletResource):
    ## This might be important to define this resource as leaf
    ## if the url path contains the "/" character after "/res/<applet_id>/":
    isLeaf = True
    path = "path_to_my_resource"

    def give_response(self, request):
        ## Whatever long IO stuff with the request
        pass

    def render_POST(self, request):
        self.deferred = defer.Deferred()
        self.deferred.addCallback(self.give_response, request)
        ## Also some errback:
        # self.deferred.addErrback(self.give_error)
        return NOT_DONE_YET


class WebUI(applet.WebUI):
    ## ... all applet code here ...
    twisted_dynamic_resources = [MyResource]

In the applet template, you can use the hanji.callback function to trigger the call:

<div dojoType="dijit.form.Button">
        Test me!
        <script type="dojo/method" event="onClick" args="evt">
                hanji.callback('{{ applet.id }}','path_to_my_resource')
        </script>
</div>

Note

If a url is defined in both Twisted and Django, the Twisted process will be used.

Table Of Contents

Previous topic

Applets

Next topic

Debugging

This Page