]> arthur.barton.de Git - netdata.git/blobdiff - src/web_client.c
fixed typo
[netdata.git] / src / web_client.c
index 50779b54af6dbb208bd70d8bd8a3b582d5f1333f..107fbb64ab21448adf3b9134ee771bd947d21356 100644 (file)
@@ -5,7 +5,8 @@
 #define TOO_BIG_REQUEST 16384
 
 int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
-int web_donotrack_comply = 0;
+int respect_web_browser_do_not_track_policy = 0;
+char *web_x_frame_options = NULL;
 
 #ifdef NETDATA_WITH_ZLIB
 int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRATEGY;
@@ -121,7 +122,7 @@ 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;
@@ -290,12 +291,7 @@ gid_t web_files_gid(void) {
 
 int mysendfile(struct web_client *w, char *filename)
 {
-    static char *web_dir = NULL;
-
-    // initialize our static data
-    if(unlikely(!web_dir)) web_dir = config_get("global", "web files directory", WEB_DIR);
-
-    debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, web_dir, filename);
+    debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename);
 
     // skip leading slashes
     while (*filename == '/') filename++;
@@ -324,7 +320,7 @@ int mysendfile(struct web_client *w, char *filename)
 
     // access the file
     char webfilename[FILENAME_MAX + 1];
-    snprintfz(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename);
+    snprintfz(webfilename, FILENAME_MAX, "%s/%s", netdata_configured_web_dir, filename);
 
     // check if the file exists
     struct stat stat;
@@ -414,7 +410,11 @@ 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;
@@ -669,7 +669,7 @@ int web_client_api_request_v1_data_group(char *name, int def)
     return def;
 }
 
-int web_client_api_request_v1_alarms(struct web_client *w, char *url)
+int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, char *url)
 {
     int all = 0;
 
@@ -683,11 +683,11 @@ int web_client_api_request_v1_alarms(struct web_client *w, char *url)
 
     buffer_flush(w->response.data);
     w->response.data->contenttype = CT_APPLICATION_JSON;
-    health_alarms2json(&localhost, w->response.data, all);
+    health_alarms2json(host, w->response.data, all);
     return 200;
 }
 
-int web_client_api_request_v1_alarm_log(struct web_client *w, char *url)
+int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url)
 {
     uint32_t after = 0;
 
@@ -699,16 +699,16 @@ 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(!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0);
     }
 
     buffer_flush(w->response.data);
     w->response.data->contenttype = CT_APPLICATION_JSON;
-    health_alarm_log2json(&localhost, w->response.data, after);
+    health_alarm_log2json(host, w->response.data, after);
     return 200;
 }
 
-int web_client_api_request_single_chart(struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf))
+int web_client_api_request_single_chart(RRDHOST *host, struct web_client *w, char *url, void callback(RRDSET *st, BUFFER *buf))
 {
     int ret = 400;
     char *chart = NULL;
@@ -738,8 +738,8 @@ int web_client_api_request_single_chart(struct web_client *w, char *url, void ca
         goto cleanup;
     }
 
-    RRDSET *st = rrdset_find(chart);
-    if(!st) st = rrdset_find_byname(chart);
+    RRDSET *st = rrdset_find(host, chart);
+    if(!st) st = rrdset_find_byname(host, chart);
     if(!st) {
         buffer_strcat(w->response.data, "Chart is not found: ");
         buffer_strcat_htmlescape(w->response.data, chart);
@@ -755,27 +755,70 @@ int web_client_api_request_single_chart(struct web_client *w, char *url, void ca
     return ret;
 }
 
-int web_client_api_request_v1_alarm_variables(struct web_client *w, char *url)
+int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client *w, char *url)
 {
-    return web_client_api_request_single_chart(w, url, health_api_v1_chart_variables2json);
+    return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json);
 }
 
-int web_client_api_request_v1_charts(struct web_client *w, char *url)
+int web_client_api_request_v1_charts(RRDHOST *host, 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);
+    rrd_stats_api_v1_charts(host, w->response.data);
     return 200;
 }
 
-int web_client_api_request_v1_chart(struct web_client *w, char *url)
+int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client *w, char *url)
 {
-    return web_client_api_request_single_chart(w, url, rrd_stats_api_v1_chart);
+    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(!strcmp(name, "format")) {
+            if(!strcmp(value, ALLMETRICS_FORMAT_SHELL))
+                format = ALLMETRICS_SHELL;
+            else if(!strcmp(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(host, w->response.data);
+            return 200;
+
+        case ALLMETRICS_PROMETHEUS:
+            w->response.data->contenttype = CT_PROMETHEUS;
+            rrd_stats_api_v1_charts_allmetrics_prometheus(host, 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(RRDHOST *host, struct web_client *w, char *url)
+{
+    return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart);
 }
 
-int web_client_api_request_v1_badge(struct web_client *w, char *url) {
+int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
     int ret = 400;
     buffer_flush(w->response.data);
 
@@ -845,11 +888,11 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) {
         goto cleanup;
     }
 
-    RRDSET *st = rrdset_find(chart);
-    if(!st) st = rrdset_find_byname(chart);
+    RRDSET *st = rrdset_find(host, chart);
+    if(!st) st = rrdset_find_byname(host, chart);
     if(!st) {
         buffer_no_cacheable(w->response.data);
-        buffer_svg(w->response.data, "chart not found", 0, "", NULL, NULL, 1, -1);
+        buffer_svg(w->response.data, "chart not found", NAN, "", NULL, NULL, -1);
         ret = 200;
         goto cleanup;
     }
@@ -859,18 +902,18 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) {
         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);
+            buffer_svg(w->response.data, "alarm not found", NAN, "", NULL, NULL, -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;
@@ -887,7 +930,7 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) {
             }
         }
         else {
-            refresh = atoi(refresh_str);
+            refresh = str2i(refresh_str);
             if(refresh < 0) refresh = -refresh;
         }
     }
@@ -935,9 +978,6 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) {
             );
 
     if(rc) {
-        calculated_number n = rc->value;
-        if(isnan(n) || isinf(n)) n = 0;
-
         if (refresh > 0) {
             buffer_sprintf(w->response.header, "Refresh: %d\r\n", refresh);
             w->response.data->expires = now_realtime_sec() + refresh;
@@ -973,19 +1013,18 @@ 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);
+                label,
+                (isnan(rc->value)||isinf(rc->value)) ? rc->value : rc->value * multiply / divide,
+                units,
+                label_color,
+                value_color,
+                precision);
         ret = 200;
     }
     else {
         time_t latest_timestamp = 0;
         int value_is_null = 1;
-        calculated_number n = 0;
+        calculated_number n = NAN;
         ret = 500;
 
         // if the collected value is too old, don't calculate its value
@@ -1018,13 +1057,12 @@ int web_client_api_request_v1_badge(struct web_client *w, char *url) {
 
         // render the badge
         buffer_svg(w->response.data,
-                   label,
-                   n * multiply / divide,
-                   units,
-                   label_color,
-                   value_color,
-                   value_is_null,
-                   precision);
+                label,
+                (value_is_null)?NAN:(n * multiply / divide),
+                units,
+                label_color,
+                value_color,
+                precision);
     }
 
 cleanup:
@@ -1034,7 +1072,7 @@ cleanup:
 }
 
 // returns the HTTP code
-int web_client_api_request_v1_data(struct web_client *w, char *url)
+int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url)
 {
     debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url);
 
@@ -1136,8 +1174,8 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
         goto cleanup;
     }
 
-    RRDSET *st = rrdset_find(chart);
-    if(!st) st = rrdset_find_byname(chart);
+    RRDSET *st = rrdset_find(host, chart);
+    if(!st) st = rrdset_find_byname(host, chart);
     if(!st) {
         buffer_strcat(w->response.data, "Chart is not found: ");
         buffer_strcat_htmlescape(w->response.data, chart);
@@ -1145,9 +1183,9 @@ int web_client_api_request_v1_data(struct web_client *w, char *url)
         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
@@ -1209,9 +1247,7 @@ 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)
+int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *w, char *url)
 {
     static uint32_t hash_action = 0, hash_access = 0, hash_hello = 0, hash_delete = 0, hash_search = 0,
             hash_switch = 0, hash_machine = 0, hash_url = 0, hash_name = 0, hash_delete_url = 0, hash_for = 0,
@@ -1235,7 +1271,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);
 
@@ -1312,7 +1348,7 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url)
 #endif /* NETDATA_INTERNAL_CHECKS */
     }
 
-    if(web_donotrack_comply && w->donottrack) {
+    if(respect_web_browser_do_not_track_policy && w->donottrack) {
         buffer_flush(w->response.data);
         buffer_sprintf(w->response.data, "Your web browser is sending 'DNT: 1' (Do Not Track). The registry requires persistent cookies on your browser to work.");
         return 400;
@@ -1350,76 +1386,22 @@ int web_client_api_request_v1_registry(struct web_client *w, char *url)
     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, now_realtime_sec());
+            return registry_request_access_json(host, 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, now_realtime_sec());
+            return registry_request_delete_json(host, 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, now_realtime_sec());
+            return registry_request_search_json(host, 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, now_realtime_sec());
+            return registry_request_switch_json(host, w, person_guid, machine_guid, machine_url, to_person_guid, now_realtime_sec());
 
         case 'H':
-            return registry_request_hello_json(w);
+            return registry_request_hello_json(host, w);
 
         default:
             buffer_flush(w->response.data);
@@ -1428,8 +1410,8 @@ 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, hash_alarm_variables = 0;
+int web_client_api_request_v1(RRDHOST *host, 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, hash_alarm_variables = 0, hash_raw = 0;
 
     if(unlikely(hash_data == 0)) {
         hash_data = simple_hash("data");
@@ -1440,6 +1422,7 @@ int web_client_api_request_v1(struct web_client *w, char *url) {
         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
@@ -1449,28 +1432,31 @@ int web_client_api_request_v1(struct web_client *w, char *url) {
         uint32_t hash = simple_hash(tok);
 
         if(hash == hash_data && !strcmp(tok, "data"))
-            return web_client_api_request_v1_data(w, url);
+            return web_client_api_request_v1_data(host, w, url);
 
         else if(hash == hash_chart && !strcmp(tok, "chart"))
-            return web_client_api_request_v1_chart(w, url);
+            return web_client_api_request_v1_chart(host, w, url);
 
         else if(hash == hash_charts && !strcmp(tok, "charts"))
-            return web_client_api_request_v1_charts(w, url);
+            return web_client_api_request_v1_charts(host, w, url);
 
         else if(hash == hash_registry && !strcmp(tok, "registry"))
-            return web_client_api_request_v1_registry(w, url);
+            return web_client_api_request_v1_registry(host, w, url);
 
         else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
-            return web_client_api_request_v1_badge(w, url);
+            return web_client_api_request_v1_badge(host, w, url);
 
         else if(hash == hash_alarms && !strcmp(tok, "alarms"))
-            return web_client_api_request_v1_alarms(w, url);
+            return web_client_api_request_v1_alarms(host, w, url);
 
         else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log"))
-            return web_client_api_request_v1_alarm_log(w, url);
+            return web_client_api_request_v1_alarm_log(host, w, url);
 
         else if(hash == hash_alarm_variables && !strcmp(tok, "alarm_variables"))
-            return web_client_api_request_v1_alarm_variables(w, url);
+            return web_client_api_request_v1_alarm_variables(host, w, url);
+
+        else if(hash == hash_raw && !strcmp(tok, "allmetrics"))
+            return web_client_api_request_v1_allmetrics(host, w, url);
 
         else {
             buffer_flush(w->response.data);
@@ -1486,14 +1472,14 @@ int web_client_api_request_v1(struct web_client *w, char *url) {
     }
 }
 
-int web_client_api_request(struct web_client *w, char *url)
+int web_client_api_request(RRDHOST *host, struct web_client *w, char *url)
 {
     // get the api version
     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)
-            return web_client_api_request_v1(w, url);
+            return web_client_api_request_v1(host, w, url);
         else {
             buffer_flush(w->response.data);
             buffer_strcat(w->response.data, "Unsupported API version: ");
@@ -1508,7 +1494,7 @@ 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)
+int web_client_api_old_data_request(RRDHOST *host, struct web_client *w, char *url, int datasource_type)
 {
     if(!url || !*url) {
         buffer_flush(w->response.data);
@@ -1531,8 +1517,8 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou
     // do we have such a data set?
     if(*tok) {
         debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
-        st = rrdset_find_byname(tok);
-        if(!st) st = rrdset_find(tok);
+        st = rrdset_find_byname(host, tok);
+        if(!st) st = rrdset_find(host, tok);
     }
 
     if(!st) {
@@ -1546,7 +1532,7 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou
     debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok);
 
     // how many entries does the client want?
-    int lines = rrd_default_history_entries;
+    int lines = (int)st->entries;
     int group_count = 1;
     time_t after = 0, before = 0;
     int group_method = GROUP_AVERAGE;
@@ -1555,13 +1541,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;
     }
@@ -1578,13 +1564,13 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou
     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) {
@@ -1681,6 +1667,167 @@ int web_client_api_old_data_request(struct web_client *w, char *url, int datasou
     return 200;
 }
 
+int validate_stream_api_key(const char *key) {
+    if(appconfig_get_number(&stream_config, key, "enabled", 0))
+        return 1;
+
+    return 0;
+}
+
+int web_client_stream_request(RRDHOST *host, struct web_client *w, char *url) {
+    char *key = NULL, *hostname = NULL, *machine_guid = NULL;
+    int update_every = default_rrd_update_every;
+    int history = default_rrd_history_entries;
+    RRD_MEMORY_MODE mode = default_rrd_memory_mode;
+    int health_enabled = default_health_enabled;
+
+    while(url) {
+        char *value = mystrsep(&url, "?&");
+        if(!value || !*value) continue;
+
+        char *name = mystrsep(&value, "=");
+        if(!name || !*name) continue;
+        if(!value || !*value) continue;
+
+        if(!strcmp(name, "key"))
+            key = value;
+        else if(!strcmp(name, "hostname"))
+            hostname = value;
+        else if(!strcmp(name, "machine_guid"))
+            machine_guid = value;
+        else if(!strcmp(name, "update_every"))
+            update_every = (int)strtoul(value, NULL, 0);
+    }
+
+    if(!key || !*key) {
+        error("STREAM [%s]:%s: request without an API key. Forbidding access.", w->client_ip, w->client_port);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "You need an API key for this request.");
+        return 401;
+    }
+
+    if(!hostname || !*hostname) {
+        error("STREAM [%s]:%s: request without a hostname. Forbidding access.", w->client_ip, w->client_port);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "You need to send a hostname too.");
+        return 400;
+    }
+
+    if(!machine_guid || !*machine_guid) {
+        error("STREAM [%s]:%s: request without a machine GUID. Forbidding access.", w->client_ip, w->client_port);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "You need to send a machine GUID too.");
+        return 400;
+    }
+
+    if(!validate_stream_api_key(key)) {
+        error("STREAM [%s]:%s: API key '%s' is not allowed. Forbidding access.", w->client_ip, w->client_port, key);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "Your API key is not permitted access.");
+        return 401;
+    }
+
+    if(!appconfig_get_boolean(&stream_config, machine_guid, "enabled", 1)) {
+        error("STREAM [%s]:%s: machine GUID '%s' is not allowed. Forbidding access.", w->client_ip, w->client_port, machine_guid);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "Your machine guide is not permitted access.");
+        return 404;
+    }
+
+    // update_every = (int)appconfig_get_number(&stream_config, key, "default update every", update_every);
+    update_every = (int)appconfig_get_number(&stream_config, machine_guid, "update every", update_every);
+    if(update_every < 0) update_every = 1;
+
+    history = (int)appconfig_get_number(&stream_config, key, "default history", history);
+    history = (int)appconfig_get_number(&stream_config, machine_guid, "history", history);
+    if(history < 5) history = 5;
+
+    mode = rrd_memory_mode_id(appconfig_get(&stream_config, key, "default memory mode", rrd_memory_mode_name(mode)));
+    mode = rrd_memory_mode_id(appconfig_get(&stream_config, machine_guid, "memory mode", rrd_memory_mode_name(mode)));
+
+    health_enabled = appconfig_get_boolean_ondemand(&stream_config, key, "health enabled by default", health_enabled);
+    health_enabled = appconfig_get_boolean_ondemand(&stream_config, machine_guid, "health enabled", health_enabled);
+
+    host = rrdhost_find_or_create(hostname, machine_guid, update_every, history, mode, health_enabled?1:0);
+
+    info("STREAM request from client '%s:%s' for host '%s' with machine_guid '%s': update every = %d, history = %d, memory mode = %s, health %s",
+            w->client_ip, w->client_port,
+            hostname, machine_guid,
+            update_every,
+            history,
+            rrd_memory_mode_name(mode),
+            (health_enabled == CONFIG_BOOLEAN_NO)?"disabled":((health_enabled == CONFIG_BOOLEAN_YES)?"enabled":"auto")
+    );
+
+    struct plugind cd = {
+            .enabled = 1,
+            .update_every = default_rrd_update_every,
+            .pid = 0,
+            .serial_failures = 0,
+            .successful_collections = 0,
+            .obsolete = 0,
+            .started_t = now_realtime_sec(),
+            .next = NULL,
+    };
+
+    // put the client IP and port into the buffers used by plugins.d
+    snprintfz(cd.id,           CONFIG_MAX_NAME,  "%s:%s", w->client_ip, w->client_port);
+    snprintfz(cd.filename,     FILENAME_MAX,     "%s:%s", w->client_ip, w->client_port);
+    snprintfz(cd.fullfilename, FILENAME_MAX,     "%s:%s", w->client_ip, w->client_port);
+    snprintfz(cd.cmd,          PLUGINSD_CMD_MAX, "%s:%s", w->client_ip, w->client_port);
+
+    info("STREAM [%s]:%s: sending STREAM to initiate streaming...", w->client_ip, w->client_port);
+    if(send_timeout(w->ifd, "STREAM", 6, 0, 60) != 6) {
+        error("STREAM [%s]:%s: cannot send STREAM.", w->client_ip, w->client_port);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "Failed to reply back with STREAM");
+        return 400;
+    }
+
+    // remove the non-blocking flag from the socket
+    if(fcntl(w->ifd, F_SETFL, fcntl(w->ifd, F_GETFL, 0) & ~O_NONBLOCK) == -1)
+        error("STREAM [%s]:%s: cannot remove the non-blocking flag from socket %d", w->client_ip, w->client_port, w->ifd);
+
+    /*
+    char buffer[1000 + 1];
+    ssize_t len;
+    while((len = read(w->ifd, buffer, 1000)) != -1) {
+        buffer[len] = '\0';
+        fprintf(stderr, "BEGIN READ %zu bytes\n%s\nEND READ\n", (size_t)len, buffer);
+    }
+    */
+
+    // convert the socket to a FILE *
+    FILE *fp = fdopen(w->ifd, "r");
+    if(!fp) {
+        error("STREAM [%s]:%s: failed to get a FILE for FD %d.", w->client_ip, w->client_port, w->ifd);
+        buffer_flush(w->response.data);
+        buffer_sprintf(w->response.data, "Failed to get a FILE for an FD.");
+        return 500;
+    }
+
+    // call the plugins.d processor to receive the metrics
+    info("STREAM [%s]:%s: connecting client to plugins.d on host '%s' with machine GUID '%s'.", w->client_ip, w->client_port, host->hostname, host->machine_guid);
+    size_t count = pluginsd_process(host, &cd, fp, 1);
+    error("STREAM [%s]:%s: client disconnected (host '%s', machine GUID '%s').", w->client_ip, w->client_port, host->hostname, host->machine_guid);
+    if(health_enabled == CONFIG_BOOLEAN_AUTO)
+        host->health_enabled = 0;
+
+    // close all sockets, to let the socket worker we are done
+    fclose(fp);
+    w->ifd = -1;
+    if(w->ofd != -1 && w->ofd != w->ifd) {
+        close(w->ofd);
+        w->ofd = -1;
+    }
+
+    // this will not send anything
+    // the socket is closed
+    buffer_flush(w->response.data);
+    if(count) return 200;
+    return 400;
+}
+
 const char *web_content_type_to_string(uint8_t contenttype) {
     switch(contenttype) {
         case CT_TEXT_HTML:
@@ -1743,6 +1890,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";
@@ -1836,7 +1986,7 @@ static inline char *http_header_parse(struct web_client *w, char *s) {
         if(strcasestr(v, "keep-alive"))
             w->keepalive = 1;
     }
-    else if(web_donotrack_comply && hash == hash_donottrack && !strcasecmp(s, "DNT")) {
+    else if(respect_web_browser_do_not_track_policy && hash == hash_donottrack && !strcasecmp(s, "DNT")) {
         if(*v == '0') w->donottrack = 0;
         else if(*v == '1') w->donottrack = 1;
     }
@@ -1864,7 +2014,13 @@ static inline char *http_header_parse(struct web_client *w, char *s) {
 // > 0 : request is not supported
 // < 0 : request is incomplete - wait for more data
 
-static inline int http_request_validate(struct web_client *w) {
+typedef enum http_validation {
+    HTTP_VALIDATION_OK,
+    HTTP_VALIDATION_NOT_SUPPORTED,
+    HTTP_VALIDATION_INCOMPLETE
+} HTTP_VALIDATION;
+
+static inline HTTP_VALIDATION http_request_validate(struct web_client *w) {
     char *s = w->response.data->buffer, *encoded_url = NULL;
 
     // is is a valid request?
@@ -1878,7 +2034,7 @@ static inline int http_request_validate(struct web_client *w) {
     }
     else {
         w->wait_receive = 0;
-        return 1;
+        return HTTP_VALIDATION_NOT_SUPPORTED;
     }
 
     // find the SPACE + "HTTP/"
@@ -1894,7 +2050,7 @@ static inline int http_request_validate(struct web_client *w) {
     // incomplete requests
     if(unlikely(!*s)) {
         w->wait_receive = 1;
-        return -2;
+        return HTTP_VALIDATION_INCOMPLETE;
     }
 
     // we have the end of encoded_url - remember it
@@ -1925,7 +2081,7 @@ static inline int http_request_validate(struct web_client *w) {
                 strncpyz(w->last_url, w->decoded_url, URL_MAX);
 
                 w->wait_receive = 0;
-                return 0;
+                return HTTP_VALIDATION_OK;
             }
 
             // another header line
@@ -1935,260 +2091,26 @@ static inline int http_request_validate(struct web_client *w) {
 
     // incomplete request
     w->wait_receive = 1;
-    return -3;
+    return HTTP_VALIDATION_INCOMPLETE;
 }
 
-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;
-
-#ifdef NETDATA_INTERNAL_CHECKS
-    static uint32_t hash_exit = 0, hash_debug = 0, hash_mirror = 0;
-#endif
-
-    // start timing us
-    now_realtime_timeval(&w->tv_in);
-
-    if(unlikely(!hash_api)) {
-        hash_api = simple_hash("api");
-        hash_netdata_conf = simple_hash("netdata.conf");
-        hash_data = simple_hash(WEB_PATH_DATA);
-        hash_datasource = simple_hash(WEB_PATH_DATASOURCE);
-        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;
-    ssize_t bytes;
-
-    int what_to_do = http_request_validate(w);
-
-    // wait for more data
-    if(what_to_do < 0) {
-        if(w->response.data->len > TOO_BIG_REQUEST) {
-            strcpy(w->last_url, "too big request");
-
-            debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zu bytes).", w->id, w->response.data->len);
-
-            code = 400;
-            buffer_flush(w->response.data);
-            buffer_sprintf(w->response.data, "Received request is too big  (%zu bytes).\r\n", w->response.data->len);
-        }
-        else {
-            // wait for more data
-            return;
-        }
-    }
-    else if(what_to_do > 0) {
-        // strcpy(w->last_url, "not a valid request");
-
-        debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer);
-
-        code = 500;
-        buffer_flush(w->response.data);
-        buffer_strcat(w->response.data, "I don't understand you...\r\n");
-    }
-    else { // what_to_do == 0
-        if(w->mode == WEB_CLIENT_MODE_OPTIONS) {
-            code = 200;
-            w->response.data->contenttype = CT_TEXT_PLAIN;
-            buffer_flush(w->response.data);
-            buffer_strcat(w->response.data, "OK");
-        }
-        else {
-            char *url = w->decoded_url;
-            char *tok = mystrsep(&url, "/?");
-            if(tok && *tok) {
-                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) {
-                    // the client is requesting api access
-                    code = web_client_api_request(w, url);
-                }
-                else if(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0) {
-                    code = 200;
-                    debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
-
-                    w->response.data->contenttype = CT_TEXT_PLAIN;
-                    buffer_flush(w->response.data);
-                    generate_config(w->response.data, 0);
-                }
-                else if(hash == hash_data && strcmp(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"
-                    // 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"
-                    // the client is requesting an rrd graph -- OLD API
-
-                    // get the name of the data to show
-                    tok = mystrsep(&url, "/?&");
-                    if(tok && *tok) {
-                        debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
-
-                        // do we have such a data set?
-                        RRDSET *st = rrdset_find_byname(tok);
-                        if(!st) st = rrdset_find(tok);
-                        if(!st) {
-                            // we don't have it
-                            // try to send a file with that name
-                            buffer_flush(w->response.data);
-                            code = mysendfile(w, tok);
-                        }
-                        else {
-                            code = 200;
-                            debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
-                            w->response.data->contenttype = CT_APPLICATION_JSON;
-                            buffer_flush(w->response.data);
-                            rrd_stats_graph_json(st, url, w->response.data);
-                        }
-                    }
-                    else {
-                        code = 400;
-                        buffer_flush(w->response.data);
-                        buffer_strcat(w->response.data, "Graph name?\r\n");
-                    }
-                }
-                else if(hash == hash_list && strcmp(tok, "list") == 0) {
-                    // OLD API
-                    code = 200;
-
-                    debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
-
-                    buffer_flush(w->response.data);
-                    RRDSET *st = localhost.rrdset_root;
-
-                    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) {
-                    // OLD API
-                    code = 200;
-                    debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
-
-                    w->response.data->contenttype = CT_APPLICATION_JSON;
-                    buffer_flush(w->response.data);
-                    rrd_stats_all_json(w->response.data);
-                }
-#ifdef NETDATA_INTERNAL_CHECKS
-                else if(hash == hash_exit && strcmp(tok, "exit") == 0) {
-                    code = 200;
-                    w->response.data->contenttype = CT_TEXT_PLAIN;
-                    buffer_flush(w->response.data);
-
-                    if(!netdata_exit)
-                        buffer_strcat(w->response.data, "ok, will do...");
-                    else
-                        buffer_strcat(w->response.data, "I am doing it already");
-
-                    error("web request to exit received.");
-                    netdata_cleanup_and_exit(0);
-                    netdata_exit = 1;
-                }
-                else if(hash == hash_debug && strcmp(tok, "debug") == 0) {
-                    buffer_flush(w->response.data);
-
-                    // get the name of the data to show
-                    tok = mystrsep(&url, "/?&");
-                    if(tok && *tok) {
-                        debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
-
-                        // do we have such a data set?
-                        RRDSET *st = rrdset_find_byname(tok);
-                        if(!st) st = rrdset_find(tok);
-                        if(!st) {
-                            code = 404;
-                            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 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");
-                        }
-                    }
-                    else {
-                        code = 500;
-                        buffer_flush(w->response.data);
-                        buffer_strcat(w->response.data, "debug which chart?\r\n");
-                    }
-                }
-                else if(hash == hash_mirror && strcmp(tok, "mirror") == 0) {
-                    code = 200;
-
-                    debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
-
-                    // replace the zero bytes with spaces
-                    buffer_char_replace(w->response.data, '\0', ' ');
-
-                    // just leave the buffer as is
-                    // it will be copied back to the client
-                }
-#endif  /* NETDATA_INTERNAL_CHECKS */
-                else {
-                    char filename[FILENAME_MAX+1];
-                    url = filename;
-                    strncpyz(filename, w->last_url, FILENAME_MAX);
-                    tok = mystrsep(&url, "?");
-                    buffer_flush(w->response.data);
-                    code = mysendfile(w, (tok && *tok)?tok:"/");
-                }
-            }
-            else {
-                char filename[FILENAME_MAX+1];
-                url = filename;
-                strncpyz(filename, w->last_url, FILENAME_MAX);
-                tok = mystrsep(&url, "?");
-                buffer_flush(w->response.data);
-                code = mysendfile(w, (tok && *tok)?tok:"/");
-            }
-        }
-    }
-
-    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))
+static inline void web_client_send_http_header(struct web_client *w) {
+    if(unlikely(w->response.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;
+            w->response.data->expires = w->tv_ready.tv_sec + localhost->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);
+    debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, w->response.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);
+    const char *code_msg = web_response_code_to_string(w->response.code);
 
     // prepare the last modified and expiration dates
     char date[32], edate[32];
@@ -2203,61 +2125,64 @@ void web_client_process(struct web_client *w) {
     }
 
     buffer_sprintf(w->response.header_output,
-        "HTTP/1.1 %d %s\r\n"
-        "Connection: %s\r\n"
-        "Server: NetData Embedded HTTP Server\r\n"
-        "Access-Control-Allow-Origin: %s\r\n"
-        "Access-Control-Allow-Credentials: true\r\n"
-        "Content-Type: %s\r\n"
-        "Date: %s\r\n"
-        , code, code_msg
-        , w->keepalive?"keep-alive":"close"
-        , w->origin
-        , content_type_string
-        , date
-        );
+            "HTTP/1.1 %d %s\r\n"
+                    "Connection: %s\r\n"
+                    "Server: NetData Embedded HTTP Server\r\n"
+                    "Access-Control-Allow-Origin: %s\r\n"
+                    "Access-Control-Allow-Credentials: true\r\n"
+                    "Content-Type: %s\r\n"
+                    "Date: %s\r\n"
+                   , w->response.code, code_msg
+                   , w->keepalive?"keep-alive":"close"
+                   , w->origin
+                   , content_type_string
+                   , date
+    );
+
+    if(unlikely(web_x_frame_options))
+        buffer_sprintf(w->response.header_output, "X-Frame-Options: %s\r\n", web_x_frame_options);
 
     if(w->cookie1[0] || w->cookie2[0]) {
         if(w->cookie1[0]) {
             buffer_sprintf(w->response.header_output,
-               "Set-Cookie: %s\r\n",
-               w->cookie1);
+                    "Set-Cookie: %s\r\n",
+                    w->cookie1);
         }
 
         if(w->cookie2[0]) {
             buffer_sprintf(w->response.header_output,
-               "Set-Cookie: %s\r\n",
-               w->cookie2);
+                    "Set-Cookie: %s\r\n",
+                    w->cookie2);
         }
 
-        if(web_donotrack_comply)
+        if(respect_web_browser_do_not_track_policy)
             buffer_sprintf(w->response.header_output,
-               "Tk: T;cookies\r\n");
+                    "Tk: T;cookies\r\n");
     }
     else {
-        if(web_donotrack_comply) {
+        if(respect_web_browser_do_not_track_policy) {
             if(w->tracking_required)
                 buffer_sprintf(w->response.header_output,
-                   "Tk: T;cookies\r\n");
+                        "Tk: T;cookies\r\n");
             else
                 buffer_sprintf(w->response.header_output,
-                   "Tk: N\r\n");
+                        "Tk: N\r\n");
         }
     }
 
     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, pragma, cache-control\r\n"
-            "Access-Control-Max-Age: 1209600\r\n" // 86400 * 14
-            );
+                "Access-Control-Allow-Methods: GET, OPTIONS\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
+        );
     }
     else {
         buffer_sprintf(w->response.header_output,
-            "Cache-Control: %s\r\n"
-            "Expires: %s\r\n",
-            (w->response.data->options & WB_CONTENT_NO_CACHEABLE)?"no-cache":"public",
-            edate);
+                "Cache-Control: %s\r\n"
+                        "Expires: %s\r\n",
+                (w->response.data->options & WB_CONTENT_NO_CACHEABLE)?"no-cache":"public",
+                edate);
     }
 
     // copy a possibly available custom header
@@ -2267,9 +2192,9 @@ void web_client_process(struct web_client *w) {
     // 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"
-            );
+                "Content-Encoding: gzip\r\n"
+                        "Transfer-Encoding: chunked\r\n"
+        );
     }
     else {
         if(likely((w->response.data->len || w->response.rlen))) {
@@ -2287,34 +2212,321 @@ void web_client_process(struct web_client *w) {
 
     // sent the HTTP header
     debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %zu: '%s'"
-            , w->id
-            , buffer_strlen(w->response.header_output)
-            , buffer_tostring(w->response.header_output)
-            );
+          , w->id
+          , buffer_strlen(w->response.header_output)
+          , buffer_tostring(w->response.header_output)
+    );
 
     web_client_crock_socket(w);
 
-    bytes = send(w->ofd, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output), 0);
+    ssize_t bytes = send(w->ofd, buffer_tostring(w->response.header_output), buffer_strlen(w->response.header_output), 0);
     if(bytes != (ssize_t) buffer_strlen(w->response.header_output)) {
         if(bytes > 0)
             w->stats_sent_bytes += bytes;
 
         debug(D_WEB_CLIENT, "%llu: HTTP Header failed to be sent (I sent %zu bytes but the system sent %zd bytes). Closing web client."
-            , w->id
-            , buffer_strlen(w->response.header_output)
-            , bytes);
+              , w->id
+              , buffer_strlen(w->response.header_output)
+              , bytes);
 
         WEB_CLIENT_IS_DEAD(w);
         return;
     }
-    else 
+    else
         w->stats_sent_bytes += bytes;
+}
+
+static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url);
+
+static inline int web_client_switch_host(RRDHOST *host, struct web_client *w, char *url) {
+    static uint32_t hash_localhost = 0;
+
+    if(unlikely(!hash_localhost)) {
+        hash_localhost = simple_hash("localhost");
+    }
+
+    if(host != localhost) {
+        buffer_flush(w->response.data);
+        buffer_strcat(w->response.data, "Nesting of hosts is not allowed.");
+        return 400;
+    }
+
+    char *tok = mystrsep(&url, "/?&");
+    if(tok && *tok) {
+        debug(D_WEB_CLIENT, "%llu: Searching for host with name '%s'.", w->id, tok);
+
+        // copy the URL, we need it to serve files
+        w->last_url[0] = '/';
+        if(url && *url) strncpyz(&w->last_url[1], url, URL_MAX - 1);
+        else w->last_url[1] = '\0';
+
+        uint32_t hash = simple_hash(tok);
+
+        if(unlikely(hash == hash_localhost && !strcmp(tok, "localhost")))
+            return web_client_process_url(localhost, w, url);
+
+        rrd_rdlock();
+        RRDHOST *h;
+        rrdhost_foreach_read(h) {
+            if(unlikely((hash == h->hash_hostname && !strcmp(tok, h->hostname)) ||
+                        (hash == h->hash_machine_guid && !strcmp(tok, h->machine_guid)))) {
+                rrd_unlock();
+                return web_client_process_url(h, w, url);
+            }
+        }
+        rrd_unlock();
+    }
+
+    buffer_flush(w->response.data);
+    buffer_strcat(w->response.data, "This netdata does not maintain a database for host: ");
+    buffer_strcat_htmlescape(w->response.data, tok?tok:"");
+    return 404;
+}
+
+static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *url) {
+    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_host = 0,
+            hash_stream = 0;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+    static uint32_t hash_exit = 0, hash_debug = 0, hash_mirror = 0;
+#endif
+
+    if(unlikely(!hash_api)) {
+        hash_api = simple_hash("api");
+        hash_netdata_conf = simple_hash("netdata.conf");
+        hash_data = simple_hash(WEB_PATH_DATA);
+        hash_datasource = simple_hash(WEB_PATH_DATASOURCE);
+        hash_graph = simple_hash(WEB_PATH_GRAPH);
+        hash_list = simple_hash("list");
+        hash_all_json = simple_hash("all.json");
+        hash_host = simple_hash("host");
+        hash_stream = simple_hash("stream");
+#ifdef NETDATA_INTERNAL_CHECKS
+        hash_exit = simple_hash("exit");
+        hash_debug = simple_hash("debug");
+        hash_mirror = simple_hash("mirror");
+#endif
+    }
+
+    char *tok = mystrsep(&url, "/?");
+    if(likely(tok && *tok)) {
+        uint32_t hash = simple_hash(tok);
+        debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
+
+        if(unlikely(hash == hash_api && strcmp(tok, "api") == 0)) {
+            debug(D_WEB_CLIENT_ACCESS, "%llu: API request ...", w->id);
+            return web_client_api_request(host, w, url);
+        }
+        else if(unlikely(hash == hash_host && strcmp(tok, "host") == 0)) {
+            debug(D_WEB_CLIENT_ACCESS, "%llu: host switch request ...", w->id);
+            return web_client_switch_host(host, w, url);
+        }
+        else if(unlikely(hash == hash_stream && strcmp(tok, "stream") == 0)) {
+            debug(D_WEB_CLIENT_ACCESS, "%llu: stream request ...", w->id);
+            return web_client_stream_request(host, w, url);
+        }
+        else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) {
+            debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
+            w->response.data->contenttype = CT_TEXT_PLAIN;
+            buffer_flush(w->response.data);
+            config_generate(w->response.data, 0);
+            return 200;
+        }
+        else if(unlikely(hash == hash_data && strcmp(tok, WEB_PATH_DATA) == 0)) { // "data"
+            // the client is requesting rrd data -- OLD API
+            return web_client_api_old_data_request(host, w, url, DATASOURCE_JSON);
+        }
+        else if(unlikely(hash == hash_datasource && strcmp(tok, WEB_PATH_DATASOURCE) == 0)) { // "datasource"
+            // the client is requesting google datasource -- OLD API
+            return web_client_api_old_data_request(host, w, url, DATASOURCE_DATATABLE_JSONP);
+        }
+        else if(unlikely(hash == hash_graph && strcmp(tok, WEB_PATH_GRAPH) == 0)) { // "graph"
+            // the client is requesting an rrd graph -- OLD API
+
+            // get the name of the data to show
+            tok = mystrsep(&url, "/?&");
+            if(tok && *tok) {
+                debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
+
+                // do we have such a data set?
+                RRDSET *st = rrdset_find_byname(host, tok);
+                if(!st) st = rrdset_find(host, tok);
+                if(!st) {
+                    // we don't have it
+                    // try to send a file with that name
+                    buffer_flush(w->response.data);
+                    return mysendfile(w, tok);
+                }
+
+                debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
+                w->response.data->contenttype = CT_APPLICATION_JSON;
+                buffer_flush(w->response.data);
+                rrd_stats_graph_json(st, url, w->response.data);
+                return 200;
+            }
+
+            buffer_flush(w->response.data);
+            buffer_strcat(w->response.data, "Graph name?\r\n");
+            return 400;
+        }
+        else if(unlikely(hash == hash_list && strcmp(tok, "list") == 0)) {
+            // OLD API
+            debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
+
+            buffer_flush(w->response.data);
+            RRDSET *st;
+
+            rrdhost_rdlock(host);
+            rrdset_foreach_read(st, host) buffer_sprintf(w->response.data, "%s\n", st->name);
+            rrdhost_unlock(host);
+
+            return 200;
+        }
+        else if(unlikely(hash == hash_all_json && strcmp(tok, "all.json") == 0)) {
+            // OLD API
+            debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
+
+            w->response.data->contenttype = CT_APPLICATION_JSON;
+            buffer_flush(w->response.data);
+            rrd_stats_all_json(host, w->response.data);
+            return 200;
+        }
+#ifdef NETDATA_INTERNAL_CHECKS
+        else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) {
+            w->response.data->contenttype = CT_TEXT_PLAIN;
+            buffer_flush(w->response.data);
+
+            if(!netdata_exit)
+                buffer_strcat(w->response.data, "ok, will do...");
+            else
+                buffer_strcat(w->response.data, "I am doing it already");
+
+            error("web request to exit received.");
+            netdata_cleanup_and_exit(0);
+            return 200;
+        }
+        else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) {
+            buffer_flush(w->response.data);
+
+            // get the name of the data to show
+            tok = mystrsep(&url, "/?&");
+            if(tok && *tok) {
+                debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
+
+                // do we have such a data set?
+                RRDSET *st = rrdset_find_byname(host, tok);
+                if(!st) st = rrdset_find(host, tok);
+                if(!st) {
+                    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);
+                    return 404;
+                }
+
+                debug_flags |= D_RRD_STATS;
+
+                if(rrdset_flag_check(st, RRDSET_FLAG_DEBUG))
+                    rrdset_flag_clear(st, RRDSET_FLAG_DEBUG);
+                else
+                    rrdset_flag_set(st, RRDSET_FLAG_DEBUG);
+
+                buffer_sprintf(w->response.data, "Chart has now debug %s: ", rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled");
+                buffer_strcat_htmlescape(w->response.data, tok);
+                debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, rrdset_flag_check(st, RRDSET_FLAG_DEBUG)?"enabled":"disabled");
+                return 200;
+            }
+
+            buffer_flush(w->response.data);
+            buffer_strcat(w->response.data, "debug which chart?\r\n");
+            return 400;
+        }
+        else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) {
+            debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
+
+            // replace the zero bytes with spaces
+            buffer_char_replace(w->response.data, '\0', ' ');
+
+            // just leave the buffer as is
+            // it will be copied back to the client
+
+            return 200;
+        }
+#endif  /* NETDATA_INTERNAL_CHECKS */
+    }
+
+    char filename[FILENAME_MAX+1];
+    url = filename;
+    strncpyz(filename, w->last_url, FILENAME_MAX);
+    tok = mystrsep(&url, "?");
+    buffer_flush(w->response.data);
+    return mysendfile(w, (tok && *tok)?tok:"/");
+}
+
+void web_client_process_request(struct web_client *w) {
+
+    // start timing us
+    now_realtime_timeval(&w->tv_in);
+
+    switch(http_request_validate(w)) {
+        case HTTP_VALIDATION_OK:
+            if(unlikely(w->mode == WEB_CLIENT_MODE_OPTIONS)) {
+                w->response.data->contenttype = CT_TEXT_PLAIN;
+                buffer_flush(w->response.data);
+                buffer_strcat(w->response.data, "OK");
+                w->response.code = 200;
+            }
+            else
+                w->response.code = web_client_process_url(localhost, w, w->decoded_url);
+            break;
+
+        case HTTP_VALIDATION_INCOMPLETE:
+            if(w->response.data->len > TOO_BIG_REQUEST) {
+                strcpy(w->last_url, "too big request");
+
+                debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big (%zu bytes).", w->id, w->response.data->len);
+
+                buffer_flush(w->response.data);
+                buffer_sprintf(w->response.data, "Received request is too big  (%zu bytes).\r\n", w->response.data->len);
+                w->response.code = 400;
+            }
+            else {
+                // wait for more data
+                return;
+            }
+            break;
+
+        case HTTP_VALIDATION_NOT_SUPPORTED:
+            debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer);
+
+            buffer_flush(w->response.data);
+            buffer_strcat(w->response.data, "I don't understand you...\r\n");
+            w->response.code = 400;
+            break;
+    }
+
+    // keep track of the time we done processing
+    now_realtime_timeval(&w->tv_ready);
+
+    w->response.sent = 0;
+
+    // set a proper last modified date
+    if(unlikely(!w->response.data->date))
+        w->response.data->date = w->tv_ready.tv_sec;
+
+    web_client_send_http_header(w);
 
     // enable sending immediately if we have data
     if(w->response.data->len) w->wait_send = 1;
     else w->wait_send = 0;
 
-    // pretty logging
     switch(w->mode) {
         case WEB_CLIENT_MODE_OPTIONS:
             debug(D_WEB_CLIENT, "%llu: Done preparing the OPTIONS response. Sending data (%zu bytes) to client.", w->id, w->response.data->len);
@@ -2669,11 +2881,14 @@ void *web_client_main(void *ptr)
 
     struct web_client *w = ptr;
     struct pollfd fds[2], *ifd, *ofd;
-    int retval, fdmax = 0, timeout;
+    int retval, timeout;
+    nfds_t fdmax = 0;
 
     log_access("%llu: %s port %s connected on thread task id %d", w->id, w->client_ip, w->client_port, gettid());
 
     for(;;) {
+        if(unlikely(netdata_exit)) break;
+
         if(unlikely(w->dead)) {
             debug(D_WEB_CLIENT, "%llu: client is dead.", w->id);
             break;
@@ -2725,6 +2940,8 @@ void *web_client_main(void *ptr)
         timeout = web_client_timeout * 1000;
         retval = poll(fds, fdmax, timeout);
 
+        if(unlikely(netdata_exit)) break;
+
         if(unlikely(retval == -1)) {
             if(errno == EAGAIN || errno == EINTR) {
                 debug(D_WEB_CLIENT, "%llu: EAGAIN received.", w->id);
@@ -2739,6 +2956,8 @@ void *web_client_main(void *ptr)
             break;
         }
 
+        if(unlikely(netdata_exit)) break;
+
         int used = 0;
         if(w->wait_send && ofd->revents & POLLOUT) {
             used++;
@@ -2748,6 +2967,8 @@ void *web_client_main(void *ptr)
             }
         }
 
+        if(unlikely(netdata_exit)) break;
+
         if(w->wait_receive && (ifd->revents & POLLIN || ifd->revents & POLLPRI)) {
             used++;
             if(web_client_receive(w) < 0) {
@@ -2757,7 +2978,11 @@ void *web_client_main(void *ptr)
 
             if(w->mode == WEB_CLIENT_MODE_NORMAL) {
                 debug(D_WEB_CLIENT, "%llu: Attempting to process received data.", w->id);
-                web_client_process(w);
+                web_client_process_request(w);
+
+                // if the sockets are closed, may have transferred this client
+                // to plugins.d
+                if(w->ifd == -1 && w->ofd == -1) break;
             }
         }