httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Ben Collins-Sussman <suss...@collab.net>
Subject [PATCH] stream mod_dav PROPFIND responses
Date Tue, 03 Jun 2003 21:24:19 GMT

mod_dav improvement: make dav_method_propfind stream its response,
rather than cache every <response> object and send the whole 207 at once.

This patch has already gone through one iteration of review by gstein.

Note:  this patch doesn't affect the mod_dav provider API at all.
Providers still return property results in text-buffers, but mod_dav
then streams them out immediately.

* mod_dav.h (dav_walker_ctx): add a brigade field and a scratchpool field.

* mod_dav.c (dav_send_one_response): new helper function to write a
  <DAV:response> into a brigade/filter.  this code has been factorized
  out of dav_send_multistatus.

  (dav_begin_multistatus): new factorized helper func; creates brigade
  and sends initial <multistatus> tag.

  (dav_send_multistatus): create brigade, call dav_begin_multistatus,
  and switch all ap_rputs calls to ap_fputs instead.  call
  dav_send_one_response when looping over response list.  use a
  subpool when iterating.

  (dav_method_propfind): initialize walker ctx's brigade.  initialize
  ctx's scratchpool as a subpool of r->pool.  Send a <multistatus> tag
  before calling the provider's walk() function, and a </multistatus>
  tag afterwards.

  (dav_stream_response): new function, originally based on
  dav_add_repsonse.  don't build linked list of responses in memory;
  just spew each response object into the brigade via
  dav_send_one_response().  take an incoming pool argument to do the
  temporary allocation and streaming.

  (dav_propfind_walker): pass ctx->scratchpool to dav_stream_response,
  and clear the pool when finished.


Index: modules/dav/main/mod_dav.c
===================================================================
RCS file: /home/cvspublic/httpd-2.0/modules/dav/main/mod_dav.c,v
retrieving revision 1.93
diff -u -r1.93 mod_dav.c
--- modules/dav/main/mod_dav.c	20 May 2003 23:09:00 -0000	1.93
+++ modules/dav/main/mod_dav.c	3 Jun 2003 21:23:01 -0000
@@ -453,78 +453,121 @@
     return apr_xml_quote_string(p, e_uri, 0);
 }
 
-static void dav_send_multistatus(request_rec *r, int status,
-                                 dav_response *first,
-                                 apr_array_header_t *namespaces)
+
+/* Write a complete RESPONSE object out as a <DAV:repsonse> xml
+   element.  Data is sent into brigade BB, which is auto-flushed into
+   OUTPUT filter stack.  Use POOL for any temporary allocations.
+
+   [Presumably the <multistatus> tag has already been written;  this
+   routine is shared by dav_send_multistatus and dav_stream_response.]
+*/
+static void dav_send_one_response(dav_response *response,
+                                  apr_bucket_brigade *bb,
+                                  ap_filter_t *output,
+                                  apr_pool_t *pool)
+{
+    apr_text *t = NULL;
+
+    if (response->propresult.xmlns == NULL) {
+      ap_fputs(output, bb, "<D:response>");
+    }
+    else {
+      ap_fputs(output, bb, "<D:response");
+      for (t = response->propresult.xmlns; t; t = t->next) {
+        ap_fputs(output, bb, t->text);
+      }
+      ap_fputc(output, bb, '>');
+    }
+    
+    ap_fputstrs(output, bb,
+                DEBUG_CR "<D:href>",
+                dav_xml_escape_uri(pool, response->href),
+                "</D:href>" DEBUG_CR,
+                NULL);
+    
+    if (response->propresult.propstats == NULL) {
+      /* use the Status-Line text from Apache.  Note, this will
+       * default to 500 Internal Server Error if first->status
+       * is not a known (or valid) status code.
+       */
+      ap_fputstrs(output, bb,
+                  "<D:status>HTTP/1.1 ",
+                  ap_get_status_line(response->status),
+                  "</D:status>" DEBUG_CR,
+                  NULL);
+    }
+    else {
+      /* assume this includes <propstat> and is quoted properly */
+      for (t = response->propresult.propstats; t; t = t->next) {
+        ap_fputs(output, bb, t->text);
+      }
+    }
+    
+    if (response->desc != NULL) {
+      /*
+       * We supply the description, so we know it doesn't have to
+       * have any escaping/encoding applied to it.
+       */
+      ap_fputstrs(output, bb,
+                  "<D:responsedescription>",
+                  response->desc,
+                  "</D:responsedescription>" DEBUG_CR,
+                  NULL);
+    }
+    
+    ap_fputs(output, bb, "</D:response>" DEBUG_CR);
+}
+
+
+/* Factorized helper function: prep request_rec R for a multistatus
+   response and write <multistatus> tag into BB, destined for
+   R->output_filters.  Use xml NAMESPACES in initial tag, if
+   non-NULL. */
+static void dav_begin_multistatus(apr_bucket_brigade *bb,
+                                  request_rec *r, int status,
+                                  apr_array_header_t *namespaces)
 {
     /* Set the correct status and Content-Type */
     r->status = status;
     ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
 
     /* Send the headers and actual multistatus response now... */
-    ap_rputs(DAV_XML_HEADER DEBUG_CR
-             "<D:multistatus xmlns:D=\"DAV:\"", r);
+    ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR
+             "<D:multistatus xmlns:D=\"DAV:\"");
 
     if (namespaces != NULL) {
        int i;
 
        for (i = namespaces->nelts; i--; ) {
-           ap_rprintf(r, " xmlns:ns%d=\"%s\"", i,
+           ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i,
                       APR_XML_GET_URI_ITEM(namespaces, i));
        }
     }
 
-    /* ap_rputc('>', r); */
-    ap_rputs(">" DEBUG_CR, r);
-
-    for (; first != NULL; first = first->next) {
-        apr_text *t;
+    ap_fputs(r->output_filters, bb, ">" DEBUG_CR);
+}
 
-        if (first->propresult.xmlns == NULL) {
-            ap_rputs("<D:response>", r);
-        }
-        else {
-            ap_rputs("<D:response", r);
-            for (t = first->propresult.xmlns; t; t = t->next) {
-                ap_rputs(t->text, r);
-            }
-            ap_rputc('>', r);
-        }
 
-        ap_rputs(DEBUG_CR "<D:href>", r);
-        ap_rputs(dav_xml_escape_uri(r->pool, first->href), r);
-        ap_rputs("</D:href>" DEBUG_CR, r);
+static void dav_send_multistatus(request_rec *r, int status,
+                                 dav_response *first,
+                                 apr_array_header_t *namespaces)
+{
+    apr_pool_t *subpool;
+    apr_bucket_brigade *bb = apr_brigade_create(r->pool,
+                                                r->connection->bucket_alloc);
 
-        if (first->propresult.propstats == NULL) {
-            /* use the Status-Line text from Apache.  Note, this will
-             * default to 500 Internal Server Error if first->status
-             * is not a known (or valid) status code.
-             */
-            ap_rprintf(r,
-                       "<D:status>HTTP/1.1 %s</D:status>" DEBUG_CR,
-                       ap_get_status_line(first->status));
-        }
-        else {
-            /* assume this includes <propstat> and is quoted properly */
-            for (t = first->propresult.propstats; t; t = t->next) {
-                ap_rputs(t->text, r);
-            }
-        }
+    dav_begin_multistatus(bb, r, status, namespaces);
 
-        if (first->desc != NULL) {
-            /*
-             * We supply the description, so we know it doesn't have to
-             * have any escaping/encoding applied to it.
-             */
-            ap_rputs("<D:responsedescription>", r);
-            ap_rputs(first->desc, r);
-            ap_rputs("</D:responsedescription>" DEBUG_CR, r);
-        }
+    apr_pool_create(&subpool, r->pool);
 
-        ap_rputs("</D:response>" DEBUG_CR, r);
+    for (; first != NULL; first = first->next) {
+      apr_pool_clear(subpool);
+      dav_send_one_response(first, bb, r->output_filters, subpool);
     }
+    apr_pool_destroy(subpool);
 
-    ap_rputs("</D:multistatus>" DEBUG_CR, r);
+    ap_fputs(r->output_filters, bb, "</D:multistatus>" DEBUG_CR);
+    ap_filter_flush(bb, r->output_filters);
 }
 
 /*
@@ -1048,6 +1091,27 @@
     return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
 }
 
+
+/* Use POOL to temporarily construct a dav_response object (from WRES
+   STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
+static void dav_stream_response(dav_walk_resource *wres,
+                                int status,
+                                dav_get_props_result *propstats,
+                                apr_pool_t *pool)
+{
+    dav_response resp = { 0 };
+    dav_walker_ctx *ctx = wres->walk_ctx;
+
+    resp.href = wres->resource->uri;
+    resp.status = status;
+    if (propstats) {
+        resp.propresult = *propstats;
+    }
+
+    dav_send_one_response(&resp, ctx->bb, ctx->r->output_filters, pool);
+}
+
+
 /* ### move this to dav_util? */
 DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
                                    int status, dav_get_props_result *propstats)
@@ -1066,6 +1130,8 @@
     wres->response = resp;
 }
 
+
+
 /* handle the DELETE method */
 static int dav_method_delete(request_rec *r)
 {
@@ -1837,12 +1903,14 @@
             /* some props were expected on this collection/resource */
             dav_cache_badprops(ctx);
             badprops.propstats = ctx->propstat_404;
-            dav_add_response(wres, 0, &badprops);
+            dav_stream_response(wres, 0, &badprops, ctx->scratchpool);
         }
         else {
             /* no props on this collection/resource */
-            dav_add_response(wres, HTTP_OK, NULL);
+            dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool);
         }
+
+        apr_pool_clear(ctx->scratchpool);
         return NULL;
     }
     /* ### what to do about closing the propdb on server failure? */
@@ -1858,7 +1926,13 @@
     }
     dav_close_propdb(propdb);
 
-    dav_add_response(wres, 0, &propstats);
+    dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
+
+    /* at this point, ctx->scratchpool has been used to stream a
+       single response.  this function fully controls the pool, and
+       thus has the right to clear it for the next iteration of this
+       callback. */
+    apr_pool_clear(ctx->scratchpool);
 
     return NULL;
 }
@@ -1951,6 +2025,8 @@
 
     ctx.doc = doc;
     ctx.r = r;
+    ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    apr_pool_create (&ctx.scratchpool, r->pool);
 
     /* ### should open read-only */
     if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
@@ -1966,6 +2042,17 @@
         ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
     }
 
+    /* send <multistatus> tag, with all doc->namespaces attached.  */
+
+    /* NOTE: we *cannot* leave out the doc's namespaces from the
+       initial <multistatus> tag.  if a 404 was generated for an HREF,
+       then we need to spit out the doc's namespaces for use by the
+       404. Note that <response> elements will override these ns0,
+       ns1, etc, but NOT within the <response> scope for the
+       badprops. */
+    dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS, doc->namespaces);
+
+    /* Have the provider walk the resource. */
     err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
 
     if (ctx.w.lockdb != NULL) {
@@ -1977,20 +2064,9 @@
         return dav_handle_err(r, err, NULL);
     }
 
-    /* return a 207 (Multi-Status) response now. */
-
-    /* if a 404 was generated for an HREF, then we need to spit out the
-     * doc's namespaces for use by the 404. Note that <response> elements
-     * will override these ns0, ns1, etc, but NOT within the <response>
-     * scope for the badprops. */
-    /* NOTE: propstat_404 != NULL implies doc != NULL */
-    if (ctx.propstat_404 != NULL) {
-        dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status,
-                             doc->namespaces);
-    }
-    else {
-        dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
-    }
+    /* Finish up the multistatus response. */
+    ap_fputs(r->output_filters, ctx.bb, "</D:multistatus>" DEBUG_CR);
+    ap_filter_flush(ctx.bb, r->output_filters);
 
     /* the response has been sent. */
     return DONE;
Index: modules/dav/main/mod_dav.h
===================================================================
RCS file: /home/cvspublic/httpd-2.0/modules/dav/main/mod_dav.h,v
retrieving revision 1.66
diff -u -r1.66 mod_dav.h
--- modules/dav/main/mod_dav.h	3 Feb 2003 17:52:58 -0000	1.66
+++ modules/dav/main/mod_dav.h	3 Jun 2003 21:23:04 -0000
@@ -1671,6 +1671,12 @@
 
     /* ### client data... phasing out this big glom */
 
+    /* this brigade buffers data being sent to r->output_filters */ 
+    apr_bucket_brigade *bb;
+
+    /* a scratch pool, used to stream responses and iteratively cleared. */
+    apr_pool_t *scratchpool;
+
     request_rec *r;                 /* original request */
 
     /* for PROPFIND operations */

Mime
View raw message