Improve HTTP server generated content logic

Support generated content with an unspecified Content-Length.  Generator
functions return 1 if there is more content to come, and 0 if they have
just produced the last piece of content.
This commit is contained in:
Andrew Bettison 2013-11-07 23:39:24 +10:30
parent 051eca4775
commit d337542067
3 changed files with 38 additions and 19 deletions

View File

@ -1642,16 +1642,22 @@ static void http_request_receive(struct http_request *r)
*/ */
static void http_request_send_response(struct http_request *r) static void http_request_send_response(struct http_request *r)
{ {
assert(r->response_sent <= r->response_length); if (r->response_length != CONTENT_LENGTH_UNKNOWN)
while (r->response_sent < r->response_length) { assert(r->response_sent <= r->response_length);
int g = 1;
while (g) {
if (r->response_length != CONTENT_LENGTH_UNKNOWN && r->response_sent == r->response_length)
break;
assert(r->response_buffer_sent <= r->response_buffer_length); assert(r->response_buffer_sent <= r->response_buffer_length);
if (r->response_buffer_sent == r->response_buffer_length) { if (r->response_buffer_sent == r->response_buffer_length) {
g = 0;
if (r->response.content_generator) { if (r->response.content_generator) {
// Content generator must fill or partly fill response_buffer and set response_buffer_sent // Content generator must fill or partly fill response_buffer and set response_buffer_sent
// and response_buffer_length. May also malloc() a bigger buffer and set response_buffer to // and response_buffer_length. May also malloc() a bigger buffer and set response_buffer to
// point to it. // point to it.
r->response_buffer_sent = r->response_buffer_length = 0; r->response_buffer_sent = r->response_buffer_length = 0;
if (r->response.content_generator(r) == -1) { g = r->response.content_generator(r);
if (g == -1) {
if (r->debug_flag && *r->debug_flag) if (r->debug_flag && *r->debug_flag)
DEBUG("Content generation error, closing connection"); DEBUG("Content generation error, closing connection");
http_request_finalise(r); http_request_finalise(r);
@ -1659,12 +1665,17 @@ static void http_request_send_response(struct http_request *r)
} }
assert(r->response_buffer_sent <= r->response_buffer_length); assert(r->response_buffer_sent <= r->response_buffer_length);
if (r->response_buffer_sent == r->response_buffer_length) { if (r->response_buffer_sent == r->response_buffer_length) {
WHYF("HTTP response generator produced no content at offset %"PRIhttp_size_t"/%"PRIhttp_size_t" (%"PRIhttp_size_t" bytes remaining)", if (g == 0)
r->response_sent, r->response_length, r->response_length - r->response_sent); break;
if (r->response_length != CONTENT_LENGTH_UNKNOWN)
WHYF("HTTP response generator produced no content at offset %"PRIhttp_size_t"/%"PRIhttp_size_t" (%"PRIhttp_size_t" bytes remaining)",
r->response_sent, r->response_length, r->response_length - r->response_sent);
else
WHYF("HTTP response generator produced no content at offset %"PRIhttp_size_t"", r->response_sent);
http_request_finalise(r); http_request_finalise(r);
return; return;
} }
} else { } else if (r->response_length != CONTENT_LENGTH_UNKNOWN) {
WHYF("HTTP response is short of total length (%"PRIhttp_size_t") by %"PRIhttp_size_t" bytes", WHYF("HTTP response is short of total length (%"PRIhttp_size_t") by %"PRIhttp_size_t" bytes",
r->response_length, r->response_length - r->response_sent); r->response_length, r->response_length - r->response_sent);
http_request_finalise(r); http_request_finalise(r);
@ -1900,7 +1911,8 @@ static int _render_response(struct http_request *r)
hr.header.resource_length hr.header.resource_length
); );
} }
strbuf_sprintf(sb, "Content-Length: %"PRIhttp_size_t"\r\n", hr.header.content_length); if (hr.header.content_length != CONTENT_LENGTH_UNKNOWN)
strbuf_sprintf(sb, "Content-Length: %"PRIhttp_size_t"\r\n", hr.header.content_length);
const char *scheme = NULL; const char *scheme = NULL;
switch (hr.header.www_authenticate.scheme) { switch (hr.header.www_authenticate.scheme) {
case NOAUTH: break; case NOAUTH: break;
@ -1913,17 +1925,23 @@ static int _render_response(struct http_request *r)
strbuf_puts(sb, "\r\n"); strbuf_puts(sb, "\r\n");
} }
strbuf_puts(sb, "\r\n"); strbuf_puts(sb, "\r\n");
if (strbuf_overrun(sb)) if (hr.header.content_length != CONTENT_LENGTH_UNKNOWN)
return 0; r->response_length = strbuf_count(sb) + hr.header.content_length;
r->response_length = strbuf_len(sb) + hr.header.content_length; else
r->response_length = CONTENT_LENGTH_UNKNOWN;
if (hr.content) { if (hr.content) {
if (r->response_buffer_size < r->response_length) assert(r->response_length != CONTENT_LENGTH_UNKNOWN);
return 0; r->response_buffer_need = r->response_length + 1;
bcopy(hr.content, strbuf_end(sb), hr.header.content_length);
r->response_buffer_length = r->response_length;
} else { } else {
r->response_buffer_length = strbuf_len(sb); assert(hr.content_generator);
r->response_buffer_need = strbuf_count(sb) + 1;
} }
if (r->response_buffer_size < r->response_buffer_need)
return 0;
assert(!strbuf_overrun(sb));
if (hr.content)
bcopy(hr.content, strbuf_end(sb), hr.header.content_length);
r->response_buffer_length = r->response_buffer_need;
r->response_buffer_sent = 0; r->response_buffer_sent = 0;
return 1; return 1;
} }
@ -1942,9 +1960,9 @@ static void http_request_render_response(struct http_request *r)
// rendered headers, so after this step, whether or not the buffer was overrun, we know the total // rendered headers, so after this step, whether or not the buffer was overrun, we know the total
// length of the response. // length of the response.
if (!_render_response(r)) { if (!_render_response(r)) {
// If the response did not fit into the existing buffer, then allocate a large buffer from the // If the static response did not fit into the existing buffer, then allocate a large buffer
// heap and try rendering again. // from the heap and try rendering again.
if (http_request_set_response_bufsize(r, r->response_length + 1) == -1) if (http_request_set_response_bufsize(r, r->response_buffer_need) == -1)
WHY("Cannot render HTTP response, out of memory"); WHY("Cannot render HTTP response, out of memory");
else if (!_render_response(r)) else if (!_render_response(r))
FATAL("Re-render of HTTP response overflowed buffer"); FATAL("Re-render of HTTP response overflowed buffer");

View File

@ -192,6 +192,7 @@ struct http_request {
http_size_t response_length; // total response bytes (header + content) http_size_t response_length; // total response bytes (header + content)
http_size_t response_sent; // for counting up to response_length http_size_t response_sent; // for counting up to response_length
char *response_buffer; char *response_buffer;
size_t response_buffer_need;
size_t response_buffer_size; size_t response_buffer_size;
size_t response_buffer_length; size_t response_buffer_length;
size_t response_buffer_sent; size_t response_buffer_sent;

View File

@ -443,7 +443,7 @@ static int rhizome_file_content(struct http_request *hr)
return -1; return -1;
assert((size_t) len <= r->http.response_buffer_size); assert((size_t) len <= r->http.response_buffer_size);
r->http.response_buffer_length += (size_t) len; r->http.response_buffer_length += (size_t) len;
return 0; return 1;
} }
static int rhizome_file_page(rhizome_http_request *r, const char *remainder) static int rhizome_file_page(rhizome_http_request *r, const char *remainder)