X-Git-Url: https://arthur.barton.de/gitweb/?a=blobdiff_plain;f=src%2Fweb_client.c;h=bd04791317bd730efe51721160fa2aaa5c6c1ca0;hb=655715342478c5da5d43c5a8e619bd214585db4d;hp=d86ca3af8d98ab5c74b2e663c028435a10bec26c;hpb=8b84bdaf6af702c7f87a9b7bf0d5d3709d3f2171;p=netdata.git diff --git a/src/web_client.c b/src/web_client.c index d86ca3af..bd047913 100644 --- a/src/web_client.c +++ b/src/web_client.c @@ -14,7 +14,7 @@ int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRAT struct web_client *web_clients = NULL; unsigned long long web_clients_count = 0; -inline int web_client_crock_socket(struct web_client *w) { +static inline int web_client_crock_socket(struct web_client *w) { #ifdef TCP_CORK if(likely(!w->tcp_cork && w->ofd != -1)) { w->tcp_cork = 1; @@ -29,7 +29,7 @@ inline int web_client_crock_socket(struct web_client *w) { return 0; } -inline int web_client_uncrock_socket(struct web_client *w) { +static inline int web_client_uncrock_socket(struct web_client *w) { #ifdef TCP_CORK if(likely(w->tcp_cork && w->ofd != -1)) { w->tcp_cork = 0; @@ -121,11 +121,11 @@ struct web_client *web_client_create(int listener) void web_client_reset(struct web_client *w) { web_client_uncrock_socket(w); - debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id); + debug(D_WEB_CLIENT, "%llu: Resetting client.", w->id); if(likely(w->last_url[0])) { struct timeval tv; - gettimeofday(&tv, NULL); + now_realtime_timeval(&tv); size_t size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->response.rlen:w->response.data->len; size_t sent = size; @@ -136,7 +136,7 @@ void web_client_reset(struct web_client *w) { // -------------------------------------------------------------------- // global statistics - finished_web_request_statistics(usec_dt(&tv, &w->tv_in), + finished_web_request_statistics(dt_usec(&tv, &w->tv_in), w->stats_received_bytes, w->stats_sent_bytes, size, @@ -152,9 +152,9 @@ void web_client_reset(struct web_client *w) { log_access("%llu: (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %s: %d '%s'", w->id, sent, size, -((size > 0) ? ((size - sent) / (double) size * 100.0) : 0.0), - usec_dt(&w->tv_ready, &w->tv_in) / 1000.0, - usec_dt(&tv, &w->tv_ready) / 1000.0, - usec_dt(&tv, &w->tv_in) / 1000.0, + dt_usec(&w->tv_ready, &w->tv_in) / 1000.0, + dt_usec(&tv, &w->tv_ready) / 1000.0, + dt_usec(&tv, &w->tv_in) / 1000.0, (w->mode == WEB_CLIENT_MODE_FILECOPY) ? "filecopy" : ((w->mode == WEB_CLIENT_MODE_OPTIONS) ? "options" : "data"), w->response.code, @@ -308,7 +308,8 @@ int mysendfile(struct web_client *w, char *filename) for(s = filename; *s ;s++) { if( !isalnum(*s) && *s != '/' && *s != '.' && *s != '-' && *s != '_') { debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); - buffer_sprintf(w->response.data, "File '%s' cannot be served. Filename contains invalid character '%c'", filename, *s); + buffer_sprintf(w->response.data, "Filename contains invalid characters: "); + buffer_strcat_htmlescape(w->response.data, filename); return 400; } } @@ -316,7 +317,8 @@ int mysendfile(struct web_client *w, char *filename) // if the filename contains a .. refuse to serve it if(strstr(filename, "..") != 0) { debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename); - buffer_sprintf(w->response.data, "File '%s' cannot be served. Relative filenames with '..' in them are not supported.", filename); + buffer_strcat(w->response.data, "Relative filenames are not supported: "); + buffer_strcat_htmlescape(w->response.data, filename); return 400; } @@ -328,21 +330,24 @@ int mysendfile(struct web_client *w, char *filename) struct stat stat; if(lstat(webfilename, &stat) != 0) { debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not found.", w->id, webfilename); - buffer_sprintf(w->response.data, "File '%s' does not exist, or is not accessible.", webfilename); + buffer_strcat(w->response.data, "File does not exist, or is not accessible: "); + buffer_strcat_htmlescape(w->response.data, webfilename); return 404; } // check if the file is owned by expected user if(stat.st_uid != web_files_uid()) { error("%llu: File '%s' is owned by user %u (expected user %u). Access Denied.", w->id, webfilename, stat.st_uid, web_files_uid()); - buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); + buffer_strcat(w->response.data, "Access to file is not permitted: "); + buffer_strcat_htmlescape(w->response.data, webfilename); return 403; } // check if the file is owned by expected group if(stat.st_gid != web_files_gid()) { error("%llu: File '%s' is owned by group %u (expected group %u). Access Denied.", w->id, webfilename, stat.st_gid, web_files_gid()); - buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); + buffer_strcat(w->response.data, "Access to file is not permitted: "); + buffer_strcat_htmlescape(w->response.data, webfilename); return 403; } @@ -353,7 +358,8 @@ int mysendfile(struct web_client *w, char *filename) if((stat.st_mode & S_IFMT) != S_IFREG) { error("%llu: File '%s' is not a regular file. Access Denied.", w->id, webfilename); - buffer_sprintf(w->response.data, "Access to file '%s' is not permitted.", webfilename); + buffer_strcat(w->response.data, "Access to file is not permitted: "); + buffer_strcat_htmlescape(w->response.data, webfilename); return 403; } @@ -365,12 +371,14 @@ int mysendfile(struct web_client *w, char *filename) if(errno == EBUSY || errno == EAGAIN) { error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename); buffer_sprintf(w->response.header, "Location: /" WEB_PATH_FILE "/%s\r\n", filename); - buffer_sprintf(w->response.data, "The file '%s' is currently busy. Please try again later.", webfilename); + buffer_strcat(w->response.data, "File is currently busy, please try again later: "); + buffer_strcat_htmlescape(w->response.data, webfilename); return 307; } else { error("%llu: Cannot open file '%s'.", w->id, webfilename); - buffer_sprintf(w->response.data, "Cannot open file '%s'.", webfilename); + buffer_strcat(w->response.data, "Cannot open file: "); + buffer_strcat_htmlescape(w->response.data, webfilename); return 404; } } @@ -406,7 +414,12 @@ int mysendfile(struct web_client *w, char *filename) w->wait_send = 0; buffer_flush(w->response.data); w->response.rlen = stat.st_size; +#ifdef __APPLE__ + w->response.data->date = stat.st_mtimespec.tv_sec; +#else w->response.data->date = stat.st_mtim.tv_sec; +#endif /* __APPLE__ */ + buffer_cacheable(w->response.data); return 200; } @@ -415,7 +428,7 @@ int mysendfile(struct web_client *w, char *filename) #ifdef NETDATA_WITH_ZLIB void web_client_enable_deflate(struct web_client *w, int gzip) { if(unlikely(w->response.zinitialized)) { - error("%llu: Compression has already be initialized for this client.", w->id); + debug(D_DEFLATE, "%llu: Compression has already be initialized for this client.", w->id); return; } @@ -489,7 +502,7 @@ void buffer_data_options2string(BUFFER *wb, uint32_t options) { if(options & RRDR_OPTION_ABSOLUTE) { if(count++) buffer_strcat(wb, " "); - buffer_strcat(wb, "abs"); + buffer_strcat(wb, "absolute"); } if(options & RRDR_OPTION_SECONDS) { @@ -531,29 +544,29 @@ uint32_t web_client_api_request_v1_data_options(char *o) while(o && *o && (tok = mystrsep(&o, ", |"))) { if(!*tok) continue; - if(!strcmp(tok, "nonzero")) + if(!strsame(tok, "nonzero")) ret |= RRDR_OPTION_NONZERO; - else if(!strcmp(tok, "flip") || !strcmp(tok, "reversed") || !strcmp(tok, "reverse")) + else if(!strsame(tok, "flip") || !strsame(tok, "reversed") || !strsame(tok, "reverse")) ret |= RRDR_OPTION_REVERSED; - else if(!strcmp(tok, "jsonwrap")) + else if(!strsame(tok, "jsonwrap")) ret |= RRDR_OPTION_JSON_WRAP; - else if(!strcmp(tok, "min2max")) + else if(!strsame(tok, "min2max")) ret |= RRDR_OPTION_MIN2MAX; - else if(!strcmp(tok, "ms") || !strcmp(tok, "milliseconds")) + else if(!strsame(tok, "ms") || !strsame(tok, "milliseconds")) ret |= RRDR_OPTION_MILLISECONDS; - else if(!strcmp(tok, "abs") || !strcmp(tok, "absolute") || !strcmp(tok, "absolute_sum") || !strcmp(tok, "absolute-sum")) + else if(!strsame(tok, "abs") || !strsame(tok, "absolute") || !strsame(tok, "absolute_sum") || !strsame(tok, "absolute-sum")) ret |= RRDR_OPTION_ABSOLUTE; - else if(!strcmp(tok, "seconds")) + else if(!strsame(tok, "seconds")) ret |= RRDR_OPTION_SECONDS; - else if(!strcmp(tok, "null2zero")) + else if(!strsame(tok, "null2zero")) ret |= RRDR_OPTION_NULL2ZERO; - else if(!strcmp(tok, "objectrows")) + else if(!strsame(tok, "objectrows")) ret |= RRDR_OPTION_OBJECTSROWS; - else if(!strcmp(tok, "google_json")) + else if(!strsame(tok, "google_json")) ret |= RRDR_OPTION_GOOGLE_JSON; - else if(!strcmp(tok, "percentage")) + else if(!strsame(tok, "percentage")) ret |= RRDR_OPTION_PERCENTAGE; - else if(!strcmp(tok, "unaligned")) + else if(!strsame(tok, "unaligned")) ret |= RRDR_OPTION_NOT_ALIGNED; } @@ -562,37 +575,37 @@ uint32_t web_client_api_request_v1_data_options(char *o) uint32_t web_client_api_request_v1_data_format(char *name) { - if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable + if(!strsame(name, DATASOURCE_FORMAT_DATATABLE_JSON)) // datatable return DATASOURCE_DATATABLE_JSON; - else if(!strcmp(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource + else if(!strsame(name, DATASOURCE_FORMAT_DATATABLE_JSONP)) // datasource return DATASOURCE_DATATABLE_JSONP; - else if(!strcmp(name, DATASOURCE_FORMAT_JSON)) // json + else if(!strsame(name, DATASOURCE_FORMAT_JSON)) // json return DATASOURCE_JSON; - else if(!strcmp(name, DATASOURCE_FORMAT_JSONP)) // jsonp + else if(!strsame(name, DATASOURCE_FORMAT_JSONP)) // jsonp return DATASOURCE_JSONP; - else if(!strcmp(name, DATASOURCE_FORMAT_SSV)) // ssv + else if(!strsame(name, DATASOURCE_FORMAT_SSV)) // ssv return DATASOURCE_SSV; - else if(!strcmp(name, DATASOURCE_FORMAT_CSV)) // csv + else if(!strsame(name, DATASOURCE_FORMAT_CSV)) // csv return DATASOURCE_CSV; - else if(!strcmp(name, DATASOURCE_FORMAT_TSV) || !strcmp(name, "tsv-excel")) // tsv + else if(!strsame(name, DATASOURCE_FORMAT_TSV) || !strsame(name, "tsv-excel")) // tsv return DATASOURCE_TSV; - else if(!strcmp(name, DATASOURCE_FORMAT_HTML)) // html + else if(!strsame(name, DATASOURCE_FORMAT_HTML)) // html return DATASOURCE_HTML; - else if(!strcmp(name, DATASOURCE_FORMAT_JS_ARRAY)) // array + else if(!strsame(name, DATASOURCE_FORMAT_JS_ARRAY)) // array return DATASOURCE_JS_ARRAY; - else if(!strcmp(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma + else if(!strsame(name, DATASOURCE_FORMAT_SSV_COMMA)) // ssvcomma return DATASOURCE_SSV_COMMA; - else if(!strcmp(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray + else if(!strsame(name, DATASOURCE_FORMAT_CSV_JSON_ARRAY)) // csvjsonarray return DATASOURCE_CSV_JSON_ARRAY; return DATASOURCE_JSON; @@ -600,16 +613,16 @@ uint32_t web_client_api_request_v1_data_format(char *name) uint32_t web_client_api_request_v1_data_google_format(char *name) { - if(!strcmp(name, "json")) + if(!strsame(name, "json")) return DATASOURCE_DATATABLE_JSONP; - else if(!strcmp(name, "html")) + else if(!strsame(name, "html")) return DATASOURCE_HTML; - else if(!strcmp(name, "csv")) + else if(!strsame(name, "csv")) return DATASOURCE_CSV; - else if(!strcmp(name, "tsv-excel")) + else if(!strsame(name, "tsv-excel")) return DATASOURCE_TSV; return DATASOURCE_JSON; @@ -642,19 +655,19 @@ const char *group_method2string(int group) { int web_client_api_request_v1_data_group(char *name, int def) { - if(!strcmp(name, "average")) + if(!strsame(name, "average")) return GROUP_AVERAGE; - else if(!strcmp(name, "min")) + else if(!strsame(name, "min")) return GROUP_MIN; - else if(!strcmp(name, "max")) + else if(!strsame(name, "max")) return GROUP_MAX; - else if(!strcmp(name, "sum")) + else if(!strsame(name, "sum")) return GROUP_SUM; - else if(!strcmp(name, "incremental-sum")) + else if(!strsame(name, "incremental-sum")) return GROUP_INCREMENTAL_SUM; return def; @@ -668,8 +681,8 @@ int web_client_api_request_v1_alarms(struct web_client *w, char *url) char *value = mystrsep(&url, "?&"); if (!value || !*value) continue; - if(!strcmp(value, "all")) all = 1; - else if(!strcmp(value, "active")) all = 0; + if(!strsame(value, "all")) all = 1; + else if(!strsame(value, "active")) all = 0; } buffer_flush(w->response.data); @@ -690,7 +703,7 @@ int web_client_api_request_v1_alarm_log(struct web_client *w, char *url) if(!name || !*name) continue; if(!value || !*value) continue; - if(!strcmp(name, "after")) after = strtoul(value, NULL, 0); + if(!strsame(name, "after")) after = strtoul(value, NULL, 0); } buffer_flush(w->response.data); @@ -699,17 +712,7 @@ int web_client_api_request_v1_alarm_log(struct web_client *w, char *url) return 200; } -int web_client_api_request_v1_charts(struct web_client *w, char *url) -{ - (void)url; - - buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; - rrd_stats_api_v1_charts(w->response.data); - return 200; -} - -int web_client_api_request_v1_chart(struct web_client *w, char *url) +int web_client_api_request_single_chart(struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf)) { int ret = 400; char *chart = NULL; @@ -727,7 +730,7 @@ int web_client_api_request_v1_chart(struct web_client *w, char *url) // name and value are now the parameters // they are not null and not empty - if(!strcmp(name, "chart")) chart = value; + if(!strsame(name, "chart")) chart = value; //else { /// buffer_sprintf(w->response.data, "Unknown parameter '%s' in request.", name); // goto cleanup; @@ -742,25 +745,87 @@ int web_client_api_request_v1_chart(struct web_client *w, char *url) RRDSET *st = rrdset_find(chart); if(!st) st = rrdset_find_byname(chart); if(!st) { - buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart); + buffer_strcat(w->response.data, "Chart is not found: "); + buffer_strcat_htmlescape(w->response.data, chart); ret = 404; goto cleanup; } w->response.data->contenttype = CT_APPLICATION_JSON; - rrd_stats_api_v1_chart(st, w->response.data); + callback(st, w->response.data); return 200; -cleanup: + cleanup: return ret; } +int web_client_api_request_v1_alarm_variables(struct web_client *w, char *url) +{ + return web_client_api_request_single_chart(w, url, health_api_v1_chart_variables2json); +} + +int web_client_api_request_v1_charts(struct web_client *w, char *url) +{ + (void)url; + + buffer_flush(w->response.data); + w->response.data->contenttype = CT_APPLICATION_JSON; + rrd_stats_api_v1_charts(w->response.data); + return 200; +} + +int web_client_api_request_v1_allmetrics(struct web_client *w, char *url) +{ + int format = ALLMETRICS_SHELL; + + while(url) { + char *value = mystrsep(&url, "?&"); + if (!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + if(!strsame(name, "format")) { + if(!strsame(value, ALLMETRICS_FORMAT_SHELL)) + format = ALLMETRICS_SHELL; + else if(!strsame(value, ALLMETRICS_FORMAT_PROMETHEUS)) + format = ALLMETRICS_PROMETHEUS; + else + format = 0; + } + } + + buffer_flush(w->response.data); + buffer_no_cacheable(w->response.data); + + switch(format) { + case ALLMETRICS_SHELL: + w->response.data->contenttype = CT_TEXT_PLAIN; + rrd_stats_api_v1_charts_allmetrics_shell(w->response.data); + return 200; + + case ALLMETRICS_PROMETHEUS: + w->response.data->contenttype = CT_PROMETHEUS; + rrd_stats_api_v1_charts_allmetrics_prometheus(w->response.data); + return 200; + + default: + w->response.data->contenttype = CT_TEXT_PLAIN; + buffer_strcat(w->response.data, "Which format? Only '" ALLMETRICS_FORMAT_SHELL "' and '" ALLMETRICS_FORMAT_PROMETHEUS "' is currently supported."); + return 400; + } +} + +int web_client_api_request_v1_chart(struct web_client *w, char *url) +{ + return web_client_api_request_single_chart(w, url, rrd_stats_api_v1_chart); +} + int web_client_api_request_v1_badge(struct web_client *w, char *url) { int ret = 400; buffer_flush(w->response.data); - w->response.data->options |= WB_CONTENT_NO_CACHEABLE; - BUFFER *dimensions = NULL; const char *chart = NULL @@ -793,35 +858,36 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) { // name and value are now the parameters // they are not null and not empty - if(!strcmp(name, "chart")) chart = value; - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { + if(!strsame(name, "chart")) chart = value; + else if(!strsame(name, "dimension") || !strsame(name, "dim") || !strsame(name, "dimensions") || !strsame(name, "dims")) { if(!dimensions) dimensions = buffer_create(100); buffer_strcat(dimensions, "|"); buffer_strcat(dimensions, value); } - else if(!strcmp(name, "after")) after_str = value; - else if(!strcmp(name, "before")) before_str = value; - else if(!strcmp(name, "points")) points_str = value; - else if(!strcmp(name, "group")) { + else if(!strsame(name, "after")) after_str = value; + else if(!strsame(name, "before")) before_str = value; + else if(!strsame(name, "points")) points_str = value; + else if(!strsame(name, "group")) { group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE); } - else if(!strcmp(name, "options")) { + else if(!strsame(name, "options")) { options |= web_client_api_request_v1_data_options(value); } - else if(!strcmp(name, "label")) label = value; - else if(!strcmp(name, "units")) units = value; - else if(!strcmp(name, "label_color")) label_color = value; - else if(!strcmp(name, "value_color")) value_color = value; - else if(!strcmp(name, "multiply")) multiply_str = value; - else if(!strcmp(name, "divide")) divide_str = value; - else if(!strcmp(name, "refresh")) refresh_str = value; - else if(!strcmp(name, "precision")) precision_str = value; - else if(!strcmp(name, "alarm")) alarm = value; + else if(!strsame(name, "label")) label = value; + else if(!strsame(name, "units")) units = value; + else if(!strsame(name, "label_color")) label_color = value; + else if(!strsame(name, "value_color")) value_color = value; + else if(!strsame(name, "multiply")) multiply_str = value; + else if(!strsame(name, "divide")) divide_str = value; + else if(!strsame(name, "refresh")) refresh_str = value; + else if(!strsame(name, "precision")) precision_str = value; + else if(!strsame(name, "alarm")) alarm = value; } if(!chart || !*chart) { + buffer_no_cacheable(w->response.data); buffer_sprintf(w->response.data, "No chart id is given at the request."); goto cleanup; } @@ -829,6 +895,7 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) { RRDSET *st = rrdset_find(chart); if(!st) st = rrdset_find_byname(chart); if(!st) { + buffer_no_cacheable(w->response.data); buffer_svg(w->response.data, "chart not found", 0, "", NULL, NULL, 1, -1); ret = 200; goto cleanup; @@ -838,35 +905,36 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) { if(alarm) { rc = rrdcalc_find(st, alarm); if (!rc) { + buffer_no_cacheable(w->response.data); buffer_svg(w->response.data, "alarm not found", 0, "", NULL, NULL, 1, -1); ret = 200; goto cleanup; } } - long long multiply = (multiply_str && *multiply_str )?atol(multiply_str):1; - long long divide = (divide_str && *divide_str )?atol(divide_str):1; - long long before = (before_str && *before_str )?atol(before_str):0; - long long after = (after_str && *after_str )?atol(after_str):-st->update_every; - int points = (points_str && *points_str )?atoi(points_str):1; - int precision = (precision_str && *precision_str)?atoi(precision_str):-1; + long long multiply = (multiply_str && *multiply_str )?str2l(multiply_str):1; + long long divide = (divide_str && *divide_str )?str2l(divide_str):1; + long long before = (before_str && *before_str )?str2l(before_str):0; + long long after = (after_str && *after_str )?str2l(after_str):-st->update_every; + int points = (points_str && *points_str )?str2i(points_str):1; + int precision = (precision_str && *precision_str)?str2i(precision_str):-1; if(!multiply) multiply = 1; if(!divide) divide = 1; int refresh = 0; if(refresh_str && *refresh_str) { - if(!strcmp(refresh_str, "auto")) { + if(!strsame(refresh_str, "auto")) { if(rc) refresh = rc->update_every; else if(options & RRDR_OPTION_NOT_ALIGNED) refresh = st->update_every; else { - refresh = (before - after); + refresh = (int)(before - after); if(refresh < 0) refresh = -refresh; } } else { - refresh = atoi(refresh_str); + refresh = str2i(refresh_str); if(refresh < 0) refresh = -refresh; } } @@ -917,8 +985,11 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) { calculated_number n = rc->value; if(isnan(n) || isinf(n)) n = 0; - if (refresh > 0) + if (refresh > 0) { buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh); + w->response.data->expires = now_realtime_sec() + refresh; + } + else buffer_no_cacheable(w->response.data); if(!value_color) { switch(rc->status) { @@ -948,7 +1019,14 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) { } } - buffer_svg(w->response.data, label, rc->value * multiply / divide, units, label_color, value_color, 0, precision); + buffer_svg(w->response.data, + label, + rc->value * multiply / divide, + units, + label_color, + value_color, + 0, + precision); ret = 200; } else { @@ -958,21 +1036,41 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) { ret = 500; // if the collected value is too old, don't calculate its value - if (rrdset_last_entry_t(st) >= (time(NULL) - (st->update_every * st->gap_when_lost_iterations_above))) - ret = rrd2value(st, w->response.data, &n, (dimensions) ? buffer_tostring(dimensions) : NULL, points, after, - before, group, options, NULL, &latest_timestamp, &value_is_null); + if (rrdset_last_entry_t(st) >= (now_realtime_sec() - (st->update_every * st->gap_when_lost_iterations_above))) + ret = rrd2value(st, + w->response.data, + &n, + (dimensions) ? buffer_tostring(dimensions) : NULL, + points, + after, + before, + group, + options, + NULL, + &latest_timestamp, + &value_is_null); // if the value cannot be calculated, show empty badge if (ret != 200) { + buffer_no_cacheable(w->response.data); value_is_null = 1; n = 0; ret = 200; } - else if (refresh > 0) + else if (refresh > 0) { buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh); + w->response.data->expires = now_realtime_sec() + refresh; + } + else buffer_no_cacheable(w->response.data); // render the badge - buffer_svg(w->response.data, label, n * multiply / divide, units, label_color, value_color, value_is_null, + buffer_svg(w->response.data, + label, + n * multiply / divide, + units, + label_color, + value_color, + value_is_null, precision); } @@ -1023,31 +1121,31 @@ int web_client_api_request_v1_data(struct web_client *w, char *url) // name and value are now the parameters // they are not null and not empty - if(!strcmp(name, "chart")) chart = value; - else if(!strcmp(name, "dimension") || !strcmp(name, "dim") || !strcmp(name, "dimensions") || !strcmp(name, "dims")) { + if(!strsame(name, "chart")) chart = value; + else if(!strsame(name, "dimension") || !strsame(name, "dim") || !strsame(name, "dimensions") || !strsame(name, "dims")) { if(!dimensions) dimensions = buffer_create(100); buffer_strcat(dimensions, "|"); buffer_strcat(dimensions, value); } - else if(!strcmp(name, "after")) after_str = value; - else if(!strcmp(name, "before")) before_str = value; - else if(!strcmp(name, "points")) points_str = value; - else if(!strcmp(name, "group")) { + else if(!strsame(name, "after")) after_str = value; + else if(!strsame(name, "before")) before_str = value; + else if(!strsame(name, "points")) points_str = value; + else if(!strsame(name, "group")) { group = web_client_api_request_v1_data_group(value, GROUP_AVERAGE); } - else if(!strcmp(name, "format")) { + else if(!strsame(name, "format")) { format = web_client_api_request_v1_data_format(value); } - else if(!strcmp(name, "options")) { + else if(!strsame(name, "options")) { options |= web_client_api_request_v1_data_options(value); } - else if(!strcmp(name, "callback")) { + else if(!strsame(name, "callback")) { responseHandler = value; } - else if(!strcmp(name, "filename")) { + else if(!strsame(name, "filename")) { outFileName = value; } - else if(!strcmp(name, "tqx")) { + else if(!strsame(name, "tqx")) { // parse Google Visualization API options // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source char *tqx_name, *tqx_value; @@ -1060,21 +1158,21 @@ int web_client_api_request_v1_data(struct web_client *w, char *url) if(!tqx_name || !*tqx_name) continue; if(!tqx_value || !*tqx_value) continue; - if(!strcmp(tqx_name, "version")) + if(!strsame(tqx_name, "version")) google_version = tqx_value; - else if(!strcmp(tqx_name, "reqId")) + else if(!strsame(tqx_name, "reqId")) google_reqId = tqx_value; - else if(!strcmp(tqx_name, "sig")) { + else if(!strsame(tqx_name, "sig")) { google_sig = tqx_value; google_timestamp = strtoul(google_sig, NULL, 0); } - else if(!strcmp(tqx_name, "out")) { + else if(!strsame(tqx_name, "out")) { google_out = tqx_value; format = web_client_api_request_v1_data_google_format(google_out); } - else if(!strcmp(tqx_name, "responseHandler")) + else if(!strsame(tqx_name, "responseHandler")) responseHandler = tqx_value; - else if(!strcmp(tqx_name, "outFileName")) + else if(!strsame(tqx_name, "outFileName")) outFileName = tqx_value; } } @@ -1088,14 +1186,15 @@ int web_client_api_request_v1_data(struct web_client *w, char *url) RRDSET *st = rrdset_find(chart); if(!st) st = rrdset_find_byname(chart); if(!st) { - buffer_sprintf(w->response.data, "Chart '%s' is not found.", chart); + buffer_strcat(w->response.data, "Chart is not found: "); + buffer_strcat_htmlescape(w->response.data, chart); ret = 404; goto cleanup; } - long long before = (before_str && *before_str)?atol(before_str):0; - long long after = (after_str && *after_str) ?atol(after_str):0; - int points = (points_str && *points_str)?atoi(points_str):0; + long long before = (before_str && *before_str)?str2l(before_str):0; + long long after = (after_str && *after_str) ?str2l(after_str):0; + int points = (points_str && *points_str)?str2i(points_str):0; debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%d', format '%u', options '0x%08x'" , w->id @@ -1157,8 +1256,6 @@ cleanup: } -#define REGISTRY_VERIFY_COOKIES_GUID "give-me-back-this-cookie-now--please" - int web_client_api_request_v1_registry(struct web_client *w, char *url) { static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0, @@ -1183,7 +1280,7 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url) */ } - char person_guid[36 + 1] = ""; + char person_guid[GUID_LEN + 1] = ""; debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url); @@ -1217,42 +1314,42 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url) uint32_t hash = simple_hash(name); - if(hash == hash_action && !strcmp(name, "action")) { + if(hash == hash_action && !strsame(name, "action")) { uint32_t vhash = simple_hash(value); - if(vhash == hash_access && !strcmp(value, "access")) action = 'A'; - else if(vhash == hash_hello && !strcmp(value, "hello")) action = 'H'; - else if(vhash == hash_delete && !strcmp(value, "delete")) action = 'D'; - else if(vhash == hash_search && !strcmp(value, "search")) action = 'S'; - else if(vhash == hash_switch && !strcmp(value, "switch")) action = 'W'; + if(vhash == hash_access && !strsame(value, "access")) action = 'A'; + else if(vhash == hash_hello && !strsame(value, "hello")) action = 'H'; + else if(vhash == hash_delete && !strsame(value, "delete")) action = 'D'; + else if(vhash == hash_search && !strsame(value, "search")) action = 'S'; + else if(vhash == hash_switch && !strsame(value, "switch")) action = 'W'; #ifdef NETDATA_INTERNAL_CHECKS else error("unknown registry action '%s'", value); #endif /* NETDATA_INTERNAL_CHECKS */ } /* - else if(hash == hash_redirects && !strcmp(name, "redirects")) + else if(hash == hash_redirects && !strsame(name, "redirects")) redirects = atoi(value); */ - else if(hash == hash_machine && !strcmp(name, "machine")) + else if(hash == hash_machine && !strsame(name, "machine")) machine_guid = value; - else if(hash == hash_url && !strcmp(name, "url")) + else if(hash == hash_url && !strsame(name, "url")) machine_url = value; else if(action == 'A') { - if(hash == hash_name && !strcmp(name, "name")) + if(hash == hash_name && !strsame(name, "name")) url_name = value; } else if(action == 'D') { - if(hash == hash_delete_url && !strcmp(name, "delete_url")) + if(hash == hash_delete_url && !strsame(name, "delete_url")) delete_url = value; } else if(action == 'S') { - if(hash == hash_for && !strcmp(name, "for")) + if(hash == hash_for && !strsame(name, "for")) search_machine_guid = value; } else if(action == 'W') { - if(hash == hash_to && !strcmp(name, "to")) + if(hash == hash_to && !strsame(name, "to")) to_person_guid = value; } #ifdef NETDATA_INTERNAL_CHECKS @@ -1267,100 +1364,50 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url) } if(action == 'A' && (!machine_guid || !machine_url || !url_name)) { + error("Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", + machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET"); buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Invalid registry request - access requires these parameters: machine ('%s'), url ('%s'), name ('%s')", - machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", url_name?url_name:"UNSET"); + buffer_strcat(w->response.data, "Invalid registry Access request."); return 400; } else if(action == 'D' && (!machine_guid || !machine_url || !delete_url)) { + error("Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", + machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET"); buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Invalid registry request - delete requires these parameters: machine ('%s'), url ('%s'), delete_url ('%s')", - machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", delete_url?delete_url:"UNSET"); + buffer_strcat(w->response.data, "Invalid registry Delete request."); return 400; } else if(action == 'S' && (!machine_guid || !machine_url || !search_machine_guid)) { + error("Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')", + machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET"); buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Invalid registry request - search requires these parameters: machine ('%s'), url ('%s'), for ('%s')", - machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", search_machine_guid?search_machine_guid:"UNSET"); + buffer_strcat(w->response.data, "Invalid registry Search request."); return 400; } else if(action == 'W' && (!machine_guid || !machine_url || !to_person_guid)) { + error("Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", + machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET"); buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Invalid registry request - switching identity requires these parameters: machine ('%s'), url ('%s'), to ('%s')", - machine_guid?machine_guid:"UNSET", machine_url?machine_url:"UNSET", to_person_guid?to_person_guid:"UNSET"); + buffer_strcat(w->response.data, "Invalid registry Switch request."); return 400; } switch(action) { case 'A': w->tracking_required = 1; - if(registry_verify_cookies_redirects() > 0 && (!cookie || !person_guid[0])) { - buffer_flush(w->response.data); - registry_set_cookie(w, REGISTRY_VERIFY_COOKIES_GUID); - w->response.data->contenttype = CT_APPLICATION_JSON; - buffer_sprintf(w->response.data, "{ \"status\": \"redirect\", \"registry\": \"%s\" }", registry_to_announce()); - return 200; - -/* - * it seems that web browsers are ignoring 307 (Moved Temporarily) - * under certain conditions, when using CORS - * so this is commented and we use application level redirects instead - * - redirects++; - - if(redirects > registry_verify_cookies_redirects()) { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Your browser does not support cookies"); - return 400; - } - - char *encoded_url = url_encode(machine_url); - if(!encoded_url) { - error("%llu: Cannot URL encode string '%s'", w->id, machine_url); - return 500; - } - - char *encoded_name = url_encode(url_name); - if(!encoded_name) { - free(encoded_url); - error("%llu: Cannot URL encode string '%s'", w->id, url_name); - return 500; - } - - char *encoded_guid = url_encode(machine_guid); - if(!encoded_guid) { - free(encoded_url); - free(encoded_name); - error("%llu: Cannot URL encode string '%s'", w->id, machine_guid); - return 500; - } - - buffer_sprintf(w->response.header, "Location: %s/api/v1/registry?action=access&machine=%s&name=%s&url=%s&redirects=%d\r\n", - registry_to_announce(), encoded_guid, encoded_name, encoded_url, redirects); - - free(encoded_guid); - free(encoded_name); - free(encoded_url); - return 307 -*/ - } - - if(unlikely(cookie && person_guid[0] && !strcmp(person_guid, REGISTRY_VERIFY_COOKIES_GUID))) - person_guid[0] = '\0'; - - return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL)); + return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, now_realtime_sec()); case 'D': w->tracking_required = 1; - return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, time(NULL)); + return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, now_realtime_sec()); case 'S': w->tracking_required = 1; - return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, time(NULL)); + return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, now_realtime_sec()); case 'W': w->tracking_required = 1; - return registry_request_switch_json(w, person_guid, machine_guid, machine_url, to_person_guid, time(NULL)); + return registry_request_switch_json(w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec()); case 'H': return registry_request_hello_json(w); @@ -1373,7 +1420,7 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url) } int web_client_api_request_v1(struct web_client *w, char *url) { - static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0, hash_alarms = 0, hash_alarm_log = 0; + static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0, hash_alarms = 0, hash_alarm_log = 0, hash_alarm_variables = 0, hash_raw = 0; if(unlikely(hash_data == 0)) { hash_data = simple_hash("data"); @@ -1383,6 +1430,8 @@ int web_client_api_request_v1(struct web_client *w, char *url) { hash_badge = simple_hash("badge.svg"); hash_alarms = simple_hash("alarms"); hash_alarm_log = simple_hash("alarm_log"); + hash_alarm_variables = simple_hash("alarm_variables"); + hash_raw = simple_hash("allmetrics"); } // get the command @@ -1391,36 +1440,43 @@ int web_client_api_request_v1(struct web_client *w, char *url) { debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok); uint32_t hash = simple_hash(tok); - if(hash == hash_data && !strcmp(tok, "data")) + if(hash == hash_data && !strsame(tok, "data")) return web_client_api_request_v1_data(w, url); - else if(hash == hash_chart && !strcmp(tok, "chart")) + else if(hash == hash_chart && !strsame(tok, "chart")) return web_client_api_request_v1_chart(w, url); - else if(hash == hash_charts && !strcmp(tok, "charts")) + else if(hash == hash_charts && !strsame(tok, "charts")) return web_client_api_request_v1_charts(w, url); - else if(hash == hash_registry && !strcmp(tok, "registry")) + else if(hash == hash_registry && !strsame(tok, "registry")) return web_client_api_request_v1_registry(w, url); - else if(hash == hash_badge && !strcmp(tok, "badge.svg")) + else if(hash == hash_badge && !strsame(tok, "badge.svg")) return web_client_api_request_v1_badge(w, url); - else if(hash == hash_alarms && !strcmp(tok, "alarms")) + else if(hash == hash_alarms && !strsame(tok, "alarms")) return web_client_api_request_v1_alarms(w, url); - else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log")) + else if(hash == hash_alarm_log && !strsame(tok, "alarm_log")) return web_client_api_request_v1_alarm_log(w, url); + else if(hash == hash_alarm_variables && !strsame(tok, "alarm_variables")) + return web_client_api_request_v1_alarm_variables(w, url); + + else if(hash == hash_raw && !strsame(tok, "allmetrics")) + return web_client_api_request_v1_allmetrics(w, url); + else { buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok); + buffer_strcat(w->response.data, "Unsupported v1 API command: "); + buffer_strcat_htmlescape(w->response.data, tok); return 404; } } else { buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "API v1 command?"); + buffer_sprintf(w->response.data, "Which API v1 command?"); return 400; } } @@ -1431,11 +1487,12 @@ int web_client_api_request(struct web_client *w, char *url) char *tok = mystrsep(&url, "/?&"); if(tok && *tok) { debug(D_WEB_CLIENT, "%llu: Searching for API version '%s'.", w->id, tok); - if(strcmp(tok, "v1") == 0) + if(strsame(tok, "v1") == 0) return web_client_api_request_v1(w, url); else { buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Unsupported API version: %s", tok); + buffer_strcat(w->response.data, "Unsupported API version: "); + buffer_strcat_htmlescape(w->response.data, tok); return 404; } } @@ -1448,6 +1505,12 @@ int web_client_api_request(struct web_client *w, char *url) int web_client_api_old_data_request(struct web_client *w, char *url, int datasource_type) { + if(!url || !*url) { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Incomplete request."); + return 400; + } + RRDSET *st = NULL; char *args = strchr(url, '?'); @@ -1487,13 +1550,13 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou if(url) { // parse the lines required tok = mystrsep(&url, "/"); - if(tok) lines = atoi(tok); + if(tok) lines = str2i(tok); if(lines < 1) lines = 1; } if(url) { // parse the group count required tok = mystrsep(&url, "/"); - if(tok && *tok) group_count = atoi(tok); + if(tok && *tok) group_count = str2i(tok); if(group_count < 1) group_count = 1; //if(group_count > save_history / 20) group_count = save_history / 20; } @@ -1501,28 +1564,28 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou // parse the grouping method required tok = mystrsep(&url, "/"); if(tok && *tok) { - if(strcmp(tok, "max") == 0) group_method = GROUP_MAX; - else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE; - else if(strcmp(tok, "sum") == 0) group_method = GROUP_SUM; + if(strsame(tok, "max") == 0) group_method = GROUP_MAX; + else if(strsame(tok, "average") == 0) group_method = GROUP_AVERAGE; + else if(strsame(tok, "sum") == 0) group_method = GROUP_SUM; else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok); } } if(url) { // parse after time tok = mystrsep(&url, "/"); - if(tok && *tok) after = strtoul(tok, NULL, 10); + if(tok && *tok) after = str2ul(tok); if(after < 0) after = 0; } if(url) { // parse before time tok = mystrsep(&url, "/"); - if(tok && *tok) before = strtoul(tok, NULL, 10); + if(tok && *tok) before = str2ul(tok); if(before < 0) before = 0; } if(url) { // parse nonzero tok = mystrsep(&url, "/"); - if(tok && *tok && strcmp(tok, "nonzero") == 0) nonzero = 1; + if(tok && *tok && strsame(tok, "nonzero") == 0) nonzero = 1; } w->response.data->contenttype = CT_APPLICATION_JSON; @@ -1543,26 +1606,26 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou tok = mystrsep(&args, "&"); if(tok && *tok) { char *name = mystrsep(&tok, "="); - if(name && *name && strcmp(name, "tqx") == 0) { + if(name && *name && strsame(name, "tqx") == 0) { char *key = mystrsep(&tok, ":"); char *value = mystrsep(&tok, ";"); if(key && value && *key && *value) { - if(strcmp(key, "version") == 0) + if(strsame(key, "version") == 0) google_version = value; - else if(strcmp(key, "reqId") == 0) + else if(strsame(key, "reqId") == 0) google_reqId = value; - else if(strcmp(key, "sig") == 0) + else if(strsame(key, "sig") == 0) google_sig = value; - else if(strcmp(key, "out") == 0) + else if(strsame(key, "out") == 0) google_out = value; - else if(strcmp(key, "responseHandler") == 0) + else if(strsame(key, "responseHandler") == 0) google_responseHandler = value; - else if(strcmp(key, "outFileName") == 0) + else if(strsame(key, "outFileName") == 0) google_outFileName = value; } } @@ -1577,7 +1640,7 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou last_timestamp_in_data = strtoul(google_sig, NULL, 0); // check the client wants json - if(strcmp(google_out, "json") != 0) { + if(strsame(google_out, "json") != 0) { buffer_sprintf(w->response.data, "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'invalid_query',message:'output format is not supported',detailed_message:'the format %s requested is not supported by netdata.'}]});", google_responseHandler, google_version, google_reqId, google_out); @@ -1675,6 +1738,9 @@ const char *web_content_type_to_string(uint8_t contenttype) { case CT_IMAGE_ICNS: return "image/icns"; + case CT_PROMETHEUS: + return "text/plain; version=0.0.4"; + default: case CT_TEXT_PLAIN: return "text/plain; charset=utf-8"; @@ -1871,11 +1937,21 @@ static inline int http_request_validate(struct web_client *w) { } void web_client_process(struct web_client *w) { - static uint32_t hash_api = 0, hash_netdata_conf = 0, hash_data = 0, hash_datasource = 0, hash_graph = 0, - hash_list = 0, hash_all_json = 0, hash_exit = 0, hash_debug = 0, hash_mirror = 0; + static uint32_t + hash_api = 0, + hash_netdata_conf = 0, + hash_data = 0, + hash_datasource = 0, + hash_graph = 0, + hash_list = 0, + hash_all_json = 0; + +#ifdef NETDATA_INTERNAL_CHECKS + static uint32_t hash_exit = 0, hash_debug = 0, hash_mirror = 0; +#endif // start timing us - gettimeofday(&w->tv_in, NULL); + now_realtime_timeval(&w->tv_in); if(unlikely(!hash_api)) { hash_api = simple_hash("api"); @@ -1885,9 +1961,11 @@ void web_client_process(struct web_client *w) { hash_graph = simple_hash(WEB_PATH_GRAPH); hash_list = simple_hash("list"); hash_all_json = simple_hash("all.json"); +#ifdef NETDATA_INTERNAL_CHECKS hash_exit = simple_hash("exit"); hash_debug = simple_hash("debug"); hash_mirror = simple_hash("mirror"); +#endif } int code = 500; @@ -1934,11 +2012,11 @@ void web_client_process(struct web_client *w) { uint32_t hash = simple_hash(tok); debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok); - if(hash == hash_api && strcmp(tok, "api") == 0) { + if(hash == hash_api && strsame(tok, "api") == 0) { // the client is requesting api access code = web_client_api_request(w, url); } - else if(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0) { + else if(hash == hash_netdata_conf && strsame(tok, "netdata.conf") == 0) { code = 200; debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id); @@ -1946,15 +2024,15 @@ void web_client_process(struct web_client *w) { buffer_flush(w->response.data); generate_config(w->response.data, 0); } - else if(hash == hash_data && strcmp(tok, WEB_PATH_DATA) == 0) { // "data" + else if(hash == hash_data && strsame(tok, WEB_PATH_DATA) == 0) { // "data" // the client is requesting rrd data -- OLD API code = web_client_api_old_data_request(w, url, DATASOURCE_JSON); } - else if(hash == hash_datasource && strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource" + else if(hash == hash_datasource && strsame(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource" // the client is requesting google datasource -- OLD API code = web_client_api_old_data_request(w, url, DATASOURCE_DATATABLE_JSONP); } - else if(hash == hash_graph && strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph" + else if(hash == hash_graph && strsame(tok, WEB_PATH_GRAPH) == 0) { // "graph" // the client is requesting an rrd graph -- OLD API // get the name of the data to show @@ -1985,7 +2063,7 @@ void web_client_process(struct web_client *w) { buffer_strcat(w->response.data, "Graph name?\r\n"); } } - else if(hash == hash_list && strcmp(tok, "list") == 0) { + else if(hash == hash_list && strsame(tok, "list") == 0) { // OLD API code = 200; @@ -1997,7 +2075,7 @@ void web_client_process(struct web_client *w) { for ( ; st ; st = st->next ) buffer_sprintf(w->response.data, "%s\n", st->name); } - else if(hash == hash_all_json && strcmp(tok, "all.json") == 0) { + else if(hash == hash_all_json && strsame(tok, "all.json") == 0) { // OLD API code = 200; debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id); @@ -2007,7 +2085,7 @@ void web_client_process(struct web_client *w) { rrd_stats_all_json(w->response.data); } #ifdef NETDATA_INTERNAL_CHECKS - else if(hash == hash_exit && strcmp(tok, "exit") == 0) { + else if(hash == hash_exit && strsame(tok, "exit") == 0) { code = 200; w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); @@ -2019,9 +2097,8 @@ void web_client_process(struct web_client *w) { error("web request to exit received."); netdata_cleanup_and_exit(0); - netdata_exit = 1; } - else if(hash == hash_debug && strcmp(tok, "debug") == 0) { + else if(hash == hash_debug && strsame(tok, "debug") == 0) { buffer_flush(w->response.data); // get the name of the data to show @@ -2034,14 +2111,16 @@ void web_client_process(struct web_client *w) { if(!st) st = rrdset_find(tok); if(!st) { code = 404; - buffer_sprintf(w->response.data, "Chart %s is not found.\r\n", tok); + buffer_strcat(w->response.data, "Chart is not found: "); + buffer_strcat_htmlescape(w->response.data, tok); debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok); } else { code = 200; debug_flags |= D_RRD_STATS; st->debug = !st->debug; - buffer_sprintf(w->response.data, "Chart %s has now debug %s.\r\n", tok, st->debug?"enabled":"disabled"); + buffer_sprintf(w->response.data, "Chart has now debug %s: ", st->debug?"enabled":"disabled"); + buffer_strcat_htmlescape(w->response.data, tok); debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, st->debug?"enabled":"disabled"); } } @@ -2051,7 +2130,7 @@ void web_client_process(struct web_client *w) { buffer_strcat(w->response.data, "debug which chart?\r\n"); } } - else if(hash == hash_mirror && strcmp(tok, "mirror") == 0) { + else if(hash == hash_mirror && strsame(tok, "mirror") == 0) { code = 200; debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id); @@ -2083,20 +2162,42 @@ void web_client_process(struct web_client *w) { } } - gettimeofday(&w->tv_ready, NULL); - w->response.data->date = time(NULL); + now_realtime_timeval(&w->tv_ready); w->response.sent = 0; w->response.code = code; + // set a proper last modified date + if(unlikely(!w->response.data->date)) + w->response.data->date = w->tv_ready.tv_sec; + + if(unlikely(code != 200)) + buffer_no_cacheable(w->response.data); + + // set a proper expiration date, if not already set + if(unlikely(!w->response.data->expires)) { + if(w->response.data->options & WB_CONTENT_NO_CACHEABLE) + w->response.data->expires = w->tv_ready.tv_sec + rrd_update_every; + else + w->response.data->expires = w->tv_ready.tv_sec + 86400; + } + // prepare the HTTP response header debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code); const char *content_type_string = web_content_type_to_string(w->response.data->contenttype); const char *code_msg = web_response_code_to_string(code); - char date[32]; - struct tm tmbuf, *tm = gmtime_r(&w->response.data->date, &tmbuf); - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", tm); + // prepare the last modified and expiration dates + char date[32], edate[32]; + { + struct tm tmbuf, *tm; + + tm = gmtime_r(&w->response.data->date, &tmbuf); + strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", tm); + + tm = gmtime_r(&w->response.data->expires, &tmbuf); + strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", tm); + } buffer_sprintf(w->response.header_output, "HTTP/1.1 %d %s\r\n" @@ -2144,48 +2245,41 @@ void web_client_process(struct web_client *w) { if(w->mode == WEB_CLIENT_MODE_OPTIONS) { buffer_strcat(w->response.header_output, "Access-Control-Allow-Methods: GET, OPTIONS\r\n" - "Access-Control-Allow-Headers: accept, x-requested-with, origin, content-type, cookie\r\n" + "Access-Control-Allow-Headers: accept, x-requested-with, origin, content-type, cookie, pragma, cache-control\r\n" "Access-Control-Max-Age: 1209600\r\n" // 86400 * 14 ); } - - if(buffer_strlen(w->response.header)) - buffer_strcat(w->response.header_output, buffer_tostring(w->response.header)); - - if(w->mode == WEB_CLIENT_MODE_NORMAL && (w->response.data->options & WB_CONTENT_NO_CACHEABLE)) { - buffer_sprintf(w->response.header_output, - "Expires: %s\r\n" - "Cache-Control: no-cache\r\n" - , date); - } - else if(w->mode != WEB_CLIENT_MODE_OPTIONS) { - char edate[32]; - time_t et = w->response.data->date + (86400 * 14); - struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf); - strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm); - + else { buffer_sprintf(w->response.header_output, - "Expires: %s\r\n" - "Cache-Control: public\r\n" - , edate); + "Cache-Control: %s\r\n" + "Expires: %s\r\n", + (w->response.data->options & WB_CONTENT_NO_CACHEABLE)?"no-cache":"public", + edate); } - // if we know the content length, put it - if(!w->response.zoutput && (w->response.data->len || w->response.rlen)) - buffer_sprintf(w->response.header_output, - "Content-Length: %zu\r\n" - , w->response.data->len? w->response.data->len: w->response.rlen - ); - else if(!w->response.zoutput) - w->keepalive = 0; // content-length is required for keep-alive + // copy a possibly available custom header + if(unlikely(buffer_strlen(w->response.header))) + buffer_strcat(w->response.header_output, buffer_tostring(w->response.header)); - if(w->response.zoutput) { + // headers related to the transfer method + if(likely(w->response.zoutput)) { buffer_strcat(w->response.header_output, "Content-Encoding: gzip\r\n" "Transfer-Encoding: chunked\r\n" ); } + else { + if(likely((w->response.data->len || w->response.rlen))) { + // we know the content length, put it + buffer_sprintf(w->response.header_output, "Content-Length: %zu\r\n", w->response.data->len? w->response.data->len: w->response.rlen); + } + else { + // we don't know the content length, disable keep-alive + w->keepalive = 0; + } + } + // end of HTTP header buffer_strcat(w->response.header_output, "\r\n"); // sent the HTTP header