From: Greg Stein Date: Wed, 25 Jun 2003 20:18:32 +0000 (+0000) Subject: Add streamy PROPFIND responses. X-Git-Tag: 2.0.47~35 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4077acac96cc7e4bc99b161c8f26ca29b2e7a857;p=thirdparty%2Fapache%2Fhttpd.git Add streamy PROPFIND responses. Backport revs 1.94, 1.95, and 1.96 of mod_dav.c, and 1.67 of mod_dav.h Submitted by: Ben Collins-Sussman Reviewed by: gstein, striker, jerenkrantz git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/APACHE_2_0_BRANCH@100361 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c index 8be2c68ba95..57f222f2588 100644 --- a/modules/dav/main/mod_dav.c +++ b/modules/dav/main/mod_dav.c @@ -453,78 +453,121 @@ static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri) 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 xml + element. Data is sent into brigade BB, which is auto-flushed into + OUTPUT filter stack. Use POOL for any temporary allocations. + + [Presumably the 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, ""); + } + else { + ap_fputs(output, bb, "propresult.xmlns; t; t = t->next) { + ap_fputs(output, bb, t->text); + } + ap_fputc(output, bb, '>'); + } + + ap_fputstrs(output, bb, + DEBUG_CR "", + dav_xml_escape_uri(pool, response->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, + "HTTP/1.1 ", + ap_get_status_line(response->status), + "" DEBUG_CR, + NULL); + } + else { + /* assume this includes 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, + "", + response->desc, + "" DEBUG_CR, + NULL); + } + + ap_fputs(output, bb, "" DEBUG_CR); +} + + +/* Factorized helper function: prep request_rec R for a multistatus + response and write 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 - "output_filters, bb, DAV_XML_HEADER DEBUG_CR + "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); + ap_fputs(r->output_filters, bb, ">" DEBUG_CR); +} - for (; first != NULL; first = first->next) { - apr_text *t; - if (first->propresult.xmlns == NULL) { - ap_rputs("", r); - } - else { - ap_rputs("propresult.xmlns; t; t = t->next) { - ap_rputs(t->text, r); - } - ap_rputc('>', 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); - ap_rputs(DEBUG_CR "", r); - ap_rputs(dav_xml_escape_uri(r->pool, first->href), r); - ap_rputs("" DEBUG_CR, r); + dav_begin_multistatus(bb, r, status, namespaces); - 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, - "HTTP/1.1 %s" DEBUG_CR, - ap_get_status_line(first->status)); - } - else { - /* assume this includes and is quoted properly */ - for (t = first->propresult.propstats; t; t = t->next) { - ap_rputs(t->text, r); - } - } + apr_pool_create(&subpool, r->pool); - 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("", r); - ap_rputs(first->desc, r); - ap_rputs("" DEBUG_CR, r); - } - - ap_rputs("" 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("" DEBUG_CR, r); + ap_fputs(r->output_filters, bb, "" DEBUG_CR); + ap_filter_flush(bb, r->output_filters); } /* @@ -1048,6 +1091,27 @@ static int dav_method_put(request_rec *r) 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,7 @@ DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres, wres->response = resp; } + /* handle the DELETE method */ static int dav_method_delete(request_rec *r) { @@ -1837,12 +1902,14 @@ static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype) /* 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 +1925,13 @@ static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype) } 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 +2024,8 @@ static int dav_method_propfind(request_rec *r) 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 +2041,18 @@ static int dav_method_propfind(request_rec *r) ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL; } + /* send tag, with all doc->namespaces attached. */ + + /* NOTE: we *cannot* leave out the doc's namespaces from the + initial 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 elements will override these ns0, + ns1, etc, but NOT within the scope for the + badprops. */ + dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS, + doc ? doc->namespaces : NULL); + + /* Have the provider walk the resource. */ err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status); if (ctx.w.lockdb != NULL) { @@ -1973,24 +2060,23 @@ static int dav_method_propfind(request_rec *r) } if (err != NULL) { - /* ### add a higher-level description? */ - return dav_handle_err(r, err, NULL); + /* If an error occurred during the resource walk, there's + basically nothing we can do but abort the connection and + log an error. This is one of the limitations of HTTP; it + needs to "know" the entire status of the response before + generating it, which is just impossible in these streamy + response situations. */ + err = dav_push_error(r->pool, err->status, 0, + "Provider encountered an error while streaming" + " a multistatus PROPFIND response.", err); + dav_log_err(r, err, APLOG_ERR); + r->connection->aborted = 1; + return DONE; } - /* 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 elements - * will override these ns0, ns1, etc, but NOT within the - * 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, "" DEBUG_CR); + ap_filter_flush(ctx.bb, r->output_filters); /* the response has been sent. */ return DONE; @@ -3958,9 +4044,22 @@ static int dav_method_report(request_rec *r) /* run report hook */ if ((err = (*vsn_hooks->deliver_report)(r, resource, doc, r->output_filters)) != NULL) { - /* NOTE: we're assuming that the provider has not generated any - content yet! */ - return dav_handle_err(r, err, NULL); + if (! r->sent_bodyct) + /* No data has been sent to client yet; throw normal error. */ + return dav_handle_err(r, err, NULL); + + /* If an error occurred during the report delivery, there's + basically nothing we can do but abort the connection and + log an error. This is one of the limitations of HTTP; it + needs to "know" the entire status of the response before + generating it, which is just impossible in these streamy + response situations. */ + err = dav_push_error(r->pool, err->status, 0, + "Provider encountered an error while streaming" + " a REPORT response.", err); + dav_log_err(r, err, APLOG_ERR); + r->connection->aborted = 1; + return DONE; } return DONE; diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h index 3f6893197f7..c8737168cd1 100644 --- a/modules/dav/main/mod_dav.h +++ b/modules/dav/main/mod_dav.h @@ -1671,6 +1671,12 @@ typedef struct dav_walker_ctx /* ### 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 */