All HTTP RESTful responses are JSON, not HTTP

Even error responses (typically code 403)
This commit is contained in:
Andrew Bettison 2014-01-20 15:44:21 +10:30
parent de46223cc4
commit 6a1c8bcf5a
3 changed files with 84 additions and 35 deletions

View File

@ -1785,6 +1785,23 @@ static const char *httpResultString(int response_code)
}
}
static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char *message)
{
if (hr->header.content_type && strcmp(hr->header.content_type, "text/plain") == 0) {
strbuf_sprintf(sb, "%03u %s\r\n", hr->result_code, message);
}
else if (hr->header.content_type && strcmp(hr->header.content_type, "application/json") == 0) {
strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->result_code);
strbuf_json_string(sb, message);
strbuf_puts(sb, " \n}");
}
else {
hr->header.content_type = "text/html";
strbuf_sprintf(sb, "<html><h1>%03u %s</h1></html>", hr->result_code, message);
}
return sb;
}
/* Render the HTTP response into the current response buffer. Return 1 if it fits, 0 if it does
* not. The buffer response_pointer may be NULL, in which case no response is rendered, but the
* content_length is still computed
@ -1833,16 +1850,16 @@ static int _render_response(struct http_request *r)
}
} else {
// If no content is supplied at all, then render a standard, short body based solely on result
// code.
// code, consistent with the response Content-Type if already set (HTML if not set).
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);
strbuf cb = strbuf_alloca(100 + strlen(result_string));
strbuf_sprintf(cb, "<html><h1>%03u %s</h1></html>", hr.result_code, result_string);
strbuf cb;
STRBUF_ALLOCA_FIT(cb, 40 + strlen(result_string), (strbuf_status_body(cb, &hr, result_string)));
hr.content = strbuf_str(cb);
hr.header.content_type = "text/html";
hr.header.resource_length = hr.header.content_length = strbuf_len(cb);
hr.header.content_length = strbuf_len(cb);
hr.header.resource_length = hr.header.content_length;
hr.header.content_range_start = 0;
}
assert(hr.header.content_type != NULL);
@ -2045,25 +2062,21 @@ 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 'body' argument
* may be a bare message which is enclosed in an HTML envelope to form the response content, so it
* may contain HTML markup. If the 'body' argument is NULL, then the response content is generated
* automatically from the result code.
* (2xx), redirection (3xx) or client error (4xx) or server error (5xx) code. The 'message'
* argument may be a bare message which is enclosed in an HTML envelope to form the response
* content, so it may contain HTML markup. If the 'message' argument is NULL, then the response
* content is generated automatically from the result code.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
void http_request_simple_response(struct http_request *r, uint16_t result, const char *body)
void http_request_simple_response(struct http_request *r, uint16_t result, const char *message)
{
assert(r->phase == RECEIVE);
strbuf h = NULL;
if (body) {
size_t html_len = strlen(body) + 40;
h = strbuf_alloca(html_len);
strbuf_sprintf(h, "<html><h1>%03u %s</h1></html>", result, body);
}
r->response.result_code = result;
r->response.header.content_type = "text/html";
r->response.header.content_range_start = 0;
strbuf h = NULL;
if (message)
STRBUF_ALLOCA_FIT(h, 40 + strlen(message), (strbuf_status_body(h, &r->response, message)));
if (h) {
r->response.header.resource_length = r->response.header.content_length = strbuf_len(h);
r->response.content = strbuf_str(h);

View File

@ -414,6 +414,7 @@ static HTTP_CONTENT_GENERATOR restful_rhizome_bundlelist_json_content;
static int restful_rhizome_bundlelist_json(rhizome_http_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
if (!is_rhizome_http_enabled())
return 403;
if (*remainder)
@ -432,6 +433,7 @@ static int restful_rhizome_bundlelist_json(rhizome_http_request *r, const char *
static int restful_rhizome_newsince(rhizome_http_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
if (!is_rhizome_http_enabled())
return 403;
uint64_t rowid;
@ -593,6 +595,7 @@ static int insert_mime_part_body(struct http_request *, char *, size_t);
static int restful_rhizome_insert(rhizome_http_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
if (*remainder)
return 404;
if (!is_rhizome_http_enabled())
@ -964,6 +967,7 @@ static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
static int restful_rhizome_(rhizome_http_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
if (!is_rhizome_http_enabled())
return 403;
HTTP_HANDLER *handler = NULL;

View File

@ -24,6 +24,29 @@ source "${0%/*}/../testdefs_rhizome.sh"
shopt -s extglob
assertJq() {
local json="$1"
local jqscript="$2"
assert --message="$jqscript" --dump-on-fail="$json" [ "$(jq "$jqscript" "$json")" = true ]
}
assertJqGrep() {
local opts=()
while [ $# -gt 0 ]; do
case "$1" in
--) shift; break;;
--*) opts+=("$1"); shift;;
*) break;;
esac
done
[ $# -eq 3 ] || error "invalid arguments"
local json="$1"
local jqscript="$2"
local pattern="$3"
jq "$jqscript" "$json" >"$TFWTMP/jqgrep.tmp"
assertGrep "${opts[@]}" "$TFWTMP/jqgrep.tmp" "$pattern"
}
setup() {
CR=' '
VT=' '
@ -76,6 +99,8 @@ test_AuthBasicMissing() {
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '401'
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$"
assertJq http.output 'contains({"http_status_code": 401})'
assertJq http.output 'contains({"http_status_message": ""})'
}
teardown_AuthBasicMissing() {
tfw_cat http.headers http.output
@ -92,6 +117,8 @@ test_AuthBasicWrong() {
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '401'
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$"
assertJq http.output 'contains({"http_status_code": 401})'
assertJq http.output 'contains({"http_status_message": ""})'
executeOk curl \
--silent --fail --show-error --write-out '%{http_code}' \
--output http.output \
@ -206,12 +233,6 @@ transform_bundlelist_json() {
' "$1" >"$2"
}
assertJq() {
local json="$1"
local jqscript="$2"
assert --message="$jqscript" [ "$(jq "$jqscript" "$json")" = true ]
}
doc_RhizomeList="HTTP RESTful list Rhizome bundles as JSON"
setup_RhizomeList() {
setup
@ -529,7 +550,8 @@ test_RhizomeInsert() {
assertStdoutIs 201
else
assertStdoutIs 403
assertGrep --ignore-case nfile$n.manifest "missing bundle secret"
assertJq nfile$n.manifest 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case nfile$n.manifest '.http_status_message' "missing bundle secret"
fi
done
}
@ -628,7 +650,8 @@ test_RhizomeInsertMissingManifest() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'missing.*manifest.*form.*part'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -650,7 +673,8 @@ test_RhizomeInsertIncorrectManifestType() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'unsupported content-type.*manifest.*form.*part'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported content-type.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -675,7 +699,8 @@ test_RhizomeInsertDuplicateManifest() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'duplicate.*manifest.*form.*part'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -698,7 +723,8 @@ test_RhizomeInsertJournal() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'not supported.*journal'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'not supported.*journal'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -720,7 +746,8 @@ test_RhizomeInsertMissingPayload() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'missing.*payload.*form.*part'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*payload.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -745,7 +772,8 @@ test_RhizomeInsertDuplicatePayload() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'duplicate.*payload.*form.*part'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*payload.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -768,7 +796,8 @@ test_RhizomeInsertPartOrder() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'missing.*manifest.*form.*part'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -792,8 +821,9 @@ test_RhizomeInsertPartUnsupported() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'unsupported.*form.*part'
assertGrep http.body 'happyhappy'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*form.*part'
assertJqGrep http.body '.http_status_message' 'happyhappy'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -818,7 +848,8 @@ test_RhizomeInsertIncorrectFilesize() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'payload size.*contradicts manifest'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'payload size.*contradicts manifest'
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
@ -853,7 +884,8 @@ test_RhizomeInsertIncorrectFilehash() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'payload hash.*contradicts manifest'
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'payload hash.*contradicts manifest'
executeOk_servald rhizome list
assert_rhizome_list
}