Improve MeshMS RESTful HTTP failure reporting

Add "meshms_status_code" element to simple JSON responses
This commit is contained in:
Andrew Bettison 2014-02-07 16:28:40 +10:30
parent 116389b589
commit 0769fa54e8
9 changed files with 155 additions and 59 deletions

View File

@ -64,6 +64,11 @@ static struct {
#undef VERB_ENTRY
};
const char CONTENT_TYPE_TEXT[] = "text/plain";
const char CONTENT_TYPE_HTML[] = "text/html";
const char CONTENT_TYPE_JSON[] = "application/json";
const char CONTENT_TYPE_BLOB[] = "application/octet-stream";
static struct profile_total http_server_stats = {
.name = "http_server_poll",
};
@ -1787,17 +1792,44 @@ 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);
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, message);
if (hr->result_extra_label) {
strbuf_puts(sb, "\r\n");
strbuf_puts(sb, hr->result_extra_label);
strbuf_puts(sb, "=");
strbuf_json_atom_as_text(sb, &hr->result_extra_value);
}
strbuf_puts(sb, "\r\n");
}
else if (hr->header.content_type && strcmp(hr->header.content_type, "application/json") == 0) {
else if ( hr->header.content_type == CONTENT_TYPE_JSON
|| (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, message);
strbuf_puts(sb, " \n}");
if (hr->result_extra_label) {
strbuf_puts(sb, ",\n ");
strbuf_json_string(sb, hr->result_extra_label);
strbuf_puts(sb, ": ");
strbuf_json_atom_as_html(sb, &hr->result_extra_value);
}
strbuf_puts(sb, "\n}");
}
else {
hr->header.content_type = "text/html";
strbuf_sprintf(sb, "<html><h1>%03u %s</h1></html>", hr->result_code, message);
hr->header.content_type = CONTENT_TYPE_HTML;
strbuf_sprintf(sb, "<html>\n<h1>%03u %s</h1>", hr->result_code, message);
if (hr->result_extra_label) {
strbuf_puts(sb, "\n<dl><dt>");
strbuf_html_escape(sb, hr->result_extra_label, strlen(hr->result_extra_label));
strbuf_puts(sb, "</dt><dd>");
strbuf_json_atom_as_html(sb, &hr->result_extra_value);
strbuf_puts(sb, "</dd></dl>");
}
strbuf_puts(sb, "\n</html>");
}
return sb;
}

View File

@ -23,6 +23,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include <limits.h>
#include "constants.h"
#include "strbuf.h"
#include "strbuf_helpers.h"
#include "fdqueue.h"
/* Generic HTTP request handling.
@ -56,6 +57,11 @@ http_size_t http_range_bytes(const struct http_range *range, unsigned nranges);
#define CONTENT_LENGTH_UNKNOWN UINT64_MAX
extern const char CONTENT_TYPE_TEXT[];
extern const char CONTENT_TYPE_HTML[];
extern const char CONTENT_TYPE_JSON[];
extern const char CONTENT_TYPE_BLOB[];
struct mime_content_type {
char type[64];
char subtype[64];
@ -63,7 +69,6 @@ struct mime_content_type {
char charset[31];
};
struct http_client_authorization {
enum http_authorization_scheme { NOAUTH = 0, BASIC } scheme;
union {
@ -105,6 +110,8 @@ typedef int (HTTP_CONTENT_GENERATOR)(struct http_request *, unsigned char *, siz
struct http_response {
uint16_t result_code;
const char *result_extra_label;
struct json_atom result_extra_value;
struct http_response_headers header;
const char *content;
HTTP_CONTENT_GENERATOR *content_generator; // callback to produce more content

View File

@ -455,7 +455,7 @@ static int root_page(httpd_request *r, const char *remainder)
WHY("HTTP Root page buffer overrun");
return 500;
}
http_request_response_static(&r->http, 200, "text/html", temp, strbuf_len(b));
http_request_response_static(&r->http, 200, CONTENT_TYPE_HTML, temp, strbuf_len(b));
return 1;
}
@ -484,7 +484,7 @@ static int neighbour_page(httpd_request *r, const char *remainder)
strbuf_puts(b, "</body></html>");
if (strbuf_overrun(b))
return -1;
http_request_response_static(&r->http, 200, "text/html", buf, strbuf_len(b));
http_request_response_static(&r->http, 200, CONTENT_TYPE_HTML, buf, strbuf_len(b));
return 1;
}
@ -502,6 +502,6 @@ static int interface_page(httpd_request *r, const char *remainder)
strbuf_puts(b, "</body></html>");
if (strbuf_overrun(b))
return -1;
http_request_response_static(&r->http, 200, "text/html", buf, strbuf_len(b));
http_request_response_static(&r->http, 200, CONTENT_TYPE_HTML, buf, strbuf_len(b));
return 1;
}

View File

@ -41,7 +41,7 @@ enum meshms_status {
MESHMS_STATUS_OK = 0, // operation succeeded, no bundle changed
MESHMS_STATUS_UPDATED = 1, // operation succeeded, bundle updated
MESHMS_STATUS_SID_LOCKED = 2, // cannot decode or send messages for that SID
MESHMS_STATUS_PROTOCOL_FAULT = 4, // missing or faulty ply bundle
MESHMS_STATUS_PROTOCOL_FAULT = 3, // missing or faulty ply bundle
};
__MESHMS_INLINE int meshms_failed(enum meshms_status status) {

View File

@ -64,6 +64,50 @@ static int strn_to_meshms_token(const char *str, rhizome_bid_t *bidp, uint64_t *
return 1;
}
static int http_request_meshms_response(struct httpd_request *r, uint16_t result, const char *message, enum meshms_status status)
{
r->http.response.result_extra_label = "meshms_status_code";
r->http.response.result_extra_value.type = JSON_INTEGER;
r->http.response.result_extra_value.u.integer = status;
switch (status) {
case MESHMS_STATUS_OK:
if (!result)
result = 200;
if (!message)
message = "OK";
break;
case MESHMS_STATUS_UPDATED:
if (!result)
result = 201;
if (!message)
message = "Updated";
break;
case MESHMS_STATUS_SID_LOCKED:
if (!result)
result = 403;
if (!message)
message = "Identity unknown";
break;
case MESHMS_STATUS_PROTOCOL_FAULT:
if (!result)
result = 403;
if (!message)
message = "MeshMS protocol fault";
break;
case MESHMS_STATUS_ERROR:
if (!result)
result = 500;
break;
default:
WHYF("Invalid MeshMS status code %d", status);
result = 500;
break;
}
assert(result != 0);
http_request_simple_response(&r->http, result, message);
return result;
}
static HTTP_HANDLER restful_meshms_conversationlist_json;
static HTTP_HANDLER restful_meshms_messagelist_json;
static HTTP_HANDLER restful_meshms_newsince_messagelist_json;
@ -71,7 +115,7 @@ static HTTP_HANDLER restful_meshms_sendmessage;
int restful_meshms_(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 403;
const char *verb = HTTP_VERB_GET;
@ -124,10 +168,11 @@ static int restful_meshms_conversationlist_json(httpd_request *r, const char *re
r->u.mclist.phase = LIST_HEADER;
r->u.mclist.rowcount = 0;
r->u.mclist.conv = NULL;
if (meshms_conversations_list(&r->sid1, NULL, &r->u.mclist.conv))
return -1;
enum meshms_status status;
if (meshms_failed(status = meshms_conversations_list(&r->sid1, NULL, &r->u.mclist.conv)))
return http_request_meshms_response(r, 0, NULL, status);
meshms_conversation_iterator_start(&r->u.mclist.iter, r->u.mclist.conv);
http_request_response_generated(&r->http, 200, "application/json", restful_meshms_conversationlist_json_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_meshms_conversationlist_json_content);
return 1;
}
@ -206,21 +251,21 @@ static int restful_meshms_conversationlist_json_content_chunk(struct http_reques
static HTTP_CONTENT_GENERATOR restful_meshms_messagelist_json_content;
static int reopen_meshms_message_iterator(httpd_request *r)
static enum meshms_status reopen_meshms_message_iterator(httpd_request *r)
{
enum meshms_status status;
if (!meshms_message_iterator_is_open(&r->u.msglist.iter)) {
enum meshms_status status;
if ( meshms_failed(status = meshms_message_iterator_open(&r->u.msglist.iter, &r->sid1, &r->sid2))
|| meshms_failed(status = meshms_message_iterator_prev(&r->u.msglist.iter))
)
return -1;
return status;
r->u.msglist.finished = status != MESHMS_STATUS_UPDATED;
if (!r->u.msglist.finished) {
r->u.msglist.latest_which_ply = r->u.msglist.iter.which_ply;
r->u.msglist.latest_offset = r->u.msglist.iter.offset;
}
}
return 0;
return MESHMS_STATUS_OK;
}
static int restful_meshms_messagelist_json(httpd_request *r, const char *remainder)
@ -233,7 +278,10 @@ static int restful_meshms_messagelist_json(httpd_request *r, const char *remaind
r->u.msglist.phase = LIST_HEADER;
r->u.msglist.token_offset = 0;
r->u.msglist.end_time = 0;
http_request_response_generated(&r->http, 200, "application/json", restful_meshms_messagelist_json_content);
enum meshms_status status;
if (meshms_failed(status = reopen_meshms_message_iterator(r)))
return http_request_meshms_response(r, 0, NULL, status);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_meshms_messagelist_json_content);
return 1;
}
@ -245,19 +293,20 @@ static int restful_meshms_newsince_messagelist_json(httpd_request *r, const char
r->finalise_union = finalise_union_meshms_messagelist;
r->u.msglist.rowcount = 0;
r->u.msglist.phase = LIST_HEADER;
if (reopen_meshms_message_iterator(r) == -1)
return -1;
enum meshms_status status;
if (meshms_failed(status = reopen_meshms_message_iterator(r)))
return http_request_meshms_response(r, 0, NULL, status);
if (cmp_rhizome_bid_t(&r->bid, r->u.msglist.iter.my_ply_bid) == 0)
r->u.msglist.token_which_ply = MY_PLY;
else if (cmp_rhizome_bid_t(&r->bid, r->u.msglist.iter.their_ply_bid) == 0)
r->u.msglist.token_which_ply = THEIR_PLY;
else {
http_request_simple_response(&r->http, 404, "Invalid token");
http_request_simple_response(&r->http, 404, "Unmatched token");
return 404;
}
r->u.msglist.token_offset = r->ui64;
r->u.msglist.end_time = gettime_ms() + config.rhizome.api.restful.newsince_timeout * 1000;
http_request_response_generated(&r->http, 200, "application/json", restful_meshms_messagelist_json_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_meshms_messagelist_json_content);
return 1;
}
@ -266,7 +315,7 @@ static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_meshms_messagelist_json_con
static int restful_meshms_messagelist_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
{
httpd_request *r = (httpd_request *) hr;
if (reopen_meshms_message_iterator(r) == -1)
if (meshms_failed(reopen_meshms_message_iterator(r)))
return -1;
return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_meshms_messagelist_json_content_chunk);
}
@ -403,7 +452,7 @@ static int restful_meshms_messagelist_json_content_chunk(struct http_request *hr
++r->u.msglist.rowcount;
enum meshms_status status;
if (meshms_failed(status = meshms_message_iterator_prev(&r->u.msglist.iter)))
return -1;
return http_request_meshms_response(r, 0, NULL, status);
r->u.msglist.finished = status != MESHMS_STATUS_UPDATED;
}
return 1;
@ -502,21 +551,8 @@ static int restful_meshms_sendmessage_end(struct http_request *hr)
return http_response_form_part(r, "Missing", PART_MESSAGE, NULL, 0);
assert(r->u.sendmsg.message.length > 0);
assert(r->u.sendmsg.message.length <= MESHMS_MESSAGE_MAX_LEN);
enum meshms_status status = meshms_send_message(&r->sid1, &r->sid2, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length);
switch (status) {
case MESHMS_STATUS_ERROR:
return 500;
case MESHMS_STATUS_OK:
case MESHMS_STATUS_UPDATED:
http_request_simple_response(&r->http, 201, "Message sent");
return 201;
case MESHMS_STATUS_SID_LOCKED:
http_request_simple_response(&r->http, 403, "Identity unknown");
return 403;
case MESHMS_STATUS_PROTOCOL_FAULT:
http_request_simple_response(&r->http, 403, "Protocol fault");
return 403;
default:
return 500;
}
enum meshms_status status;
if (meshms_failed(status = meshms_send_message(&r->sid1, &r->sid2, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length)))
return http_request_meshms_response(r, 0, NULL, status);
return http_request_meshms_response(r, 201, "Message sent", status);
}

View File

@ -276,7 +276,7 @@ static int rhizome_direct_addfile_end(struct http_request *hr)
if (config.debug.rhizome)
DEBUGF("Import sans-manifest appeared to succeed");
/* Respond with the manifest that was added. */
http_request_response_static(&r->http, 200, "text/plain", (const char *)m->manifestdata, m->manifest_all_bytes);
http_request_response_static(&r->http, 200, CONTENT_TYPE_TEXT, (const char *)m->manifestdata, m->manifest_all_bytes);
/* clean up after ourselves */
if (mout && mout != m)
rhizome_manifest_free(mout);
@ -516,9 +516,9 @@ void rhizome_direct_http_dispatch(rhizome_direct_sync_request *r)
strbuf_sprintf(content_preamble,
"--%s\r\n"
"Content-Disposition: form-data; name=\"data\"; filename=\"IHAVEs\"\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Type: %s\r\n"
"\r\n",
boundary
boundary, CONTENT_TYPE_BLOB
);
strbuf_sprintf(content_postamble, "\r\n--%s--\r\n", boundary);
assert(!strbuf_overrun(content_preamble));

View File

@ -42,7 +42,7 @@ int rhizome_file_page(httpd_request *r, const char *remainder)
int ret = rhizome_response_content_init_filehash(r, &filehash);
if (ret)
return ret;
http_request_response_generated(&r->http, 200, "application/binary", rhizome_payload_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_BLOB, rhizome_payload_content);
return 1;
}
@ -63,7 +63,7 @@ int manifest_by_prefix_page(httpd_request *r, const char *remainder)
if (ret == -1)
return 500;
if (ret == 0) {
http_request_response_static(&r->http, 200, "application/binary", (const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes);
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, (const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes);
return 1;
}
return 404;
@ -86,6 +86,6 @@ int rhizome_status_page(httpd_request *r, const char *remainder)
strbuf_puts(b, "</body></html>");
if (strbuf_overrun(b))
return -1;
http_request_response_static(&r->http, 200, "text/html", buf, strbuf_len(b));
http_request_response_static(&r->http, 200, CONTENT_TYPE_HTML, buf, strbuf_len(b));
return 1;
}

View File

@ -67,7 +67,7 @@ static HTTP_CONTENT_GENERATOR restful_rhizome_bundlelist_json_content;
int restful_rhizome_bundlelist_json(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 403;
if (*remainder)
@ -80,7 +80,7 @@ int restful_rhizome_bundlelist_json(httpd_request *r, const char *remainder)
r->u.rhlist.phase = LIST_HEADER;
r->u.rhlist.rowcount = 0;
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
http_request_response_generated(&r->http, 200, "application/json", restful_rhizome_bundlelist_json_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_rhizome_bundlelist_json_content);
return 1;
}
@ -99,7 +99,7 @@ static int restful_rhizome_bundlelist_json_content(struct http_request *hr, unsi
int restful_rhizome_newsince(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 403;
uint64_t rowid;
@ -116,7 +116,7 @@ int restful_rhizome_newsince(httpd_request *r, const char *remainder)
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
r->u.rhlist.cursor.rowid_since = rowid;
r->u.rhlist.end_time = gettime_ms() + config.rhizome.api.restful.newsince_timeout * 1000;
http_request_response_generated(&r->http, 200, "application/json", restful_rhizome_bundlelist_json_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_rhizome_bundlelist_json_content);
return 1;
}
@ -241,7 +241,7 @@ static int insert_mime_part_body(struct http_request *, char *, size_t);
int restful_rhizome_insert(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (*remainder)
return 404;
if (!is_rhizome_http_enabled())
@ -561,7 +561,7 @@ static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
int restful_rhizome_(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = "application/json";
r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled())
return 403;
HTTP_HANDLER *handler = NULL;
@ -617,13 +617,13 @@ static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder)
if (*remainder || r->manifest == NULL)
return 404;
if (r->manifest->filesize == 0) {
http_request_response_static(&r->http, 200, "application/binary", "", 0);
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);
return 1;
}
int ret = rhizome_response_content_init_filehash(r, &r->manifest->filehash);
if (ret)
return ret;
http_request_response_generated(&r->http, 200, "application/binary", rhizome_payload_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_BLOB, rhizome_payload_content);
return 1;
}
@ -633,14 +633,14 @@ static int restful_rhizome_bid_decrypted_bin(httpd_request *r, const char *remai
return 404;
if (r->manifest->filesize == 0) {
// TODO use Content Type from manifest (once it is implemented)
http_request_response_static(&r->http, 200, "application/binary", "", 0);
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);
return 1;
}
int ret = rhizome_response_content_init_payload(r, r->manifest);
if (ret)
return ret;
// TODO use Content Type from manifest (once it is implemented)
http_request_response_generated(&r->http, 200, "application/binary", rhizome_payload_content);
http_request_response_generated(&r->http, 200, CONTENT_TYPE_BLOB, rhizome_payload_content);
return 1;
}

View File

@ -1100,6 +1100,26 @@ test_MeshmsListMessages() {
done
}
doc_MeshmsListMessagesNoIdentity="HTTP RESTful list MeshMS messages from unknown identity"
setup_MeshmsListMessagesNoIdentity() {
setup
SIDX=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
}
test_MeshmsListMessagesNoIdentity() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/messagelist.json"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertJq http.body 'contains({"http_status_code": 403})'
assertJq http.body 'contains({"meshms_status_code": 2})'
assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown'
}
doc_MeshmsListMessagesNewSince="HTTP RESTful list MeshMS messages in one conversation since token as JSON"
setup_MeshmsListMessagesNewSince() {
IDENTITY_COUNT=2
@ -1282,6 +1302,7 @@ test_MeshmsSendNoIdentity() {
assertExitStatus == 0
assertStdoutIs 403
assertJq http.body 'contains({"http_status_code": 403})'
assertJq http.body 'contains({"meshms_status_code": 2})'
assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown'
}