mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-02-22 10:10:54 +00:00
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.
This commit is contained in:
parent
17b0644277
commit
078bf5eb6b
@ -237,14 +237,14 @@ static int _reserve(struct http_request *r, const char **resp, const char *src,
|
|||||||
assert(r->reserved <= reslim);
|
assert(r->reserved <= reslim);
|
||||||
size_t siz = sizeof(char**) + len + 1;
|
size_t siz = sizeof(char**) + len + 1;
|
||||||
if (r->reserved + siz > reslim) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
if (r->reserved + siz > r->parsed) {
|
if (r->reserved + siz > r->parsed) {
|
||||||
WHYF("Error during HTTP parsing, unparsed content %s would be overwritten by reserving %zu bytes",
|
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
|
alloca_toprint(30, r->parsed, r->end - r->parsed), len + 1
|
||||||
);
|
);
|
||||||
r->response.result_code = 500;
|
r->response.status_code = 500;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const char ***respp = (const char ***) r->reserved;
|
const char ***respp = (const char ***) r->reserved;
|
||||||
@ -1103,8 +1103,8 @@ static int http_request_parse_header(struct http_request *r)
|
|||||||
_commit(r);
|
_commit(r);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (r->response.result_code)
|
if (r->response.status_code)
|
||||||
return r->response.result_code;
|
return r->response.status_code;
|
||||||
goto malformed;
|
goto malformed;
|
||||||
}
|
}
|
||||||
_rewind(r);
|
_rewind(r);
|
||||||
@ -1124,8 +1124,8 @@ static int http_request_parse_header(struct http_request *r)
|
|||||||
_commit(r);
|
_commit(r);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (r->response.result_code)
|
if (r->response.status_code)
|
||||||
return r->response.result_code;
|
return r->response.status_code;
|
||||||
goto malformed;
|
goto malformed;
|
||||||
}
|
}
|
||||||
_rewind(r);
|
_rewind(r);
|
||||||
@ -1666,14 +1666,14 @@ static void http_request_receive(struct http_request *r)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result >= 200 && result < 600) {
|
if (result >= 200 && result < 600) {
|
||||||
assert(r->response.result_code == 0 || r->response.result_code == result);
|
assert(r->response.status_code == 0 || r->response.status_code == result);
|
||||||
r->response.result_code = result;
|
r->response.status_code = result;
|
||||||
} else if (result) {
|
} else if (result) {
|
||||||
WHYF("Internal failure parsing HTTP request: invalid result code %d", result);
|
WHYF("Internal failure parsing HTTP request: invalid result code %d", result);
|
||||||
DEBUG_DUMP_PARSER(r);
|
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;
|
break;
|
||||||
if (result == -1) {
|
if (result == -1) {
|
||||||
WHY("Unrecoverable error parsing HTTP request, closing connection");
|
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) {
|
if (r->phase != RECEIVE) {
|
||||||
assert(r->response.result_code != 0);
|
assert(r->response.status_code != 0);
|
||||||
RETURNVOID;
|
RETURNVOID;
|
||||||
}
|
}
|
||||||
if (r->response.result_code == 0) {
|
if (r->response.status_code == 0) {
|
||||||
WHY("No HTTP response set, using 500 Server Error");
|
WHY("No HTTP response set, using 500 Server Error");
|
||||||
DEBUG_DUMP_PARSER(r);
|
DEBUG_DUMP_PARSER(r);
|
||||||
r->response.result_code = 500;
|
r->response.status_code = 500;
|
||||||
}
|
}
|
||||||
http_request_start_response(r);
|
http_request_start_response(r);
|
||||||
OUT();
|
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
|
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 && strcmp(hr->header.content_type, CONTENT_TYPE_TEXT) == 0)
|
||||||
) {
|
) {
|
||||||
hr->header.content_type = CONTENT_TYPE_TEXT;
|
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;
|
unsigned i;
|
||||||
for (i = 0; i < NELS(hr->result_extra); ++i)
|
for (i = 0; i < NELS(hr->result_extra); ++i)
|
||||||
if (hr->result_extra[i].label) {
|
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 && strcmp(hr->header.content_type, CONTENT_TYPE_JSON) == 0)
|
||||||
) {
|
) {
|
||||||
hr->header.content_type = CONTENT_TYPE_JSON;
|
hr->header.content_type = CONTENT_TYPE_JSON;
|
||||||
strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->result_code);
|
strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->status_code);
|
||||||
strbuf_json_string(sb, reason_phrase);
|
strbuf_json_string(sb, hr->reason);
|
||||||
unsigned i;
|
unsigned i;
|
||||||
for (i = 0; i < NELS(hr->result_extra); ++i)
|
for (i = 0; i < NELS(hr->result_extra); ++i)
|
||||||
if (hr->result_extra[i].label) {
|
if (hr->result_extra[i].label) {
|
||||||
@ -2020,7 +2020,7 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
hr->header.content_type = CONTENT_TYPE_HTML;
|
hr->header.content_type = CONTENT_TYPE_HTML;
|
||||||
strbuf_sprintf(sb, "<html>\n<h1>%03u %s</h1>", hr->result_code, reason_phrase);
|
strbuf_sprintf(sb, "<html>\n<h1>%03u %s</h1>", hr->status_code, hr->reason);
|
||||||
strbuf_puts(sb, "\n<dl>");
|
strbuf_puts(sb, "\n<dl>");
|
||||||
unsigned i;
|
unsigned i;
|
||||||
for (i = 0; i < NELS(hr->result_extra); ++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)
|
static int _render_response(struct http_request *r)
|
||||||
{
|
{
|
||||||
struct http_response hr = r->response;
|
struct http_response hr = r->response;
|
||||||
assert(hr.result_code >= 100);
|
assert(hr.status_code >= 100);
|
||||||
assert(hr.result_code < 600);
|
assert(hr.status_code < 600);
|
||||||
// Status code 401 must be accompanied by a WWW-Authenticate header.
|
// 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);
|
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);
|
strbuf sb = strbuf_local(r->response_buffer, r->response_buffer_size);
|
||||||
// Cannot specify both static (pre-rendered) content AND generated content.
|
// Cannot specify both static (pre-rendered) content AND generated content.
|
||||||
assert(!(hr.content && hr.content_generator));
|
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 > 0
|
||||||
&& hr.header.content_length < hr.header.resource_length
|
&& hr.header.content_length < hr.header.resource_length
|
||||||
) {
|
) {
|
||||||
if (hr.result_code == 200)
|
if (hr.status_code == 200)
|
||||||
hr.result_code = 206; // Partial Content
|
hr.status_code = 206; // Partial Content
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no content has been supplied at all, then render a standard, short body based solely on
|
// 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.content_length == CONTENT_LENGTH_UNKNOWN);
|
||||||
assert(hr.header.resource_length == CONTENT_LENGTH_UNKNOWN);
|
assert(hr.header.resource_length == CONTENT_LENGTH_UNKNOWN);
|
||||||
assert(hr.header.content_range_start == 0);
|
assert(hr.header.content_range_start == 0);
|
||||||
assert(hr.result_code != 206);
|
assert(hr.status_code != 206);
|
||||||
strbuf cb;
|
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.content = strbuf_str(cb);
|
||||||
hr.header.content_length = strbuf_len(cb);
|
hr.header.content_length = strbuf_len(cb);
|
||||||
hr.header.resource_length = hr.header.content_length;
|
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 != NULL);
|
||||||
assert(hr.header.content_type[0]);
|
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);
|
strbuf_sprintf(sb, "Content-Type: %s", hr.header.content_type);
|
||||||
if (hr.header.boundary) {
|
if (hr.header.boundary) {
|
||||||
strbuf_puts(sb, "; 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, hr.header.boundary);
|
||||||
}
|
}
|
||||||
strbuf_puts(sb, "\r\n");
|
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
|
// Must only use result code 206 (Partial Content) if the content is in fact less than the whole
|
||||||
// resource length.
|
// resource length.
|
||||||
assert(hr.header.content_length != CONTENT_LENGTH_UNKNOWN);
|
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;
|
case BASIC: scheme = "Basic"; break;
|
||||||
}
|
}
|
||||||
if (scheme) {
|
if (scheme) {
|
||||||
assert(hr.result_code == 401);
|
assert(hr.status_code == 401);
|
||||||
strbuf_sprintf(sb, "WWW-Authenticate: %s realm=", scheme);
|
strbuf_sprintf(sb, "WWW-Authenticate: %s realm=", scheme);
|
||||||
strbuf_append_quoted_string(sb, hr.header.www_authenticate.realm);
|
strbuf_append_quoted_string(sb, hr.header.www_authenticate.realm);
|
||||||
strbuf_puts(sb, "\r\n");
|
strbuf_puts(sb, "\r\n");
|
||||||
@ -2238,9 +2239,9 @@ static void http_request_start_response(struct http_request *r)
|
|||||||
if (r->phase != RECEIVE)
|
if (r->phase != RECEIVE)
|
||||||
RETURNVOID;
|
RETURNVOID;
|
||||||
// Ensure conformance to HTTP standards.
|
// 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");
|
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 = NULL;
|
||||||
r->response.content_generator = 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);
|
http_request_render_response(r);
|
||||||
if (r->response_buffer == NULL) {
|
if (r->response_buffer == NULL) {
|
||||||
WHY("Cannot render HTTP response, sending 500 Server Error instead");
|
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 = NULL;
|
||||||
r->response.content_generator = NULL;
|
r->response.content_generator = NULL;
|
||||||
http_request_render_response(r);
|
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(r->phase == RECEIVE);
|
||||||
assert(mime_type != NULL);
|
assert(mime_type != NULL);
|
||||||
assert(mime_type[0]);
|
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_type = mime_type;
|
||||||
r->response.header.content_range_start = 0;
|
r->response.header.content_range_start = 0;
|
||||||
r->response.header.content_length = r->response.header.resource_length = bytes;
|
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(r->phase == RECEIVE);
|
||||||
assert(mime_type != NULL);
|
assert(mime_type != NULL);
|
||||||
assert(mime_type[0]);
|
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_type = mime_type;
|
||||||
r->response.content = NULL;
|
r->response.content = NULL;
|
||||||
r->response.content_generator = generator;
|
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
|
/* 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'
|
* (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
|
* argument is an optional, nul-terminated string which will be placed in the first line of the
|
||||||
* the response content, so it should contain any special markup (eg, HTML). If the 'reason_phrase'
|
* response, after the status code, and also enclosed in either a JSON, HTML or plain text envelope
|
||||||
* argument is NULL, then the reason phrase will be generated automatically from the result code.
|
* 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 <andrew@servalproject.com>
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
*/
|
*/
|
||||||
void http_request_simple_response(struct http_request *r, uint16_t result, const char *reason_phrase)
|
void http_request_simple_response(struct http_request *r, uint16_t result, const char *reason_phrase)
|
||||||
{
|
{
|
||||||
assert(r->phase == RECEIVE);
|
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;
|
r->response.header.content_range_start = 0;
|
||||||
strbuf h = NULL;
|
strbuf h = NULL;
|
||||||
if (reason_phrase)
|
if (r->response.reason)
|
||||||
STRBUF_ALLOCA_FIT(h, 40 + strlen(reason_phrase), (strbuf_status_body(h, &r->response, reason_phrase)));
|
STRBUF_ALLOCA_FIT(h, 40 + strlen(r->response.reason), (strbuf_status_body(h, &r->response)));
|
||||||
if (h) {
|
if (h) {
|
||||||
r->response.header.resource_length = r->response.header.content_length = strbuf_len(h);
|
r->response.header.resource_length = r->response.header.content_length = strbuf_len(h);
|
||||||
r->response.content = strbuf_str(h);
|
r->response.content = strbuf_str(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 *);
|
typedef int (HTTP_CONTENT_GENERATOR)(struct http_request *, unsigned char *, size_t, struct http_content_generator_result *);
|
||||||
|
|
||||||
struct http_response {
|
struct http_response {
|
||||||
uint16_t result_code;
|
uint16_t status_code;
|
||||||
|
const char *reason;
|
||||||
struct {
|
struct {
|
||||||
const char *label;
|
const char *label;
|
||||||
struct json_atom value;
|
struct json_atom value;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user