Serverside Callbacks

Since version 0.9.8, Toscawidgets allows the registration of serverside callbacks. These are a powerful feature, intended to aid the development of self-contained widgets which need support-methods on the server-side, such as AJAX-driven search pages and the like.

As usual, an introductionary example explains things better that thousand words:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import webob

from tw.api import (
    Widget,
    ServerSideCallbackMixin,
    serverside_callback,
    make_middleware,
    always_allow,
    )

from webtest import TestApp

class TestWidget(Widget, ServerSideCallbackMixin):

    @serverside_callback
    def test_callback(self, request):
        response = webob.Response()
        response.content_type= "text/plain"
        response.body = "Look mam, no webframework needed!"
        return response

    def callback_authorization(self, callback, request):
        return always_allow(callback, request)


serverside_widget = TestWidget("serverside_widget")

def app(environ, start_response):
    start_response("200 OK",
                   [("Content-type", "text/plain")])
    # this method can only be called inside the webapp,
    # because only then the resource middleware and host-framework that deal with
    # the callbacks are active.
    callback_url = serverside_widget.url_for_callback(serverside_widget.test_callback)
    return [callback_url]

app = make_middleware(app, stack_registry=True, config={})
app = TestApp(app)
callback_url = app.get("/").body
res = app.get(callback_url)
assert res.body == "Look mam, no webframework needed!"

Most of the code is just to create a testing-harness - you don’t need to worry about it.

There are three new concepts though, and these need to be addressed:

  • declaring callbacks with serverside_callback.
  • getting the url a callback is registered under.
  • protecting the callbacks with callback_authorization.

Also, please note that the widget needs to be subclassed from ServerSideCallbackMixin.

Declaring a Callback

A callback is a normal instance-method which is decorated with the decorator serverside_callback. It will always only have one argument, a webob.Request-instance which can be used to get access to all parameters and other attributes of the request.

It must return a webob.Response-instance to render it’s output.

Of course the callback can invoke all methods a widget has by itself.

Getting callback urls

The above toy-example shows how to reach a widget’s callback. To do so, one needs to know where the callback is mapped to. In a real-world example, this would then be passed to javascript-code, or rendered into links in the widget-template.

The url in our example is

/toscawidgets/resources/__callback__/serverside_widget/test_callback

Pretty straightforward - so why not hardcode them? Because that would fail if the widget is registered in an application that is not mounted as root! So, whenever you need a callback-url, use :method:`tw.core.server.ServerSideCallbackMixin.url_for_callback`.

Callback security

An important topic is how to prevent unauthorized access to serverside-callbacks.

To govern this, there are two hooks:

  • a global one that is passed on middleware-creation. It defaults to denying all access.
  • a per-widget-one that is a parameter to the widget called callback_authorization.

In both cases the authorization handler is a function that has the following signature:

(instancemethodf, webob.Request) -> webob.Response

So it takes the callback in question and the current request, and must return a response which is used to convey the results of checking the authorization via it’s status:

  • 200 if it’s ok for the current request to call the callback.
  • everything else is reported back as result of the request.

As you can see in the example, the authorization is conceived as subclassed method. This works as well as passing a parameter to the constructor, it’s up to you what you prefer.

And there are two built-in checkers, always_allow and always_deny that serve as model how to implement authorization checkers.