From 6a1c8bcf5ac34962d7dff363d507e285e829d9a6 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Mon, 20 Jan 2014 15:44:21 +1030 Subject: [PATCH] All HTTP RESTful responses are JSON, not HTTP Even error responses (typically code 403) --- http_server.c | 47 ++++++++++++++++++++------------ rhizome_http.c | 4 +++ tests/rhizomehttp | 68 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 84 insertions(+), 35 deletions(-) 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 }