You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Eric Prud'hommeaux <er...@w3.org> on 2001/08/24 20:27:02 UTC

appending to the content brigade

I'm implementing a content filter which wraps the content in multipart
mime. I can generate a separator, my data, separator, original payload
and separator. The problem is that if I just concatonate the brigades,
I end up with an EOS before my final separator. This works (the final
bucket gets sent down the wire) but, if I understand the meaning of
EOS I won't get into heaven this way. I figured I could copy all but
the EOS from the original payload to the new brigade:

-    APR_BRIGADE_CONCAT(bsend, bPayload);
+    APR_BRIGADE_FOREACH(e, bPayload) {
+	if (!APR_BUCKET_IS_EOS(e))
+	    APR_BRIGADE_INSERT_TAIL(bsend, e);
+    }
#    ...
     apr_brigade_destroy(bPayload);
     e = apr_bucket_pool_create(boundary, boundryLength, r->pool);
     APR_BRIGADE_INSERT_TAIL(bsend, e);
     e = apr_bucket_eos_create();
     APR_BRIGADE_INSERT_TAIL(bsend, e);


This corrupted bsend on the first insert. I hadn't even gotten as far
as destroying bPayload, which I expected to be a bad idea. The
byterange filter uses some function calls to copy pieces of the
brigade. Is this the sanctioned approach?

-- 
-eric

(eric@w3.org)
Feel free to forward this message to any list for any purpose other than
email address distribution.

Re: appending to the content brigade

Posted by Eric Prud'hommeaux <er...@w3.org>.
On Fri, Aug 24, 2001 at 06:54:31PM -0400, Cliff Woolley wrote:
> On Fri, 24 Aug 2001, Greg Stein wrote:
> 
> > Okay... let's back up. I believe that some poor advice may have been given.
> >
> > For now, let's assume that bPayload *does* have the entire content. Given
> > that, there is *no* reason to copy the darn payload into a new brigade.
> >
> > The whole *idea* of the filters and the brigades is that you modify the
> > brigade passed to you, and then pass it on down. Thus, your operation is
> > like this:
> 
> Oh, duh, yeah of course.  Thanks for clearing the air.  =-)  I wasn't
> thinking from the beginning.  I started with the assumption that we had
> two brigades and we wanted to combine them or twiddle them in some way
> (which filters do all the time), missing the obvious fact that in THIS
> case you don't have to do that.  Tack some stuff onto the beginning, pass
> it down.  That's it.  Well, anyway, now you (Eric) know what the rules are
> if you have a more complicated case.

Yeah, I figured I could be more economical and have been shamed into
it. I wrote up some docs for filters and buckets (attached) but didn't
work this lesson into it. Take a look and see if you think they are
useful. I haven't put a lot of polish on them, but they should be a
good conversation starter. The documents are written to be peers of
http://httpd.apache.org/docs-2.0/developer/hooks.html.


> Thanks Greg.  :)

And thanks from me.
-- 
-eric

(eric@w3.org)
Feel free to forward this message to any list for any purpose other than
email address distribution.

Re: appending to the content brigade

Posted by Eric Prud'hommeaux <er...@w3.org>.
On Fri, Aug 24, 2001 at 02:32:32PM -0700, Greg Stein wrote:
> On Fri, Aug 24, 2001 at 04:00:46PM -0400, Cliff Woolley wrote:
> > On Fri, 24 Aug 2001, Eric Prud'hommeaux wrote:
> >...
> > > > Note that if the last bucket is not eos, depending on how your filter is
> > > > supposed to work that might be an indication that you need to stash the
> > > > brigade away until you DO get an eos, because the content you're wrapping
> 
> Okay... let's back up. I believe that some poor advice may have been given.
> 
> For now, let's assume that bPayload *does* have the entire content. Given
> that, there is *no* reason to copy the darn payload into a new brigade.
> 
> The whole *idea* of the filters and the brigades is that you modify the
> brigade passed to you, and then pass it on down. Thus, your operation is
> like this:
> 
> #define BOUNDARY "---- boundary ----"
> 
>   /* insert into head in reverse order (like pushing a stack) */
>   bucket = apr_bucket_immortal_create(BOUNDARY, sizeof(BOUNDARY)-1);
>   APR_BRIGADE_INSERT_HEAD(bPayload, bucket); /* boundary of data/payload */
> 
>   my_data_bucket = ...
>   APR_BRIGADE_INSERT_HEAD(bPayload, my_data_bucket);

I beleive there are good reasons for this data being a brigade. It may
come from disk, *allloc, or shared memory. Also, this generator can
benifit from convenient apr_brigade_write functions.

Is there a good way to write into a list of buckets rather than a
brigade? This would permit a splice
  APR_BUCKET_INSERT_HEAD(bPayload, myBuckets)
which would, I believe, be more efficient than doing the maintainance
to copy the buckets from myBrigade.

In a survey (grep), of uses of APR_BRIGADE_INSERT_HEAD
APR_BRIGADE_INSERT_TAIL APR_BUCKET_INSERT_BEFORE
APR_BUCKET_INSERT_AFTER. All were inserting a single bucket that was
either newly created or newly removed from another brigade. Given
this, I would add a hint to buckets.html that said that there were no
known practical uses of lists of buckets outside of a brigade - the
right way to manage an arbitrary set of buckets is with a brigade.

>   bucket = apr_bucket_immortal_create(BOUNDARY, sizeof(BOUNDARY)-1);
>   APR_BRIGADE_INSERT_HEAD(bPayload, bucket); /* first boundary */
> 
>   /* insert a boundary before the EOS bucket */
>   eos = APR_BRIGADE_LAST(bPayload);
>   APR_BRIGADE_INSERT_BEFORE(eos, bucket);
> 
>   return ap_pass_brigade(f->next, bPayload);
> 
> 
> Construction of the boundary is probably dynamic, and I'd bet you have much
> more data to insert. But the concept demonstrated above is how you "should"
> deal with the passing brigade, and how to pass it along.
> 
> Copying the brigade is *not* the answer, if you can avoid it.

Assuming my analysis above is correct I have brigade of payload and a
brigade of metadata. The headers and associated crap can be
{pre,post}pended to either. Now, do I stick them together
(APR_BRIGADE_CONCAT) or make multiple calls to ap_pass_brigade? I
assume the CONCAT is more efficient.

> Now... the second point is that, as Cliff said, you may not get the EOS in
> your brigade. Not a problem. On the *first* invocation of your filter,
> insert the stuff up front and pass it along. (record the "first" in a
> filter-specific context structure). When you see an EOS bucket at the end of
> the brigade (using APR_BRIGADE_LAST and APR_BUCKET_IS_EOS), then you insert
> the final boundary.

Yeah, I removed my single-call dependency on my filter. I should put
an example of using f->ctx in filters.html.

> If your boundary is dynamically constructed, the store the string into your
> context (to reuse on the various invocations of your filter). When you go to
> insert it, wrap it in a POOL bucket, using whatever pool your boundary was
> allocated in (to ensure the lifetimes match).

roger, done.

> >...
> > > Oops, I forgot to include the code snippet to which I was
> > > referring. From ap_byterange_filter:
> >...
> > Hooo, that's ugly.  You don't have to do that for what you want.  The
> > reason it's done here is that byteranges might overlap within the same
> > request, so rather than just moving buckets to where they need to go, it
> > has to make copies of all the requisite buckets for each of the ranges.
> 
> To be clearer, the client might request the same range multiple times. Thus,
> we need multiple copies of the buckets.
> 
> [ and yes: we could optimize the thing to avoid copying by analyzing the
>   ranges up front, determining where overlaps may occur, and only copy those
>   portions; thus using the original brigade as much as possible, without any
>   copies.
>   
>   that said: many buckets are very efficient at copying because of the whole
>   refcount and sub-bucket stuff. ]
> 
> >...
> > heap) and then THAT can be copied.  Actually, the block here is broken
> > because it puts str into two buckets at a time, which will double-free
> > str when the second bucket gets destroyed.  It should read:
> > 
> >     if ((rv = apr_bucket_copy(ec, &foo)) != APR_SUCCESS) {
> >         if (rv == APR_ENOTIMPL) {
> >             /* morph to a copyable bucket by reading */
> >             apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
> >             apr_bucket_copy(ec, &foo);
> 
> Or, it could simply pass 1 to heap_create to COPY the data passed. That *is*
> what that parameter is for :-)
> 
> > >                 foo = apr_bucket_heap_create(str, len, 0, NULL);
> 
> becomes:
> 
> > >                 foo = apr_bucket_heap_create(str, len, 1, NULL);
> 
> >...
> > That way, ec and foo end up as two apr_buckets of type heap that share a
> > single apr_bucket_heap structure which points to str with a refcount of
> > 2, rather than having two entirely separate apr_bucket/apr_bucket_heap
> > pairs both of which point to str and each of which has a refcount of
> > 1.

This brings up a meta question I have: Assuming you want the same
string in multiple places in your response, may one have a pool bucket
pointing at the same pool memory as is used in a header as long as the
lifetimes are the same? Would one do this by using the request pool
for the bucket? Would it matter if the pools were different as long as
they were both reaped at the end of the request? Probably a bad idea,
but I'm just curious.

-- 
-eric

(eric@w3.org)
Feel free to forward this message to any list for any purpose other than
email address distribution.

Re: appending to the content brigade

Posted by Cliff Woolley <cl...@yahoo.com>.
On Fri, 24 Aug 2001, Greg Stein wrote:

> Okay... let's back up. I believe that some poor advice may have been given.
>
> For now, let's assume that bPayload *does* have the entire content. Given
> that, there is *no* reason to copy the darn payload into a new brigade.
>
> The whole *idea* of the filters and the brigades is that you modify the
> brigade passed to you, and then pass it on down. Thus, your operation is
> like this:

Oh, duh, yeah of course.  Thanks for clearing the air.  =-)  I wasn't
thinking from the beginning.  I started with the assumption that we had
two brigades and we wanted to combine them or twiddle them in some way
(which filters do all the time), missing the obvious fact that in THIS
case you don't have to do that.  Tack some stuff onto the beginning, pass
it down.  That's it.  Well, anyway, now you (Eric) know what the rules are
if you have a more complicated case.

Thanks Greg.  :)

--Cliff

--------------------------------------------------------------
   Cliff Woolley
   cliffwoolley@yahoo.com
   Charlottesville, VA



Re: appending to the content brigade

Posted by Greg Stein <gs...@lyra.org>.
On Fri, Aug 24, 2001 at 04:00:46PM -0400, Cliff Woolley wrote:
> On Fri, 24 Aug 2001, Eric Prud'hommeaux wrote:
>...
> > > Note that if the last bucket is not eos, depending on how your filter is
> > > supposed to work that might be an indication that you need to stash the
> > > brigade away until you DO get an eos, because the content you're wrapping

Okay... let's back up. I believe that some poor advice may have been given.

For now, let's assume that bPayload *does* have the entire content. Given
that, there is *no* reason to copy the darn payload into a new brigade.

The whole *idea* of the filters and the brigades is that you modify the
brigade passed to you, and then pass it on down. Thus, your operation is
like this:

#define BOUNDARY "---- boundary ----"

  /* insert into head in reverse order (like pushing a stack) */
  bucket = apr_bucket_immortal_create(BOUNDARY, sizeof(BOUNDARY)-1);
  APR_BRIGADE_INSERT_HEAD(bPayload, bucket); /* boundary of data/payload */

  my_data_bucket = ...
  APR_BRIGADE_INSERT_HEAD(bPayload, my_data_bucket);

  bucket = apr_bucket_immortal_create(BOUNDARY, sizeof(BOUNDARY)-1);
  APR_BRIGADE_INSERT_HEAD(bPayload, bucket); /* first boundary */

  /* insert a boundary before the EOS bucket */
  eos = APR_BRIGADE_LAST(bPayload);
  APR_BRIGADE_INSERT_BEFORE(eos, bucket);

  return ap_pass_brigade(f->next, bPayload);


Construction of the boundary is probably dynamic, and I'd bet you have much
more data to insert. But the concept demonstrated above is how you "should"
deal with the passing brigade, and how to pass it along.

Copying the brigade is *not* the answer, if you can avoid it.

Now... the second point is that, as Cliff said, you may not get the EOS in
your brigade. Not a problem. On the *first* invocation of your filter,
insert the stuff up front and pass it along. (record the "first" in a
filter-specific context structure). When you see an EOS bucket at the end of
the brigade (using APR_BRIGADE_LAST and APR_BUCKET_IS_EOS), then you insert
the final boundary.

If your boundary is dynamically constructed, the store the string into your
context (to reuse on the various invocations of your filter). When you go to
insert it, wrap it in a POOL bucket, using whatever pool your boundary was
allocated in (to ensure the lifetimes match).

>...
> > Oops, I forgot to include the code snippet to which I was
> > referring. From ap_byterange_filter:
>...
> Hooo, that's ugly.  You don't have to do that for what you want.  The
> reason it's done here is that byteranges might overlap within the same
> request, so rather than just moving buckets to where they need to go, it
> has to make copies of all the requisite buckets for each of the ranges.

To be clearer, the client might request the same range multiple times. Thus,
we need multiple copies of the buckets.

[ and yes: we could optimize the thing to avoid copying by analyzing the
  ranges up front, determining where overlaps may occur, and only copy those
  portions; thus using the original brigade as much as possible, without any
  copies.
  
  that said: many buckets are very efficient at copying because of the whole
  refcount and sub-bucket stuff. ]

>...
> heap) and then THAT can be copied.  Actually, the block here is broken
> because it puts str into two buckets at a time, which will double-free
> str when the second bucket gets destroyed.  It should read:
> 
>     if ((rv = apr_bucket_copy(ec, &foo)) != APR_SUCCESS) {
>         if (rv == APR_ENOTIMPL) {
>             /* morph to a copyable bucket by reading */
>             apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
>             apr_bucket_copy(ec, &foo);

Or, it could simply pass 1 to heap_create to COPY the data passed. That *is*
what that parameter is for :-)

> >                 foo = apr_bucket_heap_create(str, len, 0, NULL);

becomes:

> >                 foo = apr_bucket_heap_create(str, len, 1, NULL);

>...
> That way, ec and foo end up as two apr_buckets of type heap that share a
> single apr_bucket_heap structure which points to str with a refcount of
> 2, rather than having two entirely separate apr_bucket/apr_bucket_heap
> pairs both of which point to str and each of which has a refcount of
> 1.

But that is the cooler solution :-)

Cheers,
-g

-- 
Greg Stein, http://www.lyra.org/

Re: appending to the content brigade

Posted by Cliff Woolley <cl...@yahoo.com>.
On Fri, 24 Aug 2001, Eric Prud'hommeaux wrote:

> > > This works (the final bucket gets sent down the wire) but, if I
> > > understand the meaning of EOS I won't get into heaven this way.
> >
> > Yeah, that's definitely bad news.
>
> Is THE RULE that one should assume the end of data if one receives
> an EOS even if there are more buckets in the brigade?

The rule is that EOS is _only_ allowed to exist at the end of the brigade.
If there are buckets after it, it's broken.  The reason for that is that
it allows filters to just test the last bucket in the brigade for EOS
rather than having to test EVERY brigade in the bucket for EOS.  Besides,
by definition EOS means "no other buckets are coming, ever" so there's
never a (valid) reason to have any buckets afterward.

> > Note that if the last bucket is not eos, depending on how your filter is
> > supposed to work that might be an indication that you need to stash the
> > brigade away until you DO get an eos, because the content you're wrapping
> > with your mime headers hasn't all arrived yet.  Alternatively, you can
> > send down what you have now but a lack of eos means you shouldn't tack on
> > your boundary stuff yet.
>
> Is a filter guaranteed to get an EOS?

Eventually, yes (only once per request!).  But there might be many
intervening calls down the filter stack with partial data before that
final call which does have an EOS.

> Oops, I forgot to include the code snippet to which I was
> referring. From ap_byterange_filter:
>
>             if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
>                 apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
>                 foo = apr_bucket_heap_create(str, len, 0, NULL);
>             }
>
> I assume there's no reason to do this if it doesn't accompany some
> fragmentation of ec.

Hooo, that's ugly.  You don't have to do that for what you want.  The
reason it's done here is that byteranges might overlap within the same
request, so rather than just moving buckets to where they need to go, it
has to make copies of all the requisite buckets for each of the ranges.
Some bucket types might not implement copy (Only buckets whose data can
only be read once (pipe and socket) don't implement it), so those that
don't need to be read (thereby morphing them into a copyable bucket eg
heap) and then THAT can be copied.  Actually, the block here is broken
because it puts str into two buckets at a time, which will double-free
str when the second bucket gets destroyed.  It should read:

    if ((rv = apr_bucket_copy(ec, &foo)) != APR_SUCCESS) {
        if (rv == APR_ENOTIMPL) {
            /* morph to a copyable bucket by reading */
            apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
            apr_bucket_copy(ec, &foo);
        }
        else {
            return rv;
        }
    }

That way, ec and foo end up as two apr_buckets of type heap that share a
single apr_bucket_heap structure which points to str with a refcount of
2, rather than having two entirely separate apr_bucket/apr_bucket_heap
pairs both of which point to str and each of which has a refcount of
1.  I assume we're currently not hitting this bug because it's
probably rare to have a byterange request on a resource that gets sent
down the chain as a pipe or socket bucket.  Anyway, it's a bug... I'll go
fix it.


> > Hope this helps!  Let me know if you have more questions.
>
> Quite well, thank you kindly. I'm hacking up some docs to pass your
> help on. apr_buckets.h has func descriptions but I'd like to make a
> sybling of _Hook_Functions_ [1] with THE RULES and some common
> recipies.

Great!  I've been meaning to write up a "buckets 101" tutorial... maybe
this will give me a kick-start.  =-)

--Cliff


--------------------------------------------------------------
   Cliff Woolley
   cliffwoolley@yahoo.com
   Charlottesville, VA




Re: appending to the content brigade

Posted by Eric Prud'hommeaux <er...@w3.org>.
On Fri, Aug 24, 2001 at 02:43:28PM -0400, Cliff Woolley wrote:
> On Fri, 24 Aug 2001, Eric Prud'hommeaux wrote:
> 
> > I'm implementing a content filter which wraps the content in multipart
> > mime. I can generate a separator, my data, separator, original payload
> > and separator. The problem is that if I just concatonate the brigades,
> > I end up with an EOS before my final separator.
> 
> All you have to do is delete the EOS bucket and then do your concat.
> That's a constant time operation as opposed to a foreach loop which is a
> linear time operation.

I figured there was some way to leverage off the end pointer but had
spaced on the existence of APR_BUCKET_REMOVE(e).

> > This works (the final bucket gets sent down the wire) but, if I
> > understand the meaning of EOS I won't get into heaven this way.
> 
> Yeah, that's definitely bad news.

Is THE RULE that one should assume the end of data if one receives
an EOS even if there are more buckets in the brigade?

> > I figured I could copy all but the EOS from the original payload to
> > the new brigade:
> >
> > -    APR_BRIGADE_CONCAT(bsend, bPayload);
> > +    APR_BRIGADE_FOREACH(e, bPayload) {
> > +	if (!APR_BUCKET_IS_EOS(e))
> > +	    APR_BRIGADE_INSERT_TAIL(bsend, e);
> > +    }
> 
> This is broken because you're modifying the list pointers of e, which
> APR_BRIGADE_FOREACH() doesn't support.  The foreach macro uses e->next to
> determine what the next bucket in bPayload is at the end of each
> iteration, so you have to treat e as essentially const.  What you'd want
> to do is something more like this:
> 
>     while (!APR_BRIGADE_EMPTY(bPayload)) {
>         e = APR_BRIGADE_FIRST(bPayload);
>         if (!APR_BUCKET_IS_EOS(e)) {
>             APR_BUCKET_REMOVE(e);
>             APR_BRIGADE_INSERT_TAIL(bsend, e);
>         }
>     }
> 
> You have to do the remove before inserting into bsend so that the bPayload
> ring remains consistent.
> 
> But like I said, this is a linear time operation, when you can do it in
> constant time, like this:
> 
>     eos = APR_BRIGADE_LAST(bPayload);
>     if (APR_BUCKET_IS_EOS(eos)) {
>         APR_BUCKET_REMOVE(eos);
>     }
>     else {
>         eos = apr_bucket_eos_create();
>     }
> 
>     APR_BRIGADE_CONCAT(bsend, bPayload);
>     /* apr_brigade_destroy(bPayload); */
> 
>     e = apr_bucket_pool_create(boundary, boundryLength, r->pool);
>     APR_BRIGADE_INSERT_TAIL(bsend, e);
>     APR_BRIGADE_INSERT_TAIL(bsend, eos);

worky, thank you kindly.

> Note that if the last bucket is not eos, depending on how your filter is
> supposed to work that might be an indication that you need to stash the
> brigade away until you DO get an eos, because the content you're wrapping
> with your mime headers hasn't all arrived yet.  Alternatively, you can
> send down what you have now but a lack of eos means you shouldn't tack on
> your boundary stuff yet.

Is a filter guaranteed to get an EOS?

> > The byterange filter uses some function calls to
> > copy pieces of the brigade. Is this the sanctioned approach?
> 
> I assume you're talking about apr_brigade_partition() or
> apr_brigade_split()... you definitely don't need partition here (which
> splits the brigade at a given byte offset).  apr_brigade_split() is
> useful when you want to send part of the data you have in a single brigade
> and hang onto the rest, but I doubt you'll need that here either.
> There are plenty of uses of apr_brigade_split() in the core filters that
> you can look at as an example.

Oops, I forgot to include the code snippet to which I was
referring. From ap_byterange_filter:

            if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) {
                apr_bucket_read(ec, &str, &len, APR_BLOCK_READ);
                foo = apr_bucket_heap_create(str, len, 0, NULL);
            }

I assume there's no reason to do this if it doesn't accompany some
fragmentation of ec.

> 
> Hope this helps!  Let me know if you have more questions.

Quite well, thank you kindly. I'm hacking up some docs to pass your
help on. apr_buckets.h has func descriptions but I'd like to make a
sybling of _Hook_Functions_ [1] with THE RULES and some common
recipies.

[1] http://httpd.apache.org/docs-2.0/developer/hooks.html

-- 
-eric

(eric@w3.org)
Feel free to forward this message to any list for any purpose other than
email address distribution.

Re: appending to the content brigade

Posted by Cliff Woolley <cl...@yahoo.com>.
On Fri, 24 Aug 2001, Eric Prud'hommeaux wrote:

> I'm implementing a content filter which wraps the content in multipart
> mime. I can generate a separator, my data, separator, original payload
> and separator. The problem is that if I just concatonate the brigades,
> I end up with an EOS before my final separator.

All you have to do is delete the EOS bucket and then do your concat.
That's a constant time operation as opposed to a foreach loop which is a
linear time operation.

> This works (the final bucket gets sent down the wire) but, if I
> understand the meaning of EOS I won't get into heaven this way.

Yeah, that's definitely bad news.

> I figured I could copy all but the EOS from the original payload to
> the new brigade:
>
> -    APR_BRIGADE_CONCAT(bsend, bPayload);
> +    APR_BRIGADE_FOREACH(e, bPayload) {
> +	if (!APR_BUCKET_IS_EOS(e))
> +	    APR_BRIGADE_INSERT_TAIL(bsend, e);
> +    }

This is broken because you're modifying the list pointers of e, which
APR_BRIGADE_FOREACH() doesn't support.  The foreach macro uses e->next to
determine what the next bucket in bPayload is at the end of each
iteration, so you have to treat e as essentially const.  What you'd want
to do is something more like this:

    while (!APR_BRIGADE_EMPTY(bPayload)) {
        e = APR_BRIGADE_FIRST(bPayload);
        if (!APR_BUCKET_IS_EOS(e)) {
            APR_BUCKET_REMOVE(e);
            APR_BRIGADE_INSERT_TAIL(bsend, e);
        }
    }

You have to do the remove before inserting into bsend so that the bPayload
ring remains consistent.

But like I said, this is a linear time operation, when you can do it in
constant time, like this:

    eos = APR_BRIGADE_LAST(bPayload);
    if (APR_BUCKET_IS_EOS(eos)) {
        APR_BUCKET_REMOVE(eos);
    }
    else {
        eos = apr_bucket_eos_create();
    }

    APR_BRIGADE_CONCAT(bsend, bPayload);
    /* apr_brigade_destroy(bPayload); */

    e = apr_bucket_pool_create(boundary, boundryLength, r->pool);
    APR_BRIGADE_INSERT_TAIL(bsend, e);
    APR_BRIGADE_INSERT_TAIL(bsend, eos);

Note that if the last bucket is not eos, depending on how your filter is
supposed to work that might be an indication that you need to stash the
brigade away until you DO get an eos, because the content you're wrapping
with your mime headers hasn't all arrived yet.  Alternatively, you can
send down what you have now but a lack of eos means you shouldn't tack on
your boundary stuff yet.

> I hadn't even gotten as far as destroying bPayload, which I expected
> to be a bad idea.

It's okay.  It's actually totally unnecessary, though (which is why it's
commented out above, because bPayload is empty at that point and the
brigade itself will get cleaned up when the pool goes away.

> The byterange filter uses some function calls to
> copy pieces of the brigade. Is this the sanctioned approach?

I assume you're talking about apr_brigade_partition() or
apr_brigade_split()... you definitely don't need partition here (which
splits the brigade at a given byte offset).  apr_brigade_split() is
useful when you want to send part of the data you have in a single brigade
and hang onto the rest, but I doubt you'll need that here either.
There are plenty of uses of apr_brigade_split() in the core filters that
you can look at as an example.

Hope this helps!  Let me know if you have more questions.

--Cliff

--------------------------------------------------------------
   Cliff Woolley
   cliffwoolley@yahoo.com
   Charlottesville, VA