diff --git a/http_server.c b/http_server.c
index db0f95e9..a48b42e1 100644
--- a/http_server.c
+++ b/http_server.c
@@ -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, "
%03u %s
", 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, "%03u %s
", 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
*/
-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, "%03u %s
", 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);
diff --git a/rhizome_http.c b/rhizome_http.c
index d2480ae4..1bb0f1e4 100644
--- a/rhizome_http.c
+++ b/rhizome_http.c
@@ -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;
diff --git a/tests/rhizomehttp b/tests/rhizomehttp
index 0714ffe2..3c5c113f 100755
--- a/tests/rhizomehttp
+++ b/tests/rhizomehttp
@@ -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
}