[Twisted-web] streaming request (was: status of Twisted Web
and Web 2)
Jean-Paul Calderone
exarkun at divmod.com
Thu Mar 6 09:51:00 EST 2008
On Thu, 6 Mar 2008 07:15:53 -0700, Andrew McNabb <amcnabb at mcnabbs.org> wrote:
>On Wed, Mar 05, 2008 at 10:30:29PM -0500, Jean-Paul Calderone wrote:
>>
>> To clarify, this is about calling into application code in a web server
>> once each time some new bytes from a request body are received from the
>> client, right?
>
>Exactly.
>
>> Deferred.callback explicitly checks to see if you're giving it a Deferred
>> and raises the TypeError. The idea here is that most of the time, you're
>> probably doing this by accident, not intentionally. Clearly it wasn't an
>> accident in your case.
>
>I definitely was not doing it by accident. :) Is this really a common
>mistake?
I have absolutely no statistics on the frequency of this error. I suspect
that it isn't, but I may be wrong. The check was added years ago, presumably
by someone who thought it was/would be.
>
>> The way to actually get this behavior is to chain the Deferreds
>> together. Instead of a.callback(b), which I assume you was to send
>> the result of b to the callbacks of a when they became available, you
>> a.chainDeferred(b), which is really just a way to say
>>
>> a.addCallbacks(b.callback, b.errback)
>>
>> However, I don't think this is generally the approach to take.
>
>This actually isn't equivalent to what I was trying to do. Think of my
>approach this way. As a user, you call:
>
>d = streaming_download(url)
>
>def got_some_data(value):
> new_data, d = value
> # add callback for the next time data get sent:
> d.addCallback(got_some_data)
> # do stuff with new_data
>
>d.addCallback(got_some_data)
>
Ah. So the case that doesn't work is when the result of the Deferred is
really a new, perhaps totally unrelated, Deferred that someone wants. I
can see how that would be useful. I think I've actually encountered the
case before myself. It might be worth complaining about this misfeature
on the other list (twisted-python).
>
>>> My second attempt was to create a new deferred each time data arrived
>>> and to copy the callback list from the previous deferred. This worked,
>>> but it felt dirty, so I got rid of it.
>
>Actually, after looking at my code again, it turns out that what I am
>doing is actually a mix of attempt 2 and attempt 3. This is the code
>that copies the deferred:
>
> newdef = defer.Deferred()
> newdef.callbacks = list(self.deferred.callbacks)
> newdef.callback(False)
>
*cries*
You're not supposed to read or write the `callbacks´ attribute like that.
>
>>> My third and final attempt was to tie the downloader in to my
>>> application. Every time data arrive, the downloader sends it to the
>>> object that needs it. This isn't a very general solution, but it is
>>> very short and readable.
>>
>> This I actually like, and sounds a bit like what I would implement someday
>> if I were to implement something. Of course, it should be general-purpose
>> so it works for other applications. The idea I have is to allow resources
>> to optionally handle these new data-arrived events. It should be possible
>> for a resource to signal that it wants data as it is received, perhaps by
>> declaring that implements a new interface (eg, IStreamingRequestHandler or
>> something) with a method that is called each time data is received in the
>> request body. Resources which don't implement this interface will get the
>> old behavior of having the request body buffered and delivered all at once,
>> but resources which want streaming can get it.
>>
>> Does that make sense?
>
>That makes sense, but in the streaming case I really think that you need
>to call back every time data comes in.
Yes, definitely. `IStreamingRequestHandler´ would have some method like
`someMoreRequestDataReceived´ and it would be called as many times as
necessary for a single request until the entire request body has been
delivered.
>
>> If the above described approach sounds sensible and you want to take a
>> crack at it, I'd really appreciate it. The best thing to do with the
>> result is attach it to a ticket in the tracker, probably #288
>>
>> http://twistedmatrix.com/trac/ticket/288
>>
>> If not, I'd still like to see the code and make sure the ideas I have will
>> also satisfy the use-case it represents.
>
>I'm certainly willing to help, although I think we have some things to
>clean up.
>
Jean-Paul
More information about the Twisted-web
mailing list