From 078bf5eb6b160ed848c20de0b58f4ea5a9dd3470 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Tue, 1 Dec 2015 00:22:45 +1030 Subject: [PATCH] Improve HTTP responses In 'struct http_response', rename the 'result_code' field to 'status_code' for consistency with the terminology used in the HTTP specification, and add a 'reason' field, so that the phrase that appears in the first response line can differ from the standard "canned" phrases. --- http_server.c | 88 ++++++++++++++++++++++++++++----------------------- http_server.h | 3 +- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/http_server.c b/http_server.c index bb19816e..0f4d6c7d 100644 --- a/http_server.c +++ b/http_server.c @@ -237,14 +237,14 @@ static int _reserve(struct http_request *r, const char **resp, const char *src, assert(r->reserved <= reslim); size_t siz = sizeof(char**) + len + 1; if (r->reserved + siz > reslim) { - r->response.result_code = 414; // Request-URI Too Long + r->response.status_code = 414; // Request-URI Too Long return 0; } if (r->reserved + siz > r->parsed) { WHYF("Error during HTTP parsing, unparsed content %s would be overwritten by reserving %zu bytes", alloca_toprint(30, r->parsed, r->end - r->parsed), len + 1 ); - r->response.result_code = 500; + r->response.status_code = 500; return 0; } const char ***respp = (const char ***) r->reserved; @@ -1103,8 +1103,8 @@ static int http_request_parse_header(struct http_request *r) _commit(r); return 0; } - if (r->response.result_code) - return r->response.result_code; + if (r->response.status_code) + return r->response.status_code; goto malformed; } _rewind(r); @@ -1124,8 +1124,8 @@ static int http_request_parse_header(struct http_request *r) _commit(r); return 0; } - if (r->response.result_code) - return r->response.result_code; + if (r->response.status_code) + return r->response.status_code; goto malformed; } _rewind(r); @@ -1666,14 +1666,14 @@ static void http_request_receive(struct http_request *r) } } if (result >= 200 && result < 600) { - assert(r->response.result_code == 0 || r->response.result_code == result); - r->response.result_code = result; + assert(r->response.status_code == 0 || r->response.status_code == result); + r->response.status_code = result; } else if (result) { WHYF("Internal failure parsing HTTP request: invalid result code %d", result); DEBUG_DUMP_PARSER(r); - r->response.result_code = 500; + r->response.status_code = 500; } - if (r->response.result_code) + if (r->response.status_code) break; if (result == -1) { WHY("Unrecoverable error parsing HTTP request, closing connection"); @@ -1683,13 +1683,13 @@ static void http_request_receive(struct http_request *r) } } if (r->phase != RECEIVE) { - assert(r->response.result_code != 0); + assert(r->response.status_code != 0); RETURNVOID; } - if (r->response.result_code == 0) { + if (r->response.status_code == 0) { WHY("No HTTP response set, using 500 Server Error"); DEBUG_DUMP_PARSER(r); - r->response.result_code = 500; + r->response.status_code = 500; } http_request_start_response(r); OUT(); @@ -1985,13 +1985,13 @@ static const char *http_reason_phrase(int response_code) } } -static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char *reason_phrase) +static strbuf strbuf_status_body(strbuf sb, struct http_response *hr) { if ( hr->header.content_type == CONTENT_TYPE_TEXT || (hr->header.content_type && strcmp(hr->header.content_type, CONTENT_TYPE_TEXT) == 0) ) { hr->header.content_type = CONTENT_TYPE_TEXT; - strbuf_sprintf(sb, "%03u %s", hr->result_code, reason_phrase); + strbuf_sprintf(sb, "%03u %s", hr->status_code, hr->reason); unsigned i; for (i = 0; i < NELS(hr->result_extra); ++i) if (hr->result_extra[i].label) { @@ -2006,8 +2006,8 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char || (hr->header.content_type && strcmp(hr->header.content_type, CONTENT_TYPE_JSON) == 0) ) { hr->header.content_type = CONTENT_TYPE_JSON; - strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->result_code); - strbuf_json_string(sb, reason_phrase); + strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->status_code); + strbuf_json_string(sb, hr->reason); unsigned i; for (i = 0; i < NELS(hr->result_extra); ++i) if (hr->result_extra[i].label) { @@ -2020,7 +2020,7 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char } else { hr->header.content_type = CONTENT_TYPE_HTML; - strbuf_sprintf(sb, "\n

%03u %s

", hr->result_code, reason_phrase); + strbuf_sprintf(sb, "\n

%03u %s

", hr->status_code, hr->reason); strbuf_puts(sb, "\n
"); unsigned i; for (i = 0; i < NELS(hr->result_extra); ++i) @@ -2045,12 +2045,13 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char static int _render_response(struct http_request *r) { struct http_response hr = r->response; - assert(hr.result_code >= 100); - assert(hr.result_code < 600); + assert(hr.status_code >= 100); + assert(hr.status_code < 600); // Status code 401 must be accompanied by a WWW-Authenticate header. - if (hr.result_code == 401) + if (hr.status_code == 401) assert(hr.header.www_authenticate.scheme != NOAUTH); - const char *reason_phrase = http_reason_phrase(hr.result_code); + if (!hr.reason) + hr.reason = http_reason_phrase(hr.status_code); strbuf sb = strbuf_local(r->response_buffer, r->response_buffer_size); // Cannot specify both static (pre-rendered) content AND generated content. assert(!(hr.content && hr.content_generator)); @@ -2079,8 +2080,8 @@ static int _render_response(struct http_request *r) && hr.header.content_length > 0 && hr.header.content_length < hr.header.resource_length ) { - if (hr.result_code == 200) - hr.result_code = 206; // Partial Content + if (hr.status_code == 200) + hr.status_code = 206; // Partial Content } } else { // If no content has been supplied at all, then render a standard, short body based solely on @@ -2088,9 +2089,9 @@ static int _render_response(struct http_request *r) assert(hr.header.content_length == CONTENT_LENGTH_UNKNOWN); assert(hr.header.resource_length == CONTENT_LENGTH_UNKNOWN); assert(hr.header.content_range_start == 0); - assert(hr.result_code != 206); + assert(hr.status_code != 206); strbuf cb; - STRBUF_ALLOCA_FIT(cb, 40 + strlen(reason_phrase), (strbuf_status_body(cb, &hr, reason_phrase))); + STRBUF_ALLOCA_FIT(cb, 40 + strlen(hr.reason), (strbuf_status_body(cb, &hr))); hr.content = strbuf_str(cb); hr.header.content_length = strbuf_len(cb); hr.header.resource_length = hr.header.content_length; @@ -2098,7 +2099,7 @@ static int _render_response(struct http_request *r) } assert(hr.header.content_type != NULL); assert(hr.header.content_type[0]); - strbuf_sprintf(sb, "HTTP/1.0 %03u %s\r\n", hr.result_code, reason_phrase); + strbuf_sprintf(sb, "HTTP/1.0 %03u %s\r\n", hr.status_code, hr.reason); strbuf_sprintf(sb, "Content-Type: %s", hr.header.content_type); if (hr.header.boundary) { strbuf_puts(sb, "; boundary="); @@ -2108,7 +2109,7 @@ static int _render_response(struct http_request *r) strbuf_puts(sb, hr.header.boundary); } strbuf_puts(sb, "\r\n"); - if (hr.result_code == 206) { + if (hr.status_code == 206) { // Must only use result code 206 (Partial Content) if the content is in fact less than the whole // resource length. assert(hr.header.content_length != CONTENT_LENGTH_UNKNOWN); @@ -2148,7 +2149,7 @@ static int _render_response(struct http_request *r) case BASIC: scheme = "Basic"; break; } if (scheme) { - assert(hr.result_code == 401); + assert(hr.status_code == 401); strbuf_sprintf(sb, "WWW-Authenticate: %s realm=", scheme); strbuf_append_quoted_string(sb, hr.header.www_authenticate.realm); strbuf_puts(sb, "\r\n"); @@ -2238,9 +2239,9 @@ static void http_request_start_response(struct http_request *r) if (r->phase != RECEIVE) RETURNVOID; // Ensure conformance to HTTP standards. - if (r->response.result_code == 401 && r->response.header.www_authenticate.scheme == NOAUTH) { + if (r->response.status_code == 401 && r->response.header.www_authenticate.scheme == NOAUTH) { WHY("HTTP 401 response missing WWW-Authenticate header, sending 500 Server Error instead"); - r->response.result_code = 500; + r->response.status_code = 500; r->response.content = NULL; r->response.content_generator = NULL; } @@ -2249,7 +2250,7 @@ static void http_request_start_response(struct http_request *r) http_request_render_response(r); if (r->response_buffer == NULL) { WHY("Cannot render HTTP response, sending 500 Server Error instead"); - r->response.result_code = 500; + r->response.status_code = 500; r->response.content = NULL; r->response.content_generator = NULL; http_request_render_response(r); @@ -2278,7 +2279,7 @@ void http_request_response_static(struct http_request *r, int result, const char assert(r->phase == RECEIVE); assert(mime_type != NULL); assert(mime_type[0]); - r->response.result_code = result; + r->response.status_code = result; r->response.header.content_type = mime_type; r->response.header.content_range_start = 0; r->response.header.content_length = r->response.header.resource_length = bytes; @@ -2292,7 +2293,7 @@ void http_request_response_generated(struct http_request *r, int result, const c assert(r->phase == RECEIVE); assert(mime_type != NULL); assert(mime_type[0]); - r->response.result_code = result; + r->response.status_code = result; r->response.header.content_type = mime_type; r->response.content = NULL; r->response.content_generator = generator; @@ -2301,20 +2302,27 @@ void http_request_response_generated(struct http_request *r, int result, const c /* Start sending a short response back to the client. The result code must be either a success * (2xx), redirection (3xx) or client error (4xx) or server error (5xx) code. The 'reason_phrase' - * argument can be text which will be enclosed in either a JSON, HTML or plain text envelope to form - * the response content, so it should contain any special markup (eg, HTML). If the 'reason_phrase' - * argument is NULL, then the reason phrase will be generated automatically from the result code. + * argument is an optional, nul-terminated string which will be placed in the first line of the + * response, after the status code, and also enclosed in either a JSON, HTML or plain text envelope + * to form the response content, so it should contain any special markup (eg, HTML). If the + * 'reason_phrase' argument is NULL, then the reason phrase will be generated automatically from the + * result code. + * + * This function copies the 'reason_phrase' string into the outgoing response buffer before it + * returns, and makes no further use of the string pointer, so the caller can construct the string + * in any way, including as an auto array of char, or allocated on the stack using alloca(3). * * @author Andrew Bettison */ void http_request_simple_response(struct http_request *r, uint16_t result, const char *reason_phrase) { assert(r->phase == RECEIVE); - r->response.result_code = result; + r->response.status_code = result; + r->response.reason = reason_phrase; r->response.header.content_range_start = 0; strbuf h = NULL; - if (reason_phrase) - STRBUF_ALLOCA_FIT(h, 40 + strlen(reason_phrase), (strbuf_status_body(h, &r->response, reason_phrase))); + if (r->response.reason) + STRBUF_ALLOCA_FIT(h, 40 + strlen(r->response.reason), (strbuf_status_body(h, &r->response))); if (h) { r->response.header.resource_length = r->response.header.content_length = strbuf_len(h); r->response.content = strbuf_str(h); diff --git a/http_server.h b/http_server.h index 8b6af2a5..627c074b 100644 --- a/http_server.h +++ b/http_server.h @@ -123,7 +123,8 @@ struct http_content_generator_result { typedef int (HTTP_CONTENT_GENERATOR)(struct http_request *, unsigned char *, size_t, struct http_content_generator_result *); struct http_response { - uint16_t result_code; + uint16_t status_code; + const char *reason; struct { const char *label; struct json_atom value;