]> arthur.barton.de Git - netdata.git/blobdiff - src/web_client.c
replace strcmp() with strsame() and procfile improvements
[netdata.git] / src / web_client.c
index 874aeaf4d89629c5c710653fa19496e9ed48026b..bd04791317bd730efe51721160fa2aaa5c6c1ca0 100644 (file)
@@ -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;
-        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