[Twisted-Python] timeout using LivePage

Donovan Preston dp at twistedmatrix.com
Mon Aug 25 19:45:20 EDT 2003

On Monday, August 25, 2003, at 1:30 PM, Sean Gillies wrote:

> Hi,
> Am very new to Twisted but am quickly finding that I like
> it.  All my questions and comments involve Twisted 1.0.6
> in combo with Python 2.3 on OS X.

Great! Always glad to have more users giving me feedback.

If you are going to be using LivePage, you should probably
track CVS instead of using a release, since LivePage is the
one major area of woven I am still doing major work on.

First, I want to clarify some terminology. LivePage is designed
to allow two things:

Client-To-Server Events: A JavaScript event handler in the
browser is routed to the Twisted server and causes some code
to run. This uses a channel I am calling the "InputConduit"

Server-To-Client Events: Some event happened on the server
(For example, new mail arrived, another player entered the room)
and the server wants to "Push" some new HTML to the client.
This uses a channel I am calling the "OutputConduit"

> I've run into a problem while making a rough .rpy prototype of
> a mapping application (ala MapQuest) using the U of Minnesota
> MapServer's Python interface.
> I will need to have server-side handlers for javascript events
> to allow map zooming, panning, &c and so have been experimenting
> with LivePage.  I intend that the code should render a map image,
> and upon clicking the image, we zoom into the map (changing model
> data), and redraw the map image.  Unfortunately, browsers are
> unable to load the
> mapserv.rpy/?woven_hookupOutputConduitToThisFrame=1
> URL that is generated by my instance of LivePage, and so the map
> is never redrawn as it should.

There are actually two unrelated issues here. The first is that the
current implementation of LivePage in the Twisted CVS is geared
towards the NewReality web client (NewReality is a multiuser text
environment (game) written in Twisted) and thus the ability for
the server to send events to the client is pretty important.
You are connected and logged in using the web client, and
someone else enters the room -- you want the web browser to
display a notice about someone entering the room without having
to refresh the browser manually or set up continual reloading.

The output conduit is implemented in certain browsers (those
lacking Flash and LiveConnect) by embedding an <iframe>
tag whose src="?woven_hookupOutputConduitToThisFrame=1"
in the page. When the browser attempts to load this iframe, it
makes a request to the server, and twisted notices that the
browser "wants to hook up the output conduit to this frame",
and *never finishes sending this page to the client*. The
Twisted server just holds this connection open forever, so
that it can write data to the client on demand in response
to events on the server, and this is why it appears your
browser is unable to load this URL.

Since with your application it sounds like you only want
client-to-server events, you need to do a little bit of
hacking to prevent Woven from including the output
conduit HTML in your pages. This will get easier in future
releases of Twisted; I just haven't had time to enumerate
the possible ways people will want to use LivePage and
come up with an easy way for the programmer to specify
the features they want.

If you look at the HTML fragment woven includes in your
page when you specify the view directive "webConduitGlue"
you will see it includes the following parts:

    <script src="WebConduit2_js" language="javascript"></script>
    <iframe src="input_html"
         style="width: 0; height: 0" id="woven_inputConduit"></iframe>
         style="width: 100%" id="woven_outputConduit"></iframe>

Simply replace the view="webConduitGlue" node with the above
fragment, sans the output conduit frame:

    <script src="WebConduit2_js" language="javascript"></script>
    <iframe src="input_html"
         style="width: 0; height: 0" id="woven_inputConduit"></iframe>

After you have done this, your pages will actually appear to fully
load and you should feel happier.

> The model data does get changed
> successfully, and when I manually reload the page, I do see
> the zoomed map image as I expect.

The second issue is that you are not notifying your models
properly to tell them that they have changed. When a
client-to-server event is sent from the browser to the server,
the current version of Woven only sends the portions of the
page which have actually changed, not the entire page,
back to the browser. Again, this is something I would like
the programmer to be able to control, because sometimes
the changes are extensive enough that it makes more sense
to send the entire page back.

Your redirect hack is not a terrible solution, actually. I have
used it before.

See below for an example of how you can make your current
controller code notify the model properly.

> What is this 'woven_hookupOutputConduitToThisFrame' all about?
> Do I need to set up another method to handle this URL?
> I know that it's not recommended to put so much logic in the .rpy
> file, but as I said, a quick and dirty prototype is what I'm after.
> Am attaching the source of mapserv.rpy here:
> --------------------------------------------------------------------
> import os
> import time
> from twisted.web.woven import page, widgets, controller, model
> from mapscript import *
> base_mapfile = 'navtech_std.map'
> # Get map object from the session data if we can
> class MapModel(model.MethodModel):
>     def wmfactory_mapobj(self, request):
>         session_obj = request.getSession()
>         try:
>             session_mapobj = session_obj.mapobj
>         except AttributeError:
>             session_mapobj = mapObj(base_mapfile)
>             session_obj.mapobj = session_mapobj
>         return session_mapobj
> # Widget for creating a temp map image and a tag linking to it
> class MapImage(widgets.Widget):
>     def setUp(self, request, node, data):
>         mapobj = data
>         node.tagName = "img"
>         imgobj = mapobj.draw()
>         tmp_file = '%s_%d_%d.%s' \
>                  % (mapobj.name, time.time(), os.getpid(),
>                     imgobj.format.extension )
>         imgobj.save(os.path.join('/tmp', tmp_file))
>         map_url = os.path.join('/tmp', tmp_file)
>         node.setAttribute('src', map_url)
>         node.setAttribute('height', str(mapobj.height))
>         node.setAttribute('width', str(mapobj.width))
>         node.setAttribute('border', '1')
> # Widget for displaying the current scale of the map
> class MapScale(widgets.Widget):
>     def setUp(self, request, node, data):
>         text = 'Scale: 1:%d' % (data.scale)
>         newNode = request.d.createTextNode(text)
>         node.appendChild(newNode)
> # Template, using the web conduit glue
> template = """<html>
>   <body>
>     <img model="mapobj" view="map_image" controller="map_ctrl"/>
>     <p model="mapobj" view="map_scale" />
>     <div view="webConduitGlue" />
>   </body>
> </html>
> """
> # Zoom into the map in the event of a javascript onclick
> class MyEventHandler(controller.Controller):
>     def handle(self, request):
>         self.view.addEventHandler("onclick", self.onClick)
>     def onClick(self, request, widget):
>         # Get session map object (layers and spatial extents)
>         session_obj = request.getSession()
>         try:
>             session_mapobj = session_obj.mapobj
>         except AttributeError:
>             session_mapobj = mapObj(base_mapfile)
>             session_obj.mapobj = session_mapob
>         # zoom in on the map
>         w = session_mapobj.width
>         h = session_mapobj.height
>         pt = pointObj()
>         pt.x, pt.y = w/2, h/2
>         session_mapobj.zoomPoint(2, pt, w, h, session_mapobj.extent, 
> None)
>         print session_mapobj.extent.minx, session_mapobj.extent.maxx
>         print self, "Zoomed!!!"

            # Tell the browser that the model has changed, and
            # any widgets which rely on this model will have to be
            # rerendered, and the HTML sent to the browser
	 widget.model.notify({'request': request})

            # Delete the next three lines
>         # There is a better way to redraw the map, certainly
>         request.redirect('http://localhost:8084/mapserv.rpy/')
>         request.finish()
> # Page class
> class MyPage(page.LivePage):
>     def wcfactory_map_ctrl(self, request, node, model):
>         return MyEventHandler(model)
> # Resource
> resource = MyPage(MapModel(), template=template)
> resource.setSubviewFactory("map_image", MapImage)
> resource.setSubviewFactory("map_scale", MapScale)
> -----------------------------------------------------------------------
> looking forward to any insights into LivePage and also looking forward
> to getting enough experience with Twisted that I can begin to 
> contribute
> to the list discussions.

By the way, addEventHandler takes additional arguments
which are javascript strings which will be evaluated within the
context of the browser, and the results will be sent as additional
arguments to the server-side event handler.

To take advantage of this, you have to know a bit about
JavaScript, but for example, if you know about the IE global
"event" object, you could use it to pass the current x, y
position of the mouse to the server with something like

self.view.addEventHandler("onclick", self.onClick, 'event.x', 'event.y')

Then defining the event handler like this:

def addEventHandler(self, request, widget, x, y):
	print "x, y", x, y

Of course, doing this means knowing enough about javascript to do
this properly, but those are problems with JavaScript and not Woven.
I think Mozilla has different semantics for getting at the event object
which may require changes in WebConduit2_js. I would consider it a
bug if changes are required.

I hope this helps!


More information about the Twisted-Python mailing list