[Twisted-web] [nevow & deferreds] page rendering occurs before data is available after callback
Matthieu HUIN
matthieu.huin at wallix.com
Wed Dec 2 12:01:55 EST 2009
Greetings,
I need to render a page with Nevow containing data extracted from a
database. The query might take time so it is taken care of with
twisted.adbapi, and thus with a Deferred.
My render function returns this Deferred - to which some callbacks have
been added to format the data. All is well as long as the request is
fast enough for the Deferred to be dealt with quasi-synchronously. If
the query takes too much time, the render function simply returns the
Deferred, which is set at None, instead of its result value.
Here is an extract of the code :
-------------------------------------------------------------------------
class Page(rend.Page):
addSlash = True
buffered = True
docFactory = loaders.xmlfile(util.sibpath(__file__, 'rest.xhtml'))
def renderHTTP(self, ctx):
"""Override render HTTP to handle authentication.
We override renderHTTP to ensure that nothing has been sent to
be able to change error code.
"""
self.authorized = True
request = inevow.IRequest(ctx)
# Which kind of request ?
if request.method == "POST":
# It may be an overloaded POST, check for _method
if ctx.arg("_method") in ["PUT", "DELETE"]:
request.method = ctx.arg("_method")
del request.args["_method"]
# Is the user authorized?
username, password = request.getUser(), request.getPassword()
d = defer.maybeDeferred(Authenticate().authenticate, username,
password)
# If not authenticated, turn it into a failure
d.addCallback(lambda x: x or
failure.Failure(Unauthenticated("Incorrect username or password")))
d.addCallback(lambda x: Authorize().authorize(username,
(request.method,
inevow.ICurrentSegments(ctx))))
# If not authorized, turn it into a failure
d.addCallback(lambda x: x or failure.Failure(Unauthenticated("No
rights to access this resource")))
# Add privilege info - /!\ possible race condition here ?
d.addCallback(lambda x : privileger(x,ctx) )
# Trap any authentication error
d.addErrback(lambda x: x.trap(Unauthenticated) and
self.render_ask_authentication(ctx))
# Back to normal rendering
d.addCallback(lambda x: rend.Page.renderHTTP(self, ctx))
return d
def render_PUT(self, ctx, data):
"""Handle a query"""
def unsuccessful_results(failure, ctx):
"""Render an error message because of unsucessful results"""
inevow.IRequest(ctx).setResponseCode(http.BAD_REQUEST)
return T.invisible["While processing the query, we get this
error:",
failure]
def successful_results(self, results, query):
"""Render successful results.
This function will store the results and link back to the
resource containing chunk of them
"""
# We store results
SearchEngineResource.serial += 1
if self.original not in SearchEngineResource.results:
SearchEngineResource.results[self.original] = {}
SearchEngineResource.results[self.original][SearchEngineResource.serial]
= [time.time(),
query,
results]
# Display query results
return T.p["The query was successful. ",
"There are ", T.span(_class="cardinal")[
len(results)
], " result(s). You need to ", T.a(href="%d/" %
SearchEngineResource.serial)[
"fetch them"
], "."
]
user = inevow.IRequest(ctx).getUser()
try:
# We get a deferred object
result = self.original.query(ctx.arg("value"), user)
except ValueError, e:
# ValueError is thrown synchronously
inevow.IRequest(ctx).setResponseCode(http.BAD_REQUEST)
return "While processing the query %r, we get this error: %
s" % (ctx.arg("value"),
e)
result.addCallbacks(lambda x: successful_results(self, x,
ctx.arg("value")),
errback=unsuccessful_results,
errbackArgs=(ctx,))
return result
----------------------------------------------------------------------------
The callbacks on the Deferred are executed flawlessly otherwise. The
rendering simply seems not to care whether the Deferred has been called
or not.
What can I do to correct this behavior ? Could it be because renderHTTP
returns a Deferred already ?
Matthieu
More information about the Twisted-web
mailing list