@cryptotaxi247 / netdata-1 / commits / ea95904f5

moved web_client functions to web_client.c; added dictionary.c to support indexed dictionaries (not used yet)

Costa Tsaousis (ktsaou) committed Mar 29, 2015 at 18:49 UTC ea95904f5462d685cfae3781a40d9efd87a91ad6
8 files changed +1471 -1111
src/Makefile
+1 -1
@@ -25,7 +25,7 @@ endif
25 COMMON_FLAGS = BIN_DIR='$(BIN_DIR)' CONFIG_DIR='$(CONFIG_DIR)' LOG_DIR='$(LOG_DIR)' PLUGINS_DIR='$(PLUGINS_DIR)'
26
27 proc_sources = proc_net_dev.c proc_net_ip_vs_stats.c proc_diskstats.c proc_meminfo.c proc_net_netstat.c proc_net_snmp.c proc_net_stat_conntrack.c proc_stat.c proc_vmstat.c proc_net_rpc_nfsd.c
28 -sources = avl.c procfile.c common.c log.c popen.c url.c config.c web_buffer.c storage_number.c web_client.c global_statistics.c rrd.c rrd2json.c web_server.c plugins_d.c daemon.c plugin_tc.c plugin_checks.c plugin_idlejitter.c plugin_proc.c unit_test.c main.c
28 +sources = avl.c dictionary.c procfile.c common.c log.c popen.c url.c config.c web_buffer.c storage_number.c web_client.c global_statistics.c rrd.c rrd2json.c web_server.c plugins_d.c daemon.c plugin_tc.c plugin_checks.c plugin_idlejitter.c plugin_proc.c unit_test.c main.c
29 libs = -pthread -lz
30
31 ifdef debug
src/dictionary.c new
+146
@@ -0,0 +1,146 @@
1 +#include <pthread.h>
2 +#include <stdlib.h>
3 +#include <string.h>
4 +#include <inttypes.h>
5 +
6 +#include "avl.h"
7 +#include "common.h"
8 +#include "log.h"
9 +
10 +#include "dictionary.h"
11 +
12 +// ----------------------------------------------------------------------------
13 +// name_value index
14 +
15 +static int name_value_iterator(avl *a) { if(a) {}; return 0; }
16 +
17 +static int name_value_compare(void* a, void* b) {
18 + if(((NAME_VALUE *)a)->hash < ((NAME_VALUE *)b)->hash) return -1;
19 + else if(((NAME_VALUE *)a)->hash > ((NAME_VALUE *)b)->hash) return 1;
20 + else return strcmp(((NAME_VALUE *)a)->name, ((NAME_VALUE *)b)->name);
21 +}
22 +
23 +#define name_value_index_add(dict, cv) avl_insert(&((dict)->values_index), (avl *)(cv))
24 +#define name_value_index_del(dict, cv) avl_remove(&((dict)->values_index), (avl *)(cv))
25 +
26 +static NAME_VALUE *dictionary_name_value_index_find(DICTIONARY *dict, const char *name, unsigned long hash) {
27 + NAME_VALUE *result = NULL, tmp;
28 + tmp.hash = (hash)?hash:simple_hash(name);
29 + tmp.name = (char *)name;
30 +
31 + avl_search(&(dict->values_index), (avl *)&tmp, name_value_iterator, (avl **)&result);
32 + return result;
33 +}
34 +
35 +// ----------------------------------------------------------------------------
36 +
37 +static NAME_VALUE *dictionary_name_value_create(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
38 + debug(D_DICTIONARY, "Creating name value entry for name '%s', value '%s'.", name, value);
39 +
40 + NAME_VALUE *nv = calloc(1, sizeof(NAME_VALUE));
41 + if(!nv) fatal("Cannot allocate name_value of size %z", sizeof(NAME_VALUE));
42 +
43 + nv->name = strdup(name);
44 + if(!nv->name) fatal("Cannot allocate name_value.name of size %z", strlen(name));
45 + nv->hash = simple_hash(nv->name);
46 +
47 + nv->value = malloc(value_len);
48 + if(!nv->value) fatal("Cannot allocate name_value.value of size %z", value_len);
49 + memcpy(nv->value, value, value_len);
50 +
51 + // link it
52 + pthread_rwlock_wrlock(&dict->rwlock);
53 + nv->next = dict->values;
54 + dict->values = nv;
55 + pthread_rwlock_unlock(&dict->rwlock);
56 +
57 + // index it
58 + name_value_index_add(dict, nv);
59 +
60 + return nv;
61 +}
62 +
63 +static void dictionary_name_value_destroy(DICTIONARY *dict, NAME_VALUE *nv) {
64 + debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", nv->name);
65 +
66 + pthread_rwlock_wrlock(&dict->rwlock);
67 + if(dict->values == nv) dict->values = nv->next;
68 + else {
69 + NAME_VALUE *n = dict->values;
70 + while(n && n->next && n->next != nv) nv = nv->next;
71 + if(!n || n->next != nv) fatal("Cannot find name_value with name '%s' in dictionary.", nv->name);
72 + n->next = nv->next;
73 + nv->next = NULL;
74 + }
75 + pthread_rwlock_unlock(&dict->rwlock);
76 +
77 + free(nv->value);
78 + free(nv);
79 +}
80 +
81 +// ----------------------------------------------------------------------------
82 +
83 +DICTIONARY *dictionary_create(void) {
84 + debug(D_DICTIONARY, "Creating dictionary.");
85 +
86 + DICTIONARY *dict = calloc(1, sizeof(DICTIONARY));
87 + if(!dict) fatal("Cannot allocate DICTIONARY");
88 +
89 + dict->values_index.compar = name_value_compare;
90 + pthread_rwlock_init(&dict->rwlock, NULL);
91 +
92 + return dict;
93 +}
94 +
95 +void dictionary_destroy(DICTIONARY *dict) {
96 + debug(D_DICTIONARY, "Destroying dictionary.");
97 +
98 + pthread_rwlock_wrlock(&dict->rwlock);
99 + while(dict->values) dictionary_name_value_destroy(dict, dict->values);
100 + pthread_rwlock_unlock(&dict->rwlock);
101 +
102 + free(dict);
103 +}
104 +
105 +// ----------------------------------------------------------------------------
106 +
107 +void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len) {
108 + debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
109 +
110 + pthread_rwlock_rdlock(&dict->rwlock);
111 + NAME_VALUE *nv = dictionary_name_value_index_find(dict, name, 0);
112 + pthread_rwlock_unlock(&dict->rwlock);
113 + if(!nv) {
114 + debug(D_DICTIONARY, "Dictionary entry with name '%s' not found. Creating a new one.", name);
115 + nv = dictionary_name_value_create(dict, name, value, value_len);
116 + if(!nv) fatal("Cannot create name_value.");
117 + return nv->value;
118 + }
119 + else {
120 + debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", name);
121 + pthread_rwlock_wrlock(&dict->rwlock);
122 + void *old = nv->value;
123 + nv->value = malloc(value_len);
124 + if(!nv->value) fatal("Cannot allocate value of size %z", value_len);
125 + memcpy(nv->value, value, value_len);
126 + pthread_rwlock_unlock(&dict->rwlock);
127 + free(old);
128 + }
129 +
130 + return nv->value;
131 +}
132 +
133 +void *dictionary_get(DICTIONARY *dict, const char *name) {
134 + debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name);
135 +
136 + pthread_rwlock_rdlock(&dict->rwlock);
137 + NAME_VALUE *nv = dictionary_name_value_index_find(dict, name, 0);
138 + pthread_rwlock_unlock(&dict->rwlock);
139 + if(!nv) {
140 + debug(D_DICTIONARY, "Not found dictionary entry with name '%s'.", name);
141 + return NULL;
142 + }
143 +
144 + debug(D_DICTIONARY, "Found dictionary entry with name '%s'.", name);
145 + return nv->value;
146 +}
src/dictionary.h new
+29
@@ -0,0 +1,29 @@
1 +#include "web_buffer.h"
2 +
3 +#ifndef NETDATA_DICTIONARY_H
4 +#define NETDATA_DICTIONARY_H 1
5 +
6 +typedef struct name_value {
7 + avl avl; // the index - this has to be first!
8 +
9 + unsigned long hash; // a simple hash to speed up searching
10 + // we first compare hashes, and only if the hashes are equal we do string comparisons
11 +
12 + char *name;
13 + char *value;
14 +
15 + struct name_value *next;
16 +} NAME_VALUE;
17 +
18 +typedef struct dictionary {
19 + NAME_VALUE *values;
20 + avl_tree values_index;
21 + pthread_rwlock_t rwlock;
22 +} DICTIONARY;
23 +
24 +extern DICTIONARY *dictionary_create(void);
25 +extern void dictionary_destroy(DICTIONARY *dict);
26 +extern void *dictionary_set(DICTIONARY *dict, const char *name, void *value, size_t value_len);
27 +extern void *dictionary_get(DICTIONARY *dict, const char *name);
28 +
29 +#endif /* NETDATA_DICTIONARY_H */
src/log.h
+1
@@ -22,6 +22,7 @@
22 #define D_NFACCT_LOOP 0x00008000
23 #define D_PROCFILE 0x00010000
24 #define D_RRD_CALLS 0x00020000
25 +#define D_DICTIONARY 0x00040000
26
27 #define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
28 //#define DEBUG 0xffffffff
src/rrd2json.c
+95
@@ -1,5 +1,6 @@
1 #include <pthread.h>
2 #include <sys/time.h>
3 +#include <stdlib.h>
4
5 #include "log.h"
6 #include "common.h"
@@ -483,3 +484,97 @@ unsigned long rrd_stats_json(int type, RRDSET *st, struct web_buffer *wb, int en
484 pthread_rwlock_unlock(&st->rwlock);
485 return last_timestamp;
486 }
487 +
488 +
489 +/*
490 +unsigned long rrdset2json(int type, RRDSET *st, struct web_buffer *wb, int entries_to_show, int group, int group_method, time_t after, time_t before, int only_non_zero) {
491 +
492 + if(!st->dimensions) {
493 + web_buffer_printf(wb, "No dimensions yet.");
494 + return 0;
495 + }
496 +
497 + int c;
498 + pthread_rwlock_rdlock(&st->rwlock);
499 +
500 + // -------------------------------------------------------------------------
501 + // validate the parameters
502 +
503 + if(entries_to_show < 1) entries_to_show = 1;
504 + if(group < 1) group = 1;
505 +
506 + // make sure current_entry is within limits
507 + long current_entry = (long)st->current_entry - (long)1;
508 + if(current_entry < 0) current_entry = 0;
509 + else if(current_entry >= st->entries) current_entry = st->entries - 1;
510 +
511 + // find the oldest entry of the round-robin
512 + long max_entries = (st->counter < (unsigned long)st->entries) ? st->counter : (unsigned long)st->entries;
513 + if(entries_to_show > max_entries) entries_to_show = max_entries;
514 +
515 + if(before == 0 || before > st->last_updated.tv_sec) before = st->last_updated.tv_sec;
516 + if(after == 0 || after < rrdset_first_entry_t(st)) after = rrdset_first_entry_t(st);
517 +
518 + if(entries_to_show > (before - after) / st->update_every / group) entries_to_show = (before - after) / st->update_every / group;
519 +
520 + // -------------------------------------------------------------------------
521 + // checks for debuging
522 +
523 + if(st->debug) {
524 + debug(D_RRD_STATS, "%s first_entry_t = %lu, last_entry_t = %lu, duration = %lu, after = %lu, before = %lu, duration = %lu, entries_to_show = %lu, group = %lu, max_entries = %ld"
525 + , st->id
526 + , rrdset_first_entry_t(st)
527 + , st->last_updated.tv_sec
528 + , st->last_updated.tv_sec - rrdset_first_entry_t(st)
529 + , after
530 + , before
531 + , before - after
532 + , entries_to_show
533 + , group
534 + , max_entries
535 + );
536 +
537 + if(before < after)
538 + debug(D_RRD_STATS, "WARNING: %s The newest value in the database (%lu) is earlier than the oldest (%lu)", st->name, before, after);
539 +
540 + if((before - after) > st->entries * st->update_every)
541 + debug(D_RRD_STATS, "WARNING: %s The time difference between the oldest and the newest entries (%lu) is higher than the capacity of the database (%lu)", st->name, before - after, st->entries * st->update_every);
542 + }
543 +
544 + // -------------------------------------------------------------------------
545 +
546 + int dimensions = 0, i, j;
547 + RRDDIM *rd;
548 + for( rd = st->dimensions ; rd ; rd = rd->next) dimensions++;
549 +
550 + uint32_t *annotations[dimensions];
551 + calculated_number *values[dimensions];
552 + int enabled[dimensions];
553 +
554 + for( rd = st->dimensions, i = 0 ; rd && i < dimensions ; rd = rd->next, i++) {
555 + annotations[i] = malloc(sizeof(uint32_t) * entries_to_show);
556 + if(!annotations[i]) fatal("Cannot allocate %z bytes of memory.", sizeof(uint32_t) * entries_to_show);
557 +
558 + values[i] = malloc(sizeof(calculated_number) * entries_to_show);
559 + if(!values[i]) fatal("Cannot allocate %z bytes of memory.", sizeof(calculated_number) * entries_to_show);
560 +
561 + calculated_number sum = rrddim2values(st, rd, values, annotations, entries_to_show, group, group_method, after, before);
562 + if(only_non_zero && sum == 0) enabled[i] = 0;
563 + else enabled[i] = 1;
564 + }
565 +
566 + switch(type) {
567 + case DATASOURCE_GOOGLE_JSON:
568 + case DATASOURCE_GOOGLE_JSONP:
569 + //rrdvalues2googlejson(st, rd, values, annotations, enabled, entries_to_show, );
570 + break;
571 +
572 + case DATASOURCE_JSON:
573 + default:
574 + //rrdvalues2json();
575 + break;
576 + }
577 +
578 + //return ???;
579 +}
580 +*/
src/web_client.c
+1171 -1
@@ -1,16 +1,35 @@
1 +// enable strcasestr()
2 +#define _GNU_SOURCE
3 +
4 #include <stdlib.h>
5 #include <sys/types.h>
6 #include <sys/socket.h>
7 #include <netinet/in.h>
8 #include <arpa/inet.h>
9 +#include <errno.h>
10 +#include <pthread.h>
11 +#include <sys/stat.h>
12 +#include <fcntl.h>
13 +#include <netinet/tcp.h>
14 +#include <malloc.h>
15
7 -#include "global_statistics.h"
16 +#include "common.h"
17 #include "log.h"
18 +#include "config.h"
19 +#include "url.h"
20 +#include "web_buffer.h"
21 +#include "web_server.h"
22 +#include "global_statistics.h"
23 +#include "rrd.h"
24 +#include "rrd2json.h"
25
26 #include "web_client.h"
27
28 #define INITIAL_WEB_DATA_LENGTH 65536
29
30 +int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
31 +
32 +
33 struct web_client *web_clients = NULL;
34 unsigned long long web_clients_count = 0;
35
@@ -108,3 +127,1154 @@ struct web_client *web_client_free(struct web_client *w)
127
128 return(n);
129 }
130 +
131 +int mysendfile(struct web_client *w, char *filename)
132 +{
133 + static char *web_dir = NULL;
134 + if(!web_dir) web_dir = config_get("global", "web files directory", "web");
135 +
136 + debug(D_WEB_CLIENT, "%llu: Looking for file '%s'...", w->id, filename);
137 +
138 + // skip leading slashes
139 + while (*filename == '/') filename++;
140 +
141 + // if the filename contain known paths, skip them
142 + if(strncmp(filename, WEB_PATH_DATA "/", strlen(WEB_PATH_DATA) + 1) == 0) filename = &filename[strlen(WEB_PATH_DATA) + 1];
143 + else if(strncmp(filename, WEB_PATH_DATASOURCE "/", strlen(WEB_PATH_DATASOURCE) + 1) == 0) filename = &filename[strlen(WEB_PATH_DATASOURCE) + 1];
144 + else if(strncmp(filename, WEB_PATH_GRAPH "/", strlen(WEB_PATH_GRAPH) + 1) == 0) filename = &filename[strlen(WEB_PATH_GRAPH) + 1];
145 + else if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) filename = &filename[strlen(WEB_PATH_FILE) + 1];
146 +
147 + // if the filename contains a / or a .., refuse to serve it
148 + if(strchr(filename, '/') != 0 || strstr(filename, "..") != 0) {
149 + debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
150 + web_buffer_printf(w->data, "File '%s' cannot be served. Filenames cannot contain / or ..", filename);
151 + return 400;
152 + }
153 +
154 + // access the file
155 + char webfilename[FILENAME_MAX + 1];
156 + snprintf(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename);
157 +
158 + // check if the file exists
159 + struct stat stat;
160 + if(lstat(webfilename, &stat) != 0) {
161 + error("%llu: File '%s' is not found.", w->id, webfilename);
162 + web_buffer_printf(w->data, "File '%s' does not exist, or is not accessible.", filename);
163 + return 404;
164 + }
165 +
166 + // check if the file is owned by us
167 + if(stat.st_uid != getuid() && stat.st_uid != geteuid()) {
168 + error("%llu: File '%s' is owned by user %d (I run as user %d). Access Denied.", w->id, webfilename, stat.st_uid, getuid());
169 + web_buffer_printf(w->data, "Access to file '%s' is not permitted.", filename);
170 + return 403;
171 + }
172 +
173 + // open the file
174 + w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY);
175 + if(w->ifd == -1) {
176 + w->ifd = w->ofd;
177 +
178 + if(errno == EBUSY || errno == EAGAIN) {
179 + error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename);
180 + snprintf(w->response_header, MAX_HTTP_HEADER_SIZE, "Location: /" WEB_PATH_FILE "/%s\r\n", filename);
181 + web_buffer_printf(w->data, "The file '%s' is currently busy. Please try again later.", filename);
182 + return 307;
183 + }
184 + else {
185 + error("%llu: Cannot open file '%s'.", w->id, webfilename);
186 + web_buffer_printf(w->data, "Cannot open file '%s'.", filename);
187 + return 404;
188 + }
189 + }
190 +
191 + // pick a Content-Type for the file
192 + if(strstr(filename, ".html") != NULL) w->data->contenttype = CT_TEXT_HTML;
193 + else if(strstr(filename, ".js") != NULL) w->data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
194 + else if(strstr(filename, ".css") != NULL) w->data->contenttype = CT_TEXT_CSS;
195 + else if(strstr(filename, ".xml") != NULL) w->data->contenttype = CT_TEXT_XML;
196 + else if(strstr(filename, ".xsl") != NULL) w->data->contenttype = CT_TEXT_XSL;
197 + else if(strstr(filename, ".txt") != NULL) w->data->contenttype = CT_TEXT_PLAIN;
198 + else if(strstr(filename, ".svg") != NULL) w->data->contenttype = CT_IMAGE_SVG_XML;
199 + else if(strstr(filename, ".ttf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE;
200 + else if(strstr(filename, ".otf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE;
201 + else if(strstr(filename, ".woff") != NULL) w->data->contenttype = CT_APPLICATION_FONT_WOFF;
202 + else if(strstr(filename, ".eot") != NULL) w->data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ;
203 + else w->data->contenttype = CT_APPLICATION_OCTET_STREAM;
204 +
205 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, webfilename, stat.st_size, w->ifd, w->ofd);
206 +
207 + w->mode = WEB_CLIENT_MODE_FILECOPY;
208 + w->wait_receive = 1;
209 + w->wait_send = 0;
210 + w->data->bytes = 0;
211 + w->data->buffer[0] = '\0';
212 + w->data->rbytes = stat.st_size;
213 + w->data->date = stat.st_mtim.tv_sec;
214 +
215 + return 200;
216 +}
217 +
218 +void web_client_reset(struct web_client *w)
219 +{
220 + struct timeval tv;
221 + gettimeofday(&tv, NULL);
222 +
223 + long sent = w->zoutput?(long)w->zstream.total_out:((w->mode == WEB_CLIENT_MODE_FILECOPY)?w->data->rbytes:w->data->bytes);
224 + long size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->data->rbytes:w->data->bytes;
225 +
226 + if(w->last_url[0]) log_access("%llu: (sent/all = %ld/%ld bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %s: '%s'",
227 + w->id,
228 + sent, size, -((size>0)?((float)(size-sent)/(float)size * 100.0):0.0),
229 + (float)usecdiff(&w->tv_ready, &w->tv_in) / 1000.0,
230 + (float)usecdiff(&tv, &w->tv_ready) / 1000.0,
231 + (float)usecdiff(&tv, &w->tv_in) / 1000.0,
232 + (w->mode == WEB_CLIENT_MODE_FILECOPY)?"filecopy":"data",
233 + w->last_url
234 + );
235 +
236 + debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id);
237 +
238 + if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
239 + debug(D_WEB_CLIENT, "%llu: Closing filecopy input file.", w->id);
240 + close(w->ifd);
241 + w->ifd = w->ofd;
242 + }
243 +
244 + w->last_url[0] = '\0';
245 +
246 + w->data->contenttype = CT_TEXT_PLAIN;
247 + w->mode = WEB_CLIENT_MODE_NORMAL;
248 +
249 + w->data->rbytes = 0;
250 + w->data->bytes = 0;
251 + w->data->sent = 0;
252 +
253 + w->response_header[0] = '\0';
254 + w->data->buffer[0] = '\0';
255 +
256 + w->wait_receive = 1;
257 + w->wait_send = 0;
258 +
259 + // if we had enabled compression, release it
260 + if(w->zinitialized) {
261 + debug(D_DEFLATE, "%llu: Reseting compression.", w->id);
262 + deflateEnd(&w->zstream);
263 + w->zoutput = 0;
264 + w->zsent = 0;
265 + w->zhave = 0;
266 + w->zstream.avail_in = 0;
267 + w->zstream.avail_out = 0;
268 + w->zstream.total_in = 0;
269 + w->zstream.total_out = 0;
270 + w->zinitialized = 0;
271 + }
272 +}
273 +
274 +void web_client_enable_deflate(struct web_client *w) {
275 + if(w->zinitialized == 1) {
276 + error("%llu: Compression has already be initialized for this client.", w->id);
277 + return;
278 + }
279 +
280 + if(w->data->sent) {
281 + error("%llu: Cannot enable compression in the middle of a conversation.", w->id);
282 + return;
283 + }
284 +
285 + w->zstream.zalloc = Z_NULL;
286 + w->zstream.zfree = Z_NULL;
287 + w->zstream.opaque = Z_NULL;
288 +
289 + w->zstream.next_in = (Bytef *)w->data->buffer;
290 + w->zstream.avail_in = 0;
291 + w->zstream.total_in = 0;
292 +
293 + w->zstream.next_out = w->zbuffer;
294 + w->zstream.avail_out = 0;
295 + w->zstream.total_out = 0;
296 +
297 + w->zstream.zalloc = Z_NULL;
298 + w->zstream.zfree = Z_NULL;
299 + w->zstream.opaque = Z_NULL;
300 +
301 +// if(deflateInit(&w->zstream, Z_DEFAULT_COMPRESSION) != Z_OK) {
302 +// error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
303 +// return;
304 +// }
305 +
306 + // Select GZIP compression: windowbits = 15 + 16 = 31
307 + if(deflateInit2(&w->zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
308 + error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
309 + return;
310 + }
311 +
312 + w->zsent = 0;
313 + w->zoutput = 1;
314 + w->zinitialized = 1;
315 +
316 + debug(D_DEFLATE, "%llu: Initialized compression.", w->id);
317 +}
318 +
319 +int web_client_data_request(struct web_client *w, char *url, int datasource_type)
320 +{
321 + char *args = strchr(url, '?');
322 + if(args) {
323 + *args='\0';
324 + args = &args[1];
325 + }
326 +
327 + // get the name of the data to show
328 + char *tok = mystrsep(&url, "/");
329 + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
330 +
331 + // do we have such a data set?
332 + RRDSET *st = rrdset_find_byname(tok);
333 + if(!st) st = rrdset_find(tok);
334 + if(!st) {
335 + // we don't have it
336 + // try to send a file with that name
337 + w->data->bytes = 0;
338 + return(mysendfile(w, tok));
339 + }
340 +
341 + // we have it
342 + debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok);
343 +
344 + // how many entries does the client want?
345 + long lines = rrd_default_history_entries;
346 + long group_count = 1;
347 + time_t after = 0, before = 0;
348 + int group_method = GROUP_AVERAGE;
349 + int nonzero = 0;
350 +
351 + if(url) {
352 + // parse the lines required
353 + tok = mystrsep(&url, "/");
354 + if(tok) lines = atoi(tok);
355 + if(lines < 1) lines = 1;
356 + }
357 + if(url) {
358 + // parse the group count required
359 + tok = mystrsep(&url, "/");
360 + if(tok) group_count = atoi(tok);
361 + if(group_count < 1) group_count = 1;
362 + //if(group_count > save_history / 20) group_count = save_history / 20;
363 + }
364 + if(url) {
365 + // parse the grouping method required
366 + tok = mystrsep(&url, "/");
367 + if(strcmp(tok, "max") == 0) group_method = GROUP_MAX;
368 + else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE;
369 + else if(strcmp(tok, "sum") == 0) group_method = GROUP_SUM;
370 + else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok);
371 + }
372 + if(url) {
373 + // parse after time
374 + tok = mystrsep(&url, "/");
375 + if(tok) after = strtoul(tok, NULL, 10);
376 + if(after < 0) after = 0;
377 + }
378 + if(url) {
379 + // parse before time
380 + tok = mystrsep(&url, "/");
381 + if(tok) before = strtoul(tok, NULL, 10);
382 + if(before < 0) before = 0;
383 + }
384 + if(url) {
385 + // parse nonzero
386 + tok = mystrsep(&url, "/");
387 + if(tok && strcmp(tok, "nonzero") == 0) nonzero = 1;
388 + }
389 +
390 + w->data->contenttype = CT_APPLICATION_JSON;
391 + w->data->bytes = 0;
392 +
393 + char *google_version = "0.6";
394 + char *google_reqId = "0";
395 + char *google_sig = "0";
396 + char *google_out = "json";
397 + char *google_responseHandler = "google.visualization.Query.setResponse";
398 + char *google_outFileName = NULL;
399 + unsigned long last_timestamp_in_data = 0;
400 + if(datasource_type == DATASOURCE_GOOGLE_JSON || datasource_type == DATASOURCE_GOOGLE_JSONP) {
401 +
402 + w->data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
403 +
404 + while(args) {
405 + tok = mystrsep(&args, "&");
406 + if(tok) {
407 + char *name = mystrsep(&tok, "=");
408 + if(name && strcmp(name, "tqx") == 0) {
409 + char *key = mystrsep(&tok, ":");
410 + char *value = mystrsep(&tok, ";");
411 + if(key && value && *key && *value) {
412 + if(strcmp(key, "version") == 0)
413 + google_version = value;
414 +
415 + else if(strcmp(key, "reqId") == 0)
416 + google_reqId = value;
417 +
418 + else if(strcmp(key, "sig") == 0)
419 + google_sig = value;
420 +
421 + else if(strcmp(key, "out") == 0)
422 + google_out = value;
423 +
424 + else if(strcmp(key, "responseHandler") == 0)
425 + google_responseHandler = value;
426 +
427 + else if(strcmp(key, "outFileName") == 0)
428 + google_outFileName = value;
429 + }
430 + }
431 + }
432 + }
433 +
434 + debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
435 + w->id, google_version, google_reqId, google_sig, google_out, google_responseHandler, google_outFileName
436 + );
437 +
438 + if(datasource_type == DATASOURCE_GOOGLE_JSONP) {
439 + last_timestamp_in_data = strtoul(google_sig, NULL, 0);
440 +
441 + // check the client wants json
442 + if(strcmp(google_out, "json") != 0) {
443 + w->data->bytes = snprintf(w->data->buffer, w->data->size,
444 + "%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.'}]});",
445 + google_responseHandler, google_version, google_reqId, google_out);
446 + return 200;
447 + }
448 + }
449 + }
450 +
451 + if(datasource_type == DATASOURCE_GOOGLE_JSONP) {
452 + w->data->bytes = snprintf(w->data->buffer, w->data->size,
453 + "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:",
454 + google_responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
455 + }
456 +
457 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group, %d group_method, %lu after, %lu before).", w->id, st->name, st->id, lines, group_count, group_method, after, before);
458 + unsigned long timestamp_in_data = rrd_stats_json(datasource_type, st, w->data, lines, group_count, group_method, after, before, nonzero);
459 +
460 + if(datasource_type == DATASOURCE_GOOGLE_JSONP) {
461 + if(timestamp_in_data > last_timestamp_in_data)
462 + w->data->bytes += snprintf(&w->data->buffer[w->data->bytes], w->data->size - w->data->bytes, "});");
463 +
464 + else {
465 + // the client already has the latest data
466 + w->data->bytes = snprintf(w->data->buffer, w->data->size,
467 + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
468 + google_responseHandler, google_version, google_reqId);
469 + }
470 + }
471 +
472 + return 200;
473 +}
474 +
475 +/*
476 +int web_client_parse_request(struct web_client *w) {
477 + // protocol
478 + // hostname
479 + // path
480 + // query string name-value
481 + // http version
482 + // method
483 + // http request headers name-value
484 +
485 + web_client_clean_request(w);
486 +
487 + debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->data->bytes, w->data->buffer);
488 +
489 + char *buf = w->data->buffer;
490 + char *line, *tok;
491 +
492 + // ------------------------------------------------------------------------
493 + // the first line
494 +
495 + if(buf && (line = strsep(&buf, "\r\n"))) {
496 + // method
497 + if(line && (tok = strsep(&line, " "))) {
498 + w->request.protocol = strdup(tok);
499 + }
500 + else goto cleanup;
501 +
502 + // url
503 + }
504 + else goto cleanup;
505 +
506 + // ------------------------------------------------------------------------
507 + // the rest of the lines
508 +
509 + while(buf && (line = strsep(&buf, "\r\n"))) {
510 + while(line && (tok = strsep(&line, ": "))) {
511 + }
512 + }
513 +
514 + char *url = NULL;
515 +
516 +
517 +cleanup:
518 + web_client_clean_request(w);
519 + return 0;
520 +}
521 +*/
522 +
523 +void web_client_process(struct web_client *w) {
524 + int code = 500;
525 + int bytes;
526 +
527 + w->wait_receive = 0;
528 +
529 + // check if we have an empty line (end of HTTP header)
530 + if(strstr(w->data->buffer, "\r\n\r\n")) {
531 + global_statistics_lock();
532 + global_statistics.web_requests++;
533 + global_statistics_unlock();
534 +
535 + gettimeofday(&w->tv_in, NULL);
536 + debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->data->bytes, w->data->buffer);
537 +
538 + // check if the client requested keep-alive HTTP
539 + if(strcasestr(w->data->buffer, "Connection: keep-alive")) w->keepalive = 1;
540 + else w->keepalive = 0;
541 +
542 + // check if the client accepts deflate
543 + if(strstr(w->data->buffer, "gzip"))
544 + web_client_enable_deflate(w);
545 +
546 + int datasource_type = DATASOURCE_GOOGLE_JSONP;
547 + //if(strstr(w->data->buffer, "X-DataSource-Auth"))
548 + // datasource_type = DATASOURCE_GOOGLE_JSON;
549 +
550 + char *buf = w->data->buffer;
551 + char *tok = strsep(&buf, " \r\n");
552 + char *url = NULL;
553 + char *pointer_to_free = NULL; // keep url_decode() allocated buffer
554 +
555 + if(buf && strcmp(tok, "GET") == 0) {
556 + tok = strsep(&buf, " \r\n");
557 + pointer_to_free = url = url_decode(tok);
558 + debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url);
559 + }
560 + else if (buf && strcmp(tok, "POST") == 0) {
561 + w->keepalive = 0;
562 + tok = strsep(&buf, " \r\n");
563 + pointer_to_free = url = url_decode(tok);
564 +
565 + debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url);
566 + }
567 +
568 + w->last_url[0] = '\0';
569 + if(url) {
570 + strncpy(w->last_url, url, URL_MAX);
571 + w->last_url[URL_MAX] = '\0';
572 +
573 + tok = mystrsep(&url, "/?&");
574 +
575 + debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
576 +
577 + if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
578 + // the client is requesting rrd data
579 + datasource_type = DATASOURCE_JSON;
580 + code = web_client_data_request(w, url, datasource_type);
581 + }
582 + else if(strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource"
583 + // the client is requesting google datasource
584 + code = web_client_data_request(w, url, datasource_type);
585 + }
586 + else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
587 + // the client is requesting an rrd graph
588 +
589 + // get the name of the data to show
590 + tok = mystrsep(&url, "/?&");
591 + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
592 +
593 + // do we have such a data set?
594 + RRDSET *st = rrdset_find_byname(tok);
595 + if(!st) st = rrdset_find(tok);
596 + if(!st) {
597 + // we don't have it
598 + // try to send a file with that name
599 + w->data->bytes = 0;
600 + code = mysendfile(w, tok);
601 + }
602 + else {
603 + code = 200;
604 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
605 + w->data->contenttype = CT_APPLICATION_JSON;
606 + w->data->bytes = 0;
607 + rrd_stats_graph_json(st, url, w->data);
608 + }
609 + }
610 + else if(strcmp(tok, "debug") == 0) {
611 + w->data->bytes = 0;
612 +
613 + // get the name of the data to show
614 + tok = mystrsep(&url, "/?&");
615 + debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
616 +
617 + // do we have such a data set?
618 + RRDSET *st = rrdset_find_byname(tok);
619 + if(!st) st = rrdset_find(tok);
620 + if(!st) {
621 + code = 404;
622 + web_buffer_printf(w->data, "Chart %s is not found.\r\n", tok);
623 + debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok);
624 + }
625 + else {
626 + code = 200;
627 + debug_flags |= D_RRD_STATS;
628 + st->debug = st->debug?0:1;
629 + web_buffer_printf(w->data, "Chart %s has now debug %s.\r\n", tok, st->debug?"enabled":"disabled");
630 + debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, st->debug?"enabled":"disabled");
631 + }
632 + }
633 + else if(strcmp(tok, "mirror") == 0) {
634 + code = 200;
635 +
636 + debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
637 +
638 + // replace the zero bytes with spaces
639 + int i;
640 + for(i = 0; i < w->data->size; i++)
641 + if(w->data->buffer[i] == '\0') w->data->buffer[i] = ' ';
642 +
643 + // just leave the buffer as is
644 + // it will be copied back to the client
645 + }
646 + else if(strcmp(tok, "list") == 0) {
647 + code = 200;
648 +
649 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
650 +
651 + w->data->bytes = 0;
652 + RRDSET *st = rrdset_root;
653 +
654 + for ( ; st ; st = st->next )
655 + web_buffer_printf(w->data, "%s\n", st->name);
656 + }
657 + else if(strcmp(tok, "all.json") == 0) {
658 + code = 200;
659 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
660 +
661 + w->data->contenttype = CT_APPLICATION_JSON;
662 + w->data->bytes = 0;
663 + rrd_stats_all_json(w->data);
664 + }
665 + else if(strcmp(tok, "netdata.conf") == 0) {
666 + code = 200;
667 + debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
668 +
669 + w->data->contenttype = CT_TEXT_PLAIN;
670 + w->data->bytes = 0;
671 + generate_config(w->data, 0);
672 + }
673 + else if(strcmp(tok, WEB_PATH_FILE) == 0) { // "file"
674 + tok = mystrsep(&url, "/?&");
675 + if(tok && *tok) code = mysendfile(w, tok);
676 + else {
677 + code = 400;
678 + w->data->bytes = 0;
679 + strcpy(w->data->buffer, "You have to give a filename to get.\r\n");
680 + w->data->bytes = strlen(w->data->buffer);
681 + }
682 + }
683 + else if(!tok[0]) {
684 + w->data->bytes = 0;
685 + code = mysendfile(w, "index.html");
686 + }
687 + else {
688 + w->data->bytes = 0;
689 + code = mysendfile(w, tok);
690 + }
691 +
692 + }
693 + else {
694 + strcpy(w->last_url, "not a valid response");
695 +
696 + if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf);
697 +
698 + code = 500;
699 + w->data->bytes = 0;
700 + strcpy(w->data->buffer, "I don't understand you...\r\n");
701 + w->data->bytes = strlen(w->data->buffer);
702 + }
703 +
704 + // free url_decode() buffer
705 + if(pointer_to_free) free(pointer_to_free);
706 + }
707 + else if(w->data->bytes > 8192) {
708 + strcpy(w->last_url, "too big request");
709 +
710 + debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big.", w->id);
711 +
712 + code = 400;
713 + w->data->bytes = 0;
714 + strcpy(w->data->buffer, "Received request is too big.\r\n");
715 + w->data->bytes = strlen(w->data->buffer);
716 + }
717 + else {
718 + // wait for more data
719 + w->wait_receive = 1;
720 + return;
721 + }
722 +
723 + if(w->data->bytes > w->data->size) {
724 + error("%llu: memory overflow encountered (size is %ld, written %ld).", w->data->size, w->data->bytes);
725 + }
726 +
727 + gettimeofday(&w->tv_ready, NULL);
728 + w->data->date = time(NULL);
729 + w->data->sent = 0;
730 +
731 + // prepare the HTTP response header
732 + debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code);
733 +
734 + char *content_type_string = "";
735 + switch(w->data->contenttype) {
736 + case CT_TEXT_HTML:
737 + content_type_string = "text/html";
738 + break;
739 +
740 + case CT_APPLICATION_XML:
741 + content_type_string = "application/xml";
742 + break;
743 +
744 + case CT_APPLICATION_JSON:
745 + content_type_string = "application/json";
746 + break;
747 +
748 + case CT_APPLICATION_X_JAVASCRIPT:
749 + content_type_string = "application/x-javascript";
750 + break;
751 +
752 + case CT_TEXT_CSS:
753 + content_type_string = "text/css";
754 + break;
755 +
756 + case CT_TEXT_XML:
757 + content_type_string = "text/xml";
758 + break;
759 +
760 + case CT_TEXT_XSL:
761 + content_type_string = "text/xsl";
762 + break;
763 +
764 + case CT_APPLICATION_OCTET_STREAM:
765 + content_type_string = "application/octet-stream";
766 + break;
767 +
768 + case CT_IMAGE_SVG_XML:
769 + content_type_string = "image/svg+xml";
770 + break;
771 +
772 + case CT_APPLICATION_X_FONT_TRUETYPE:
773 + content_type_string = "application/x-font-truetype";
774 + break;
775 +
776 + case CT_APPLICATION_X_FONT_OPENTYPE:
777 + content_type_string = "application/x-font-opentype";
778 + break;
779 +
780 + case CT_APPLICATION_FONT_WOFF:
781 + content_type_string = "application/font-woff";
782 + break;
783 +
784 + case CT_APPLICATION_VND_MS_FONTOBJ:
785 + content_type_string = "application/vnd.ms-fontobject";
786 + break;
787 +
788 + default:
789 + case CT_TEXT_PLAIN:
790 + content_type_string = "text/plain";
791 + break;
792 + }
793 +
794 + char *code_msg = "";
795 + switch(code) {
796 + case 200:
797 + code_msg = "OK";
798 + break;
799 +
800 + case 307:
801 + code_msg = "Temporary Redirect";
802 + break;
803 +
804 + case 400:
805 + code_msg = "Bad Request";
806 + break;
807 +
808 + case 403:
809 + code_msg = "Forbidden";
810 + break;
811 +
812 + case 404:
813 + code_msg = "Not Found";
814 + break;
815 +
816 + default:
817 + code_msg = "Internal Server Error";
818 + break;
819 + }
820 +
821 + char date[100];
822 + struct tm tm = *gmtime(&w->data->date);
823 + strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", &tm);
824 +
825 + char custom_header[MAX_HTTP_HEADER_SIZE + 1] = "";
826 + if(w->response_header[0])
827 + strcpy(custom_header, w->response_header);
828 +
829 + int headerlen = 0;
830 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
831 + "HTTP/1.1 %d %s\r\n"
832 + "Connection: %s\r\n"
833 + "Server: NetData Embedded HTTP Server\r\n"
834 + "Content-Type: %s\r\n"
835 + "Access-Control-Allow-Origin: *\r\n"
836 + "Date: %s\r\n"
837 + , code, code_msg
838 + , w->keepalive?"keep-alive":"close"
839 + , content_type_string
840 + , date
841 + );
842 +
843 + if(custom_header[0])
844 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen, "%s", custom_header);
845 +
846 + if(w->mode == WEB_CLIENT_MODE_NORMAL) {
847 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
848 + "Expires: %s\r\n"
849 + "Cache-Control: no-cache\r\n"
850 + , date
851 + );
852 + }
853 + else {
854 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
855 + "Cache-Control: public\r\n"
856 + );
857 + }
858 +
859 + // if we know the content length, put it
860 + if(!w->zoutput && (w->data->bytes || w->data->rbytes))
861 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
862 + "Content-Length: %ld\r\n"
863 + , w->data->bytes?w->data->bytes:w->data->rbytes
864 + );
865 + else if(!w->zoutput)
866 + w->keepalive = 0; // content-length is required for keep-alive
867 +
868 + if(w->zoutput) {
869 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
870 + "Content-Encoding: gzip\r\n"
871 + "Transfer-Encoding: chunked\r\n"
872 + );
873 + }
874 +
875 + headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen, "\r\n");
876 +
877 + // disable TCP_NODELAY, to buffer the header
878 + int flag = 0;
879 + if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to disable TCP_NODELAY on socket.", w->id);
880 +
881 + // sent the HTTP header
882 + debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %d: '%s'", w->id, headerlen, w->response_header);
883 +
884 + bytes = send(w->ofd, w->response_header, headerlen, 0);
885 + if(bytes != headerlen)
886 + error("%llu: HTTP Header failed to be sent (I sent %d bytes but the system sent %d bytes).", w->id, headerlen, bytes);
887 + else {
888 + global_statistics_lock();
889 + global_statistics.bytes_sent += bytes;
890 + global_statistics_unlock();
891 + }
892 +
893 + // enable TCP_NODELAY, to send all data immediately at the next send()
894 + flag = 1;
895 + if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to enable TCP_NODELAY on socket.", w->id);
896 +
897 + // enable sending immediately if we have data
898 + if(w->data->bytes) w->wait_send = 1;
899 + else w->wait_send = 0;
900 +
901 + // pretty logging
902 + switch(w->mode) {
903 + case WEB_CLIENT_MODE_NORMAL:
904 + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%d bytes) to client.", w->id, w->data->bytes);
905 + break;
906 +
907 + case WEB_CLIENT_MODE_FILECOPY:
908 + if(w->data->rbytes) {
909 + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %d bytes to client.", w->id, w->data->rbytes);
910 + w->wait_receive = 1;
911 +
912 + /*
913 + // utilize the kernel sendfile() for copying the file to the socket.
914 + // this block of code can be commented, without anything missing.
915 + // when it is commented, the program will copy the data using async I/O.
916 + {
917 + long len = sendfile(w->ofd, w->ifd, NULL, w->data->rbytes);
918 + if(len != w->data->rbytes) error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->data->rbytes, len);
919 + else web_client_reset(w);
920 + }
921 + */
922 + }
923 + else
924 + debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id);
925 + break;
926 +
927 + default:
928 + fatal("%llu: Unknown client mode %d.", w->id, w->mode);
929 + break;
930 + }
931 +}
932 +
933 +long web_client_send_chunk_header(struct web_client *w, int len)
934 +{
935 + debug(D_DEFLATE, "%llu: OPEN CHUNK of %d bytes (hex: %x).", w->id, len, len);
936 + char buf[1024];
937 + sprintf(buf, "%X\r\n", len);
938 + int bytes = send(w->ofd, buf, strlen(buf), MSG_DONTWAIT);
939 +
940 + if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk header %d bytes.", w->id, bytes);
941 + else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk header to the client.", w->id);
942 + else debug(D_DEFLATE, "%llu: Failed to send chunk header to client. Reason: %s", w->id, strerror(errno));
943 +
944 + return bytes;
945 +}
946 +
947 +long web_client_send_chunk_close(struct web_client *w)
948 +{
949 + //debug(D_DEFLATE, "%llu: CLOSE CHUNK.", w->id);
950 +
951 + int bytes = send(w->ofd, "\r\n", 2, MSG_DONTWAIT);
952 +
953 + if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
954 + else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id);
955 + else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client. Reason: %s", w->id, strerror(errno));
956 +
957 + return bytes;
958 +}
959 +
960 +long web_client_send_chunk_finalize(struct web_client *w)
961 +{
962 + //debug(D_DEFLATE, "%llu: FINALIZE CHUNK.", w->id);
963 +
964 + int bytes = send(w->ofd, "\r\n0\r\n\r\n", 7, MSG_DONTWAIT);
965 +
966 + if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
967 + else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id);
968 + else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client. Reason: %s", w->id, strerror(errno));
969 +
970 + return bytes;
971 +}
972 +
973 +long web_client_send_deflate(struct web_client *w)
974 +{
975 + long bytes = 0, t = 0;
976 +
977 + // when using compression,
978 + // w->data->sent is the amount of bytes passed through compression
979 +
980 + // debug(D_DEFLATE, "%llu: TEST w->data->bytes = %d, w->data->sent = %d, w->zhave = %d, w->zsent = %d, w->zstream.avail_in = %d, w->zstream.avail_out = %d, w->zstream.total_in = %d, w->zstream.total_out = %d.", w->id, w->data->bytes, w->data->sent, w->zhave, w->zsent, w->zstream.avail_in, w->zstream.avail_out, w->zstream.total_in, w->zstream.total_out);
981 +
982 + if(w->data->bytes - w->data->sent == 0 && w->zstream.avail_in == 0 && w->zhave == w->zsent && w->zstream.avail_out != 0) {
983 + // there is nothing to send
984 +
985 + debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
986 +
987 + // finalize the chunk
988 + if(w->data->sent != 0)
989 + t += web_client_send_chunk_finalize(w);
990 +
991 + // there can be two cases for this
992 + // A. we have done everything
993 + // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
994 +
995 + if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->data->rbytes && w->data->rbytes > w->data->bytes) {
996 + // we have to wait, more data will come
997 + debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
998 + w->wait_send = 0;
999 + return(0);
1000 + }
1001 +
1002 + if(w->keepalive == 0) {
1003 + debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->data->sent);
1004 + errno = 0;
1005 + return(-1);
1006 + }
1007 +
1008 + // reset the client
1009 + web_client_reset(w);
1010 + debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
1011 + return(0);
1012 + }
1013 +
1014 + if(w->zstream.avail_out == 0 && w->zhave == w->zsent) {
1015 + // compress more input data
1016 +
1017 + // close the previous open chunk
1018 + if(w->data->sent != 0) t += web_client_send_chunk_close(w);
1019 +
1020 + debug(D_DEFLATE, "%llu: Compressing %d bytes starting from %d.", w->id, (w->data->bytes - w->data->sent), w->data->sent);
1021 +
1022 + // give the compressor all the data not passed through the compressor yet
1023 + if(w->data->bytes > w->data->sent) {
1024 + w->zstream.next_in = (Bytef *)&w->data->buffer[w->data->sent];
1025 + w->zstream.avail_in = (w->data->bytes - w->data->sent);
1026 + }
1027 +
1028 + // reset the compressor output buffer
1029 + w->zstream.next_out = w->zbuffer;
1030 + w->zstream.avail_out = ZLIB_CHUNK;
1031 +
1032 + // ask for FINISH if we have all the input
1033 + int flush = Z_SYNC_FLUSH;
1034 + if(w->mode == WEB_CLIENT_MODE_NORMAL
1035 + || (w->mode == WEB_CLIENT_MODE_FILECOPY && w->data->bytes == w->data->rbytes)) {
1036 + flush = Z_FINISH;
1037 + debug(D_DEFLATE, "%llu: Requesting Z_FINISH.", w->id);
1038 + }
1039 + else {
1040 + debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id);
1041 + }
1042 +
1043 + // compress
1044 + if(deflate(&w->zstream, flush) == Z_STREAM_ERROR) {
1045 + error("%llu: Compression failed. Closing down client.", w->id);
1046 + web_client_reset(w);
1047 + return(-1);
1048 + }
1049 +
1050 + w->zhave = ZLIB_CHUNK - w->zstream.avail_out;
1051 + w->zsent = 0;
1052 +
1053 + // keep track of the bytes passed through the compressor
1054 + w->data->sent = w->data->bytes;
1055 +
1056 + debug(D_DEFLATE, "%llu: Compression produced %d bytes.", w->id, w->zhave);
1057 +
1058 + // open a new chunk
1059 + t += web_client_send_chunk_header(w, w->zhave);
1060 + }
1061 +
1062 + bytes = send(w->ofd, &w->zbuffer[w->zsent], w->zhave - w->zsent, MSG_DONTWAIT);
1063 + if(bytes > 0) {
1064 + w->zsent += bytes;
1065 + if(t > 0) bytes += t;
1066 + debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1067 + }
1068 + else if(bytes == 0) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
1069 + else debug(D_WEB_CLIENT, "%llu: Failed to send data to client. Reason: %s", w->id, strerror(errno));
1070 +
1071 + return(bytes);
1072 +}
1073 +
1074 +long web_client_send(struct web_client *w)
1075 +{
1076 + if(w->zoutput) return web_client_send_deflate(w);
1077 +
1078 + long bytes;
1079 +
1080 + if(w->data->bytes - w->data->sent == 0) {
1081 + // there is nothing to send
1082 +
1083 + debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
1084 +
1085 + // there can be two cases for this
1086 + // A. we have done everything
1087 + // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
1088 +
1089 + if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->data->rbytes && w->data->rbytes > w->data->bytes) {
1090 + // we have to wait, more data will come
1091 + debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
1092 + w->wait_send = 0;
1093 + return(0);
1094 + }
1095 +
1096 + if(w->keepalive == 0) {
1097 + debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->data->sent);
1098 + errno = 0;
1099 + return(-1);
1100 + }
1101 +
1102 + web_client_reset(w);
1103 + debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
1104 + return(0);
1105 + }
1106 +
1107 + bytes = send(w->ofd, &w->data->buffer[w->data->sent], w->data->bytes - w->data->sent, MSG_DONTWAIT);
1108 + if(bytes > 0) {
1109 + w->data->sent += bytes;
1110 + debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1111 + }
1112 + else if(bytes == 0) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
1113 + else debug(D_WEB_CLIENT, "%llu: Failed to send data to client. Reason: %s", w->id, strerror(errno));
1114 +
1115 +
1116 + return(bytes);
1117 +}
1118 +
1119 +long web_client_receive(struct web_client *w)
1120 +{
1121 + // do we have any space for more data?
1122 + web_buffer_increase(w->data, WEB_DATA_LENGTH_INCREASE_STEP);
1123 +
1124 + long left = w->data->size - w->data->bytes;
1125 + long bytes;
1126 +
1127 + if(w->mode == WEB_CLIENT_MODE_FILECOPY)
1128 + bytes = read(w->ifd, &w->data->buffer[w->data->bytes], (left-1));
1129 + else
1130 + bytes = recv(w->ifd, &w->data->buffer[w->data->bytes], left-1, MSG_DONTWAIT);
1131 +
1132 + if(bytes > 0) {
1133 + int old = w->data->bytes;
1134 + w->data->bytes += bytes;
1135 + w->data->buffer[w->data->bytes] = '\0';
1136 +
1137 + debug(D_WEB_CLIENT, "%llu: Received %d bytes.", w->id, bytes);
1138 + debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->data->buffer[old]);
1139 +
1140 + if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1141 + w->wait_send = 1;
1142 + if(w->data->rbytes && w->data->bytes >= w->data->rbytes) w->wait_receive = 0;
1143 + }
1144 + }
1145 + else if(bytes == 0) {
1146 + debug(D_WEB_CLIENT, "%llu: Out of input data.", w->id);
1147 +
1148 + // if we cannot read, it means we have an error on input.
1149 + // if however, we are copying a file from ifd to ofd, we should not return an error.
1150 + // in this case, the error should be generated when the file has been sent to the client.
1151 +
1152 + if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1153 + // we are copying data fron ifd to ofd
1154 + // let it finish copying...
1155 + w->wait_receive = 0;
1156 + debug(D_WEB_CLIENT, "%llu: Disabling input.", w->id);
1157 + }
1158 + else {
1159 + bytes = -1;
1160 + errno = 0;
1161 + }
1162 + }
1163 +
1164 + return(bytes);
1165 +}
1166 +
1167 +
1168 +// --------------------------------------------------------------------------------------
1169 +// the thread of a single client
1170 +
1171 +// 1. waits for input and output, using async I/O
1172 +// 2. it processes HTTP requests
1173 +// 3. it generates HTTP responses
1174 +// 4. it copies data from input to output if mode is FILECOPY
1175 +
1176 +void *web_client_main(void *ptr)
1177 +{
1178 + if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
1179 + error("Cannot set pthread cancel type to DEFERRED.");
1180 +
1181 + if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
1182 + error("Cannot set pthread cancel state to ENABLE.");
1183 +
1184 + struct timeval tv;
1185 + struct web_client *w = ptr;
1186 + int retval;
1187 + fd_set ifds, ofds, efds;
1188 + int fdmax = 0;
1189 +
1190 + for(;;) {
1191 + FD_ZERO (&ifds);
1192 + FD_ZERO (&ofds);
1193 + FD_ZERO (&efds);
1194 +
1195 + FD_SET(w->ifd, &efds);
1196 +
1197 + if(w->ifd != w->ofd)
1198 + FD_SET(w->ofd, &efds);
1199 +
1200 + if (w->wait_receive) {
1201 + FD_SET(w->ifd, &ifds);
1202 + if(w->ifd > fdmax) fdmax = w->ifd;
1203 + }
1204 +
1205 + if (w->wait_send) {
1206 + FD_SET(w->ofd, &ofds);
1207 + if(w->ofd > fdmax) fdmax = w->ofd;
1208 + }
1209 +
1210 + tv.tv_sec = web_client_timeout;
1211 + tv.tv_usec = 0;
1212 +
1213 + debug(D_WEB_CLIENT, "%llu: Waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":"");
1214 + retval = select(fdmax+1, &ifds, &ofds, &efds, &tv);
1215 +
1216 + if(retval == -1) {
1217 + debug(D_WEB_CLIENT_ACCESS, "%llu: LISTENER: select() failed.", w->id);
1218 + continue;
1219 + }
1220 + else if(!retval) {
1221 + // timeout
1222 + web_client_reset(w);
1223 + w->obsolete = 1;
1224 + return NULL;
1225 + }
1226 +
1227 + if(FD_ISSET(w->ifd, &efds)) {
1228 + debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on input socket (%s).", w->id, strerror(errno));
1229 + web_client_reset(w);
1230 + w->obsolete = 1;
1231 + return NULL;
1232 + }
1233 +
1234 + if(FD_ISSET(w->ofd, &efds)) {
1235 + debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on output socket (%s).", w->id, strerror(errno));
1236 + web_client_reset(w);
1237 + w->obsolete = 1;
1238 + return NULL;
1239 + }
1240 +
1241 + if(w->wait_send && FD_ISSET(w->ofd, &ofds)) {
1242 + long bytes;
1243 + if((bytes = web_client_send(w)) < 0) {
1244 + debug(D_WEB_CLIENT, "%llu: Closing client (input: %s).", w->id, strerror(errno));
1245 + web_client_reset(w);
1246 + w->obsolete = 1;
1247 + errno = 0;
1248 + return NULL;
1249 + }
1250 + else {
1251 + global_statistics_lock();
1252 + global_statistics.bytes_sent += bytes;
1253 + global_statistics_unlock();
1254 + }
1255 + }
1256 +
1257 + if(w->wait_receive && FD_ISSET(w->ifd, &ifds)) {
1258 + long bytes;
1259 + if((bytes = web_client_receive(w)) < 0) {
1260 + debug(D_WEB_CLIENT, "%llu: Closing client (output: %s).", w->id, strerror(errno));
1261 + web_client_reset(w);
1262 + w->obsolete = 1;
1263 + errno = 0;
1264 + return NULL;
1265 + }
1266 + else {
1267 + if(w->mode != WEB_CLIENT_MODE_FILECOPY) {
1268 + global_statistics_lock();
1269 + global_statistics.bytes_received += bytes;
1270 + global_statistics_unlock();
1271 + }
1272 + }
1273 +
1274 + if(w->mode == WEB_CLIENT_MODE_NORMAL) web_client_process(w);
1275 + }
1276 + }
1277 + debug(D_WEB_CLIENT, "%llu: done...", w->id);
1278 +
1279 + return NULL;
1280 +}
src/web_client.h
+27
@@ -8,6 +8,9 @@
8
9 #include "web_buffer.h"
10
11 +#define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
12 +extern int web_client_timeout;
13 +
14 #ifndef NETDATA_WEB_CLIENT_H
15 #define NETDATA_WEB_CLIENT_H 1
16
@@ -18,6 +21,26 @@
21 #define ZLIB_CHUNK 16384
22 #define MAX_HTTP_HEADER_SIZE 16384
23
24 +/*
25 +struct name_value {
26 + char *name;
27 + char *value;
28 + unsigned long hash;
29 + struct name_value *next;
30 +};
31 +
32 +struct web_request {
33 + char *protocol;
34 + char *hostname;
35 + char *path;
36 + char *query_string;
37 +
38 + struct name_value *headers;
39 + struct name_value *query_parameters;
40 + struct name_value *post_parameters;
41 +};
42 +*/
43 +
44 struct web_client {
45 unsigned long long id;
46
@@ -26,6 +49,8 @@ struct web_client {
49
50 char last_url[URL_MAX+1];
51
52 + struct web_request *request;
53 +
54 struct timeval tv_in, tv_ready;
55
56 int mode;
@@ -62,4 +87,6 @@ extern struct web_client *web_clients;
87 extern struct web_client *web_client_create(int listener);
88 extern struct web_client *web_client_free(struct web_client *w);
89
90 +extern void *web_client_main(void *ptr);
91 +
92 #endif
src/web_server.c
+1 -1109
@@ -29,10 +29,6 @@ int listen_backlog = LISTEN_BACKLOG;
29 int listen_fd = -1;
30 int listen_port = LISTEN_PORT;
31
32 -#define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60
33 -
34 -int web_client_timeout = DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS;
35 -
32 static void log_allocations(void)
33 {
34 static int mem = 0;
@@ -128,1110 +124,6 @@ int create_listen_socket6(int port, int listen_backlog)
124 }
125
126
131 -int mysendfile(struct web_client *w, char *filename)
132 -{
133 - static char *web_dir = NULL;
134 - if(!web_dir) web_dir = config_get("global", "web files directory", "web");
135 -
136 - debug(D_WEB_CLIENT, "%llu: Looking for file '%s'...", w->id, filename);
137 -
138 - // skip leading slashes
139 - while (*filename == '/') filename++;
140 -
141 - // if the filename contain known paths, skip them
142 - if(strncmp(filename, WEB_PATH_DATA "/", strlen(WEB_PATH_DATA) + 1) == 0) filename = &filename[strlen(WEB_PATH_DATA) + 1];
143 - else if(strncmp(filename, WEB_PATH_DATASOURCE "/", strlen(WEB_PATH_DATASOURCE) + 1) == 0) filename = &filename[strlen(WEB_PATH_DATASOURCE) + 1];
144 - else if(strncmp(filename, WEB_PATH_GRAPH "/", strlen(WEB_PATH_GRAPH) + 1) == 0) filename = &filename[strlen(WEB_PATH_GRAPH) + 1];
145 - else if(strncmp(filename, WEB_PATH_FILE "/", strlen(WEB_PATH_FILE) + 1) == 0) filename = &filename[strlen(WEB_PATH_FILE) + 1];
146 -
147 - // if the filename contains a / or a .., refuse to serve it
148 - if(strchr(filename, '/') != 0 || strstr(filename, "..") != 0) {
149 - debug(D_WEB_CLIENT_ACCESS, "%llu: File '%s' is not acceptable.", w->id, filename);
150 - web_buffer_printf(w->data, "File '%s' cannot be served. Filenames cannot contain / or ..", filename);
151 - return 400;
152 - }
153 -
154 - // access the file
155 - char webfilename[FILENAME_MAX + 1];
156 - snprintf(webfilename, FILENAME_MAX, "%s/%s", web_dir, filename);
157 -
158 - // check if the file exists
159 - struct stat stat;
160 - if(lstat(webfilename, &stat) != 0) {
161 - error("%llu: File '%s' is not found.", w->id, webfilename);
162 - web_buffer_printf(w->data, "File '%s' does not exist, or is not accessible.", filename);
163 - return 404;
164 - }
165 -
166 - // check if the file is owned by us
167 - if(stat.st_uid != getuid() && stat.st_uid != geteuid()) {
168 - error("%llu: File '%s' is owned by user %d (I run as user %d). Access Denied.", w->id, webfilename, stat.st_uid, getuid());
169 - web_buffer_printf(w->data, "Access to file '%s' is not permitted.", filename);
170 - return 403;
171 - }
172 -
173 - // open the file
174 - w->ifd = open(webfilename, O_NONBLOCK, O_RDONLY);
175 - if(w->ifd == -1) {
176 - w->ifd = w->ofd;
177 -
178 - if(errno == EBUSY || errno == EAGAIN) {
179 - error("%llu: File '%s' is busy, sending 307 Moved Temporarily to force retry.", w->id, webfilename);
180 - snprintf(w->response_header, MAX_HTTP_HEADER_SIZE, "Location: /" WEB_PATH_FILE "/%s\r\n", filename);
181 - web_buffer_printf(w->data, "The file '%s' is currently busy. Please try again later.", filename);
182 - return 307;
183 - }
184 - else {
185 - error("%llu: Cannot open file '%s'.", w->id, webfilename);
186 - web_buffer_printf(w->data, "Cannot open file '%s'.", filename);
187 - return 404;
188 - }
189 - }
190 -
191 - // pick a Content-Type for the file
192 - if(strstr(filename, ".html") != NULL) w->data->contenttype = CT_TEXT_HTML;
193 - else if(strstr(filename, ".js") != NULL) w->data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
194 - else if(strstr(filename, ".css") != NULL) w->data->contenttype = CT_TEXT_CSS;
195 - else if(strstr(filename, ".xml") != NULL) w->data->contenttype = CT_TEXT_XML;
196 - else if(strstr(filename, ".xsl") != NULL) w->data->contenttype = CT_TEXT_XSL;
197 - else if(strstr(filename, ".txt") != NULL) w->data->contenttype = CT_TEXT_PLAIN;
198 - else if(strstr(filename, ".svg") != NULL) w->data->contenttype = CT_IMAGE_SVG_XML;
199 - else if(strstr(filename, ".ttf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_TRUETYPE;
200 - else if(strstr(filename, ".otf") != NULL) w->data->contenttype = CT_APPLICATION_X_FONT_OPENTYPE;
201 - else if(strstr(filename, ".woff") != NULL) w->data->contenttype = CT_APPLICATION_FONT_WOFF;
202 - else if(strstr(filename, ".eot") != NULL) w->data->contenttype = CT_APPLICATION_VND_MS_FONTOBJ;
203 - else w->data->contenttype = CT_APPLICATION_OCTET_STREAM;
204 -
205 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending file '%s' (%ld bytes, ifd %d, ofd %d).", w->id, webfilename, stat.st_size, w->ifd, w->ofd);
206 -
207 - w->mode = WEB_CLIENT_MODE_FILECOPY;
208 - w->wait_receive = 1;
209 - w->wait_send = 0;
210 - w->data->bytes = 0;
211 - w->data->buffer[0] = '\0';
212 - w->data->rbytes = stat.st_size;
213 - w->data->date = stat.st_mtim.tv_sec;
214 -
215 - return 200;
216 -}
217 -
218 -void web_client_reset(struct web_client *w)
219 -{
220 - struct timeval tv;
221 - gettimeofday(&tv, NULL);
222 -
223 - long sent = w->zoutput?(long)w->zstream.total_out:((w->mode == WEB_CLIENT_MODE_FILECOPY)?w->data->rbytes:w->data->bytes);
224 - long size = (w->mode == WEB_CLIENT_MODE_FILECOPY)?w->data->rbytes:w->data->bytes;
225 -
226 - if(w->last_url[0]) log_access("%llu: (sent/all = %ld/%ld bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %s: '%s'",
227 - w->id,
228 - sent, size, -((size>0)?((float)(size-sent)/(float)size * 100.0):0.0),
229 - (float)usecdiff(&w->tv_ready, &w->tv_in) / 1000.0,
230 - (float)usecdiff(&tv, &w->tv_ready) / 1000.0,
231 - (float)usecdiff(&tv, &w->tv_in) / 1000.0,
232 - (w->mode == WEB_CLIENT_MODE_FILECOPY)?"filecopy":"data",
233 - w->last_url
234 - );
235 -
236 - debug(D_WEB_CLIENT, "%llu: Reseting client.", w->id);
237 -
238 - if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
239 - debug(D_WEB_CLIENT, "%llu: Closing filecopy input file.", w->id);
240 - close(w->ifd);
241 - w->ifd = w->ofd;
242 - }
243 -
244 - w->last_url[0] = '\0';
245 -
246 - w->data->contenttype = CT_TEXT_PLAIN;
247 - w->mode = WEB_CLIENT_MODE_NORMAL;
248 -
249 - w->data->rbytes = 0;
250 - w->data->bytes = 0;
251 - w->data->sent = 0;
252 -
253 - w->response_header[0] = '\0';
254 - w->data->buffer[0] = '\0';
255 -
256 - w->wait_receive = 1;
257 - w->wait_send = 0;
258 -
259 - // if we had enabled compression, release it
260 - if(w->zinitialized) {
261 - debug(D_DEFLATE, "%llu: Reseting compression.", w->id);
262 - deflateEnd(&w->zstream);
263 - w->zoutput = 0;
264 - w->zsent = 0;
265 - w->zhave = 0;
266 - w->zstream.avail_in = 0;
267 - w->zstream.avail_out = 0;
268 - w->zstream.total_in = 0;
269 - w->zstream.total_out = 0;
270 - w->zinitialized = 0;
271 - }
272 -}
273 -
274 -void web_client_enable_deflate(struct web_client *w) {
275 - if(w->zinitialized == 1) {
276 - error("%llu: Compression has already be initialized for this client.", w->id);
277 - return;
278 - }
279 -
280 - if(w->data->sent) {
281 - error("%llu: Cannot enable compression in the middle of a conversation.", w->id);
282 - return;
283 - }
284 -
285 - w->zstream.zalloc = Z_NULL;
286 - w->zstream.zfree = Z_NULL;
287 - w->zstream.opaque = Z_NULL;
288 -
289 - w->zstream.next_in = (Bytef *)w->data->buffer;
290 - w->zstream.avail_in = 0;
291 - w->zstream.total_in = 0;
292 -
293 - w->zstream.next_out = w->zbuffer;
294 - w->zstream.avail_out = 0;
295 - w->zstream.total_out = 0;
296 -
297 - w->zstream.zalloc = Z_NULL;
298 - w->zstream.zfree = Z_NULL;
299 - w->zstream.opaque = Z_NULL;
300 -
301 -// if(deflateInit(&w->zstream, Z_DEFAULT_COMPRESSION) != Z_OK) {
302 -// error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
303 -// return;
304 -// }
305 -
306 - // Select GZIP compression: windowbits = 15 + 16 = 31
307 - if(deflateInit2(&w->zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
308 - error("%llu: Failed to initialize zlib. Proceeding without compression.", w->id);
309 - return;
310 - }
311 -
312 - w->zsent = 0;
313 - w->zoutput = 1;
314 - w->zinitialized = 1;
315 -
316 - debug(D_DEFLATE, "%llu: Initialized compression.", w->id);
317 -}
318 -
319 -int web_client_data_request(struct web_client *w, char *url, int datasource_type)
320 -{
321 - char *args = strchr(url, '?');
322 - if(args) {
323 - *args='\0';
324 - args = &args[1];
325 - }
326 -
327 - // get the name of the data to show
328 - char *tok = mystrsep(&url, "/");
329 - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
330 -
331 - // do we have such a data set?
332 - RRDSET *st = rrdset_find_byname(tok);
333 - if(!st) st = rrdset_find(tok);
334 - if(!st) {
335 - // we don't have it
336 - // try to send a file with that name
337 - w->data->bytes = 0;
338 - return(mysendfile(w, tok));
339 - }
340 -
341 - // we have it
342 - debug(D_WEB_CLIENT, "%llu: Found RRD data with name '%s'.", w->id, tok);
343 -
344 - // how many entries does the client want?
345 - long lines = rrd_default_history_entries;
346 - long group_count = 1;
347 - time_t after = 0, before = 0;
348 - int group_method = GROUP_AVERAGE;
349 - int nonzero = 0;
350 -
351 - if(url) {
352 - // parse the lines required
353 - tok = mystrsep(&url, "/");
354 - if(tok) lines = atoi(tok);
355 - if(lines < 1) lines = 1;
356 - }
357 - if(url) {
358 - // parse the group count required
359 - tok = mystrsep(&url, "/");
360 - if(tok) group_count = atoi(tok);
361 - if(group_count < 1) group_count = 1;
362 - //if(group_count > save_history / 20) group_count = save_history / 20;
363 - }
364 - if(url) {
365 - // parse the grouping method required
366 - tok = mystrsep(&url, "/");
367 - if(strcmp(tok, "max") == 0) group_method = GROUP_MAX;
368 - else if(strcmp(tok, "average") == 0) group_method = GROUP_AVERAGE;
369 - else if(strcmp(tok, "sum") == 0) group_method = GROUP_SUM;
370 - else debug(D_WEB_CLIENT, "%llu: Unknown group method '%s'", w->id, tok);
371 - }
372 - if(url) {
373 - // parse after time
374 - tok = mystrsep(&url, "/");
375 - if(tok) after = strtoul(tok, NULL, 10);
376 - if(after < 0) after = 0;
377 - }
378 - if(url) {
379 - // parse before time
380 - tok = mystrsep(&url, "/");
381 - if(tok) before = strtoul(tok, NULL, 10);
382 - if(before < 0) before = 0;
383 - }
384 - if(url) {
385 - // parse nonzero
386 - tok = mystrsep(&url, "/");
387 - if(tok && strcmp(tok, "nonzero") == 0) nonzero = 1;
388 - }
389 -
390 - w->data->contenttype = CT_APPLICATION_JSON;
391 - w->data->bytes = 0;
392 -
393 - char *google_version = "0.6";
394 - char *google_reqId = "0";
395 - char *google_sig = "0";
396 - char *google_out = "json";
397 - char *google_responseHandler = "google.visualization.Query.setResponse";
398 - char *google_outFileName = NULL;
399 - unsigned long last_timestamp_in_data = 0;
400 - if(datasource_type == DATASOURCE_GOOGLE_JSON || datasource_type == DATASOURCE_GOOGLE_JSONP) {
401 -
402 - w->data->contenttype = CT_APPLICATION_X_JAVASCRIPT;
403 -
404 - while(args) {
405 - tok = mystrsep(&args, "&");
406 - if(tok) {
407 - char *name = mystrsep(&tok, "=");
408 - if(name && strcmp(name, "tqx") == 0) {
409 - char *key = mystrsep(&tok, ":");
410 - char *value = mystrsep(&tok, ";");
411 - if(key && value && *key && *value) {
412 - if(strcmp(key, "version") == 0)
413 - google_version = value;
414 -
415 - else if(strcmp(key, "reqId") == 0)
416 - google_reqId = value;
417 -
418 - else if(strcmp(key, "sig") == 0)
419 - google_sig = value;
420 -
421 - else if(strcmp(key, "out") == 0)
422 - google_out = value;
423 -
424 - else if(strcmp(key, "responseHandler") == 0)
425 - google_responseHandler = value;
426 -
427 - else if(strcmp(key, "outFileName") == 0)
428 - google_outFileName = value;
429 - }
430 - }
431 - }
432 - }
433 -
434 - debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'",
435 - w->id, google_version, google_reqId, google_sig, google_out, google_responseHandler, google_outFileName
436 - );
437 -
438 - if(datasource_type == DATASOURCE_GOOGLE_JSONP) {
439 - last_timestamp_in_data = strtoul(google_sig, NULL, 0);
440 -
441 - // check the client wants json
442 - if(strcmp(google_out, "json") != 0) {
443 - w->data->bytes = snprintf(w->data->buffer, w->data->size,
444 - "%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.'}]});",
445 - google_responseHandler, google_version, google_reqId, google_out);
446 - return 200;
447 - }
448 - }
449 - }
450 -
451 - if(datasource_type == DATASOURCE_GOOGLE_JSONP) {
452 - w->data->bytes = snprintf(w->data->buffer, w->data->size,
453 - "%s({version:'%s',reqId:'%s',status:'ok',sig:'%lu',table:",
454 - google_responseHandler, google_version, google_reqId, st->last_updated.tv_sec);
455 - }
456 -
457 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending RRD data '%s' (id %s, %d lines, %d group, %d group_method, %lu after, %lu before).", w->id, st->name, st->id, lines, group_count, group_method, after, before);
458 - unsigned long timestamp_in_data = rrd_stats_json(datasource_type, st, w->data, lines, group_count, group_method, after, before, nonzero);
459 -
460 - if(datasource_type == DATASOURCE_GOOGLE_JSONP) {
461 - if(timestamp_in_data > last_timestamp_in_data)
462 - w->data->bytes += snprintf(&w->data->buffer[w->data->bytes], w->data->size - w->data->bytes, "});");
463 -
464 - else {
465 - // the client already has the latest data
466 - w->data->bytes = snprintf(w->data->buffer, w->data->size,
467 - "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});",
468 - google_responseHandler, google_version, google_reqId);
469 - }
470 - }
471 -
472 - return 200;
473 -}
474 -
475 -void web_client_process(struct web_client *w)
476 -{
477 - int code = 500;
478 - int bytes;
479 -
480 - w->wait_receive = 0;
481 -
482 - // check if we have an empty line (end of HTTP header)
483 - if(strstr(w->data->buffer, "\r\n\r\n")) {
484 - global_statistics_lock();
485 - global_statistics.web_requests++;
486 - global_statistics_unlock();
487 -
488 - gettimeofday(&w->tv_in, NULL);
489 - debug(D_WEB_DATA, "%llu: Processing data buffer of %d bytes: '%s'.", w->id, w->data->bytes, w->data->buffer);
490 -
491 - // check if the client requested keep-alive HTTP
492 - if(strcasestr(w->data->buffer, "Connection: keep-alive")) w->keepalive = 1;
493 - else w->keepalive = 0;
494 -
495 - // check if the client accepts deflate
496 - if(strstr(w->data->buffer, "gzip"))
497 - web_client_enable_deflate(w);
498 -
499 - int datasource_type = DATASOURCE_GOOGLE_JSONP;
500 - //if(strstr(w->data->buffer, "X-DataSource-Auth"))
501 - // datasource_type = DATASOURCE_GOOGLE_JSON;
502 -
503 - char *buf = w->data->buffer;
504 - char *tok = strsep(&buf, " \r\n");
505 - char *url = NULL;
506 - char *pointer_to_free = NULL; // keep url_decode() allocated buffer
507 -
508 - if(buf && strcmp(tok, "GET") == 0) {
509 - tok = strsep(&buf, " \r\n");
510 - pointer_to_free = url = url_decode(tok);
511 - debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url);
512 - }
513 - else if (buf && strcmp(tok, "POST") == 0) {
514 - w->keepalive = 0;
515 - tok = strsep(&buf, " \r\n");
516 - pointer_to_free = url = url_decode(tok);
517 -
518 - debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url);
519 - }
520 -
521 - w->last_url[0] = '\0';
522 - if(url) {
523 - strncpy(w->last_url, url, URL_MAX);
524 - w->last_url[URL_MAX] = '\0';
525 -
526 - tok = mystrsep(&url, "/?&");
527 -
528 - debug(D_WEB_CLIENT, "%llu: Processing command '%s'.", w->id, tok);
529 -
530 - if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data"
531 - // the client is requesting rrd data
532 - datasource_type = DATASOURCE_JSON;
533 - code = web_client_data_request(w, url, datasource_type);
534 - }
535 - else if(strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource"
536 - // the client is requesting google datasource
537 - code = web_client_data_request(w, url, datasource_type);
538 - }
539 - else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph"
540 - // the client is requesting an rrd graph
541 -
542 - // get the name of the data to show
543 - tok = mystrsep(&url, "/?&");
544 - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
545 -
546 - // do we have such a data set?
547 - RRDSET *st = rrdset_find_byname(tok);
548 - if(!st) st = rrdset_find(tok);
549 - if(!st) {
550 - // we don't have it
551 - // try to send a file with that name
552 - w->data->bytes = 0;
553 - code = mysendfile(w, tok);
554 - }
555 - else {
556 - code = 200;
557 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending %s.json of RRD_STATS...", w->id, st->name);
558 - w->data->contenttype = CT_APPLICATION_JSON;
559 - w->data->bytes = 0;
560 - rrd_stats_graph_json(st, url, w->data);
561 - }
562 - }
563 - else if(strcmp(tok, "debug") == 0) {
564 - w->data->bytes = 0;
565 -
566 - // get the name of the data to show
567 - tok = mystrsep(&url, "/?&");
568 - debug(D_WEB_CLIENT, "%llu: Searching for RRD data with name '%s'.", w->id, tok);
569 -
570 - // do we have such a data set?
571 - RRDSET *st = rrdset_find_byname(tok);
572 - if(!st) st = rrdset_find(tok);
573 - if(!st) {
574 - code = 404;
575 - web_buffer_printf(w->data, "Chart %s is not found.\r\n", tok);
576 - debug(D_WEB_CLIENT_ACCESS, "%llu: %s is not found.", w->id, tok);
577 - }
578 - else {
579 - code = 200;
580 - debug_flags |= D_RRD_STATS;
581 - st->debug = st->debug?0:1;
582 - web_buffer_printf(w->data, "Chart %s has now debug %s.\r\n", tok, st->debug?"enabled":"disabled");
583 - debug(D_WEB_CLIENT_ACCESS, "%llu: debug for %s is %s.", w->id, tok, st->debug?"enabled":"disabled");
584 - }
585 - }
586 - else if(strcmp(tok, "mirror") == 0) {
587 - code = 200;
588 -
589 - debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
590 -
591 - // replace the zero bytes with spaces
592 - int i;
593 - for(i = 0; i < w->data->size; i++)
594 - if(w->data->buffer[i] == '\0') w->data->buffer[i] = ' ';
595 -
596 - // just leave the buffer as is
597 - // it will be copied back to the client
598 - }
599 - else if(strcmp(tok, "list") == 0) {
600 - code = 200;
601 -
602 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending list of RRD_STATS...", w->id);
603 -
604 - w->data->bytes = 0;
605 - RRDSET *st = rrdset_root;
606 -
607 - for ( ; st ; st = st->next )
608 - web_buffer_printf(w->data, "%s\n", st->name);
609 - }
610 - else if(strcmp(tok, "all.json") == 0) {
611 - code = 200;
612 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending JSON list of all monitors of RRD_STATS...", w->id);
613 -
614 - w->data->contenttype = CT_APPLICATION_JSON;
615 - w->data->bytes = 0;
616 - rrd_stats_all_json(w->data);
617 - }
618 - else if(strcmp(tok, "netdata.conf") == 0) {
619 - code = 200;
620 - debug(D_WEB_CLIENT_ACCESS, "%llu: Sending netdata.conf ...", w->id);
621 -
622 - w->data->contenttype = CT_TEXT_PLAIN;
623 - w->data->bytes = 0;
624 - generate_config(w->data, 0);
625 - }
626 - else if(strcmp(tok, WEB_PATH_FILE) == 0) { // "file"
627 - tok = mystrsep(&url, "/?&");
628 - if(tok && *tok) code = mysendfile(w, tok);
629 - else {
630 - code = 400;
631 - w->data->bytes = 0;
632 - strcpy(w->data->buffer, "You have to give a filename to get.\r\n");
633 - w->data->bytes = strlen(w->data->buffer);
634 - }
635 - }
636 - else if(!tok[0]) {
637 - w->data->bytes = 0;
638 - code = mysendfile(w, "index.html");
639 - }
640 - else {
641 - w->data->bytes = 0;
642 - code = mysendfile(w, tok);
643 - }
644 -
645 - }
646 - else {
647 - strcpy(w->last_url, "not a valid response");
648 -
649 - if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf);
650 -
651 - code = 500;
652 - w->data->bytes = 0;
653 - strcpy(w->data->buffer, "I don't understand you...\r\n");
654 - w->data->bytes = strlen(w->data->buffer);
655 - }
656 -
657 - // free url_decode() buffer
658 - if(pointer_to_free) free(pointer_to_free);
659 - }
660 - else if(w->data->bytes > 8192) {
661 - strcpy(w->last_url, "too big request");
662 -
663 - debug(D_WEB_CLIENT_ACCESS, "%llu: Received request is too big.", w->id);
664 -
665 - code = 400;
666 - w->data->bytes = 0;
667 - strcpy(w->data->buffer, "Received request is too big.\r\n");
668 - w->data->bytes = strlen(w->data->buffer);
669 - }
670 - else {
671 - // wait for more data
672 - w->wait_receive = 1;
673 - return;
674 - }
675 -
676 - if(w->data->bytes > w->data->size) {
677 - error("%llu: memory overflow encountered (size is %ld, written %ld).", w->data->size, w->data->bytes);
678 - }
679 -
680 - gettimeofday(&w->tv_ready, NULL);
681 - w->data->date = time(NULL);
682 - w->data->sent = 0;
683 -
684 - // prepare the HTTP response header
685 - debug(D_WEB_CLIENT, "%llu: Generating HTTP header with response %d.", w->id, code);
686 -
687 - char *content_type_string = "";
688 - switch(w->data->contenttype) {
689 - case CT_TEXT_HTML:
690 - content_type_string = "text/html";
691 - break;
692 -
693 - case CT_APPLICATION_XML:
694 - content_type_string = "application/xml";
695 - break;
696 -
697 - case CT_APPLICATION_JSON:
698 - content_type_string = "application/json";
699 - break;
700 -
701 - case CT_APPLICATION_X_JAVASCRIPT:
702 - content_type_string = "application/x-javascript";
703 - break;
704 -
705 - case CT_TEXT_CSS:
706 - content_type_string = "text/css";
707 - break;
708 -
709 - case CT_TEXT_XML:
710 - content_type_string = "text/xml";
711 - break;
712 -
713 - case CT_TEXT_XSL:
714 - content_type_string = "text/xsl";
715 - break;
716 -
717 - case CT_APPLICATION_OCTET_STREAM:
718 - content_type_string = "application/octet-stream";
719 - break;
720 -
721 - case CT_IMAGE_SVG_XML:
722 - content_type_string = "image/svg+xml";
723 - break;
724 -
725 - case CT_APPLICATION_X_FONT_TRUETYPE:
726 - content_type_string = "application/x-font-truetype";
727 - break;
728 -
729 - case CT_APPLICATION_X_FONT_OPENTYPE:
730 - content_type_string = "application/x-font-opentype";
731 - break;
732 -
733 - case CT_APPLICATION_FONT_WOFF:
734 - content_type_string = "application/font-woff";
735 - break;
736 -
737 - case CT_APPLICATION_VND_MS_FONTOBJ:
738 - content_type_string = "application/vnd.ms-fontobject";
739 - break;
740 -
741 - default:
742 - case CT_TEXT_PLAIN:
743 - content_type_string = "text/plain";
744 - break;
745 - }
746 -
747 - char *code_msg = "";
748 - switch(code) {
749 - case 200:
750 - code_msg = "OK";
751 - break;
752 -
753 - case 307:
754 - code_msg = "Temporary Redirect";
755 - break;
756 -
757 - case 400:
758 - code_msg = "Bad Request";
759 - break;
760 -
761 - case 403:
762 - code_msg = "Forbidden";
763 - break;
764 -
765 - case 404:
766 - code_msg = "Not Found";
767 - break;
768 -
769 - default:
770 - code_msg = "Internal Server Error";
771 - break;
772 - }
773 -
774 - char date[100];
775 - struct tm tm = *gmtime(&w->data->date);
776 - strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %Z", &tm);
777 -
778 - char custom_header[MAX_HTTP_HEADER_SIZE + 1] = "";
779 - if(w->response_header[0])
780 - strcpy(custom_header, w->response_header);
781 -
782 - int headerlen = 0;
783 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
784 - "HTTP/1.1 %d %s\r\n"
785 - "Connection: %s\r\n"
786 - "Server: NetData Embedded HTTP Server\r\n"
787 - "Content-Type: %s\r\n"
788 - "Access-Control-Allow-Origin: *\r\n"
789 - "Date: %s\r\n"
790 - , code, code_msg
791 - , w->keepalive?"keep-alive":"close"
792 - , content_type_string
793 - , date
794 - );
795 -
796 - if(custom_header[0])
797 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen, "%s", custom_header);
798 -
799 - if(w->mode == WEB_CLIENT_MODE_NORMAL) {
800 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
801 - "Expires: %s\r\n"
802 - "Cache-Control: no-cache\r\n"
803 - , date
804 - );
805 - }
806 - else {
807 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
808 - "Cache-Control: public\r\n"
809 - );
810 - }
811 -
812 - // if we know the content length, put it
813 - if(!w->zoutput && (w->data->bytes || w->data->rbytes))
814 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
815 - "Content-Length: %ld\r\n"
816 - , w->data->bytes?w->data->bytes:w->data->rbytes
817 - );
818 - else if(!w->zoutput)
819 - w->keepalive = 0; // content-length is required for keep-alive
820 -
821 - if(w->zoutput) {
822 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen,
823 - "Content-Encoding: gzip\r\n"
824 - "Transfer-Encoding: chunked\r\n"
825 - );
826 - }
827 -
828 - headerlen += snprintf(&w->response_header[headerlen], MAX_HTTP_HEADER_SIZE - headerlen, "\r\n");
829 -
830 - // disable TCP_NODELAY, to buffer the header
831 - int flag = 0;
832 - if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to disable TCP_NODELAY on socket.", w->id);
833 -
834 - // sent the HTTP header
835 - debug(D_WEB_DATA, "%llu: Sending response HTTP header of size %d: '%s'", w->id, headerlen, w->response_header);
836 -
837 - bytes = send(w->ofd, w->response_header, headerlen, 0);
838 - if(bytes != headerlen)
839 - error("%llu: HTTP Header failed to be sent (I sent %d bytes but the system sent %d bytes).", w->id, headerlen, bytes);
840 - else {
841 - global_statistics_lock();
842 - global_statistics.bytes_sent += bytes;
843 - global_statistics_unlock();
844 - }
845 -
846 - // enable TCP_NODELAY, to send all data immediately at the next send()
847 - flag = 1;
848 - if(setsockopt(w->ofd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) != 0) error("%llu: failed to enable TCP_NODELAY on socket.", w->id);
849 -
850 - // enable sending immediately if we have data
851 - if(w->data->bytes) w->wait_send = 1;
852 - else w->wait_send = 0;
853 -
854 - // pretty logging
855 - switch(w->mode) {
856 - case WEB_CLIENT_MODE_NORMAL:
857 - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Sending data (%d bytes) to client.", w->id, w->data->bytes);
858 - break;
859 -
860 - case WEB_CLIENT_MODE_FILECOPY:
861 - if(w->data->rbytes) {
862 - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending data file of %d bytes to client.", w->id, w->data->rbytes);
863 - w->wait_receive = 1;
864 -
865 - /*
866 - // utilize the kernel sendfile() for copying the file to the socket.
867 - // this block of code can be commented, without anything missing.
868 - // when it is commented, the program will copy the data using async I/O.
869 - {
870 - long len = sendfile(w->ofd, w->ifd, NULL, w->data->rbytes);
871 - if(len != w->data->rbytes) error("%llu: sendfile() should copy %ld bytes, but copied %ld. Falling back to manual copy.", w->id, w->data->rbytes, len);
872 - else web_client_reset(w);
873 - }
874 - */
875 - }
876 - else
877 - debug(D_WEB_CLIENT, "%llu: Done preparing the response. Will be sending an unknown amount of bytes to client.", w->id);
878 - break;
879 -
880 - default:
881 - fatal("%llu: Unknown client mode %d.", w->id, w->mode);
882 - break;
883 - }
884 -}
885 -
886 -long web_client_send_chunk_header(struct web_client *w, int len)
887 -{
888 - debug(D_DEFLATE, "%llu: OPEN CHUNK of %d bytes (hex: %x).", w->id, len, len);
889 - char buf[1024];
890 - sprintf(buf, "%X\r\n", len);
891 - int bytes = send(w->ofd, buf, strlen(buf), MSG_DONTWAIT);
892 -
893 - if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk header %d bytes.", w->id, bytes);
894 - else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk header to the client.", w->id);
895 - else debug(D_DEFLATE, "%llu: Failed to send chunk header to client. Reason: %s", w->id, strerror(errno));
896 -
897 - return bytes;
898 -}
899 -
900 -long web_client_send_chunk_close(struct web_client *w)
901 -{
902 - //debug(D_DEFLATE, "%llu: CLOSE CHUNK.", w->id);
903 -
904 - int bytes = send(w->ofd, "\r\n", 2, MSG_DONTWAIT);
905 -
906 - if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
907 - else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id);
908 - else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client. Reason: %s", w->id, strerror(errno));
909 -
910 - return bytes;
911 -}
912 -
913 -long web_client_send_chunk_finalize(struct web_client *w)
914 -{
915 - //debug(D_DEFLATE, "%llu: FINALIZE CHUNK.", w->id);
916 -
917 - int bytes = send(w->ofd, "\r\n0\r\n\r\n", 7, MSG_DONTWAIT);
918 -
919 - if(bytes > 0) debug(D_DEFLATE, "%llu: Sent chunk suffix %d bytes.", w->id, bytes);
920 - else if(bytes == 0) debug(D_DEFLATE, "%llu: Did not send chunk suffix to the client.", w->id);
921 - else debug(D_DEFLATE, "%llu: Failed to send chunk suffix to client. Reason: %s", w->id, strerror(errno));
922 -
923 - return bytes;
924 -}
925 -
926 -long web_client_send_deflate(struct web_client *w)
927 -{
928 - long bytes = 0, t = 0;
929 -
930 - // when using compression,
931 - // w->data->sent is the amount of bytes passed through compression
932 -
933 - // debug(D_DEFLATE, "%llu: TEST w->data->bytes = %d, w->data->sent = %d, w->zhave = %d, w->zsent = %d, w->zstream.avail_in = %d, w->zstream.avail_out = %d, w->zstream.total_in = %d, w->zstream.total_out = %d.", w->id, w->data->bytes, w->data->sent, w->zhave, w->zsent, w->zstream.avail_in, w->zstream.avail_out, w->zstream.total_in, w->zstream.total_out);
934 -
935 - if(w->data->bytes - w->data->sent == 0 && w->zstream.avail_in == 0 && w->zhave == w->zsent && w->zstream.avail_out != 0) {
936 - // there is nothing to send
937 -
938 - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
939 -
940 - // finalize the chunk
941 - if(w->data->sent != 0)
942 - t += web_client_send_chunk_finalize(w);
943 -
944 - // there can be two cases for this
945 - // A. we have done everything
946 - // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
947 -
948 - if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->data->rbytes && w->data->rbytes > w->data->bytes) {
949 - // we have to wait, more data will come
950 - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
951 - w->wait_send = 0;
952 - return(0);
953 - }
954 -
955 - if(w->keepalive == 0) {
956 - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->data->sent);
957 - errno = 0;
958 - return(-1);
959 - }
960 -
961 - // reset the client
962 - web_client_reset(w);
963 - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
964 - return(0);
965 - }
966 -
967 - if(w->zstream.avail_out == 0 && w->zhave == w->zsent) {
968 - // compress more input data
969 -
970 - // close the previous open chunk
971 - if(w->data->sent != 0) t += web_client_send_chunk_close(w);
972 -
973 - debug(D_DEFLATE, "%llu: Compressing %d bytes starting from %d.", w->id, (w->data->bytes - w->data->sent), w->data->sent);
974 -
975 - // give the compressor all the data not passed through the compressor yet
976 - if(w->data->bytes > w->data->sent) {
977 - w->zstream.next_in = (Bytef *)&w->data->buffer[w->data->sent];
978 - w->zstream.avail_in = (w->data->bytes - w->data->sent);
979 - }
980 -
981 - // reset the compressor output buffer
982 - w->zstream.next_out = w->zbuffer;
983 - w->zstream.avail_out = ZLIB_CHUNK;
984 -
985 - // ask for FINISH if we have all the input
986 - int flush = Z_SYNC_FLUSH;
987 - if(w->mode == WEB_CLIENT_MODE_NORMAL
988 - || (w->mode == WEB_CLIENT_MODE_FILECOPY && w->data->bytes == w->data->rbytes)) {
989 - flush = Z_FINISH;
990 - debug(D_DEFLATE, "%llu: Requesting Z_FINISH.", w->id);
991 - }
992 - else {
993 - debug(D_DEFLATE, "%llu: Requesting Z_SYNC_FLUSH.", w->id);
994 - }
995 -
996 - // compress
997 - if(deflate(&w->zstream, flush) == Z_STREAM_ERROR) {
998 - error("%llu: Compression failed. Closing down client.", w->id);
999 - web_client_reset(w);
1000 - return(-1);
1001 - }
1002 -
1003 - w->zhave = ZLIB_CHUNK - w->zstream.avail_out;
1004 - w->zsent = 0;
1005 -
1006 - // keep track of the bytes passed through the compressor
1007 - w->data->sent = w->data->bytes;
1008 -
1009 - debug(D_DEFLATE, "%llu: Compression produced %d bytes.", w->id, w->zhave);
1010 -
1011 - // open a new chunk
1012 - t += web_client_send_chunk_header(w, w->zhave);
1013 - }
1014 -
1015 - bytes = send(w->ofd, &w->zbuffer[w->zsent], w->zhave - w->zsent, MSG_DONTWAIT);
1016 - if(bytes > 0) {
1017 - w->zsent += bytes;
1018 - if(t > 0) bytes += t;
1019 - debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1020 - }
1021 - else if(bytes == 0) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
1022 - else debug(D_WEB_CLIENT, "%llu: Failed to send data to client. Reason: %s", w->id, strerror(errno));
1023 -
1024 - return(bytes);
1025 -}
1026 -
1027 -long web_client_send(struct web_client *w)
1028 -{
1029 - if(w->zoutput) return web_client_send_deflate(w);
1030 -
1031 - long bytes;
1032 -
1033 - if(w->data->bytes - w->data->sent == 0) {
1034 - // there is nothing to send
1035 -
1036 - debug(D_WEB_CLIENT, "%llu: Out of output data.", w->id);
1037 -
1038 - // there can be two cases for this
1039 - // A. we have done everything
1040 - // B. we temporarily have nothing to send, waiting for the buffer to be filled by ifd
1041 -
1042 - if(w->mode == WEB_CLIENT_MODE_FILECOPY && w->wait_receive && w->ifd != w->ofd && w->data->rbytes && w->data->rbytes > w->data->bytes) {
1043 - // we have to wait, more data will come
1044 - debug(D_WEB_CLIENT, "%llu: Waiting for more data to become available.", w->id);
1045 - w->wait_send = 0;
1046 - return(0);
1047 - }
1048 -
1049 - if(w->keepalive == 0) {
1050 - debug(D_WEB_CLIENT, "%llu: Closing (keep-alive is not enabled). %ld bytes sent.", w->id, w->data->sent);
1051 - errno = 0;
1052 - return(-1);
1053 - }
1054 -
1055 - web_client_reset(w);
1056 - debug(D_WEB_CLIENT, "%llu: Done sending all data on socket. Waiting for next request on the same socket.", w->id);
1057 - return(0);
1058 - }
1059 -
1060 - bytes = send(w->ofd, &w->data->buffer[w->data->sent], w->data->bytes - w->data->sent, MSG_DONTWAIT);
1061 - if(bytes > 0) {
1062 - w->data->sent += bytes;
1063 - debug(D_WEB_CLIENT, "%llu: Sent %d bytes.", w->id, bytes);
1064 - }
1065 - else if(bytes == 0) debug(D_WEB_CLIENT, "%llu: Did not send any bytes to the client.", w->id);
1066 - else debug(D_WEB_CLIENT, "%llu: Failed to send data to client. Reason: %s", w->id, strerror(errno));
1067 -
1068 -
1069 - return(bytes);
1070 -}
1071 -
1072 -long web_client_receive(struct web_client *w)
1073 -{
1074 - // do we have any space for more data?
1075 - web_buffer_increase(w->data, WEB_DATA_LENGTH_INCREASE_STEP);
1076 -
1077 - long left = w->data->size - w->data->bytes;
1078 - long bytes;
1079 -
1080 - if(w->mode == WEB_CLIENT_MODE_FILECOPY)
1081 - bytes = read(w->ifd, &w->data->buffer[w->data->bytes], (left-1));
1082 - else
1083 - bytes = recv(w->ifd, &w->data->buffer[w->data->bytes], left-1, MSG_DONTWAIT);
1084 -
1085 - if(bytes > 0) {
1086 - int old = w->data->bytes;
1087 - w->data->bytes += bytes;
1088 - w->data->buffer[w->data->bytes] = '\0';
1089 -
1090 - debug(D_WEB_CLIENT, "%llu: Received %d bytes.", w->id, bytes);
1091 - debug(D_WEB_DATA, "%llu: Received data: '%s'.", w->id, &w->data->buffer[old]);
1092 -
1093 - if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1094 - w->wait_send = 1;
1095 - if(w->data->rbytes && w->data->bytes >= w->data->rbytes) w->wait_receive = 0;
1096 - }
1097 - }
1098 - else if(bytes == 0) {
1099 - debug(D_WEB_CLIENT, "%llu: Out of input data.", w->id);
1100 -
1101 - // if we cannot read, it means we have an error on input.
1102 - // if however, we are copying a file from ifd to ofd, we should not return an error.
1103 - // in this case, the error should be generated when the file has been sent to the client.
1104 -
1105 - if(w->mode == WEB_CLIENT_MODE_FILECOPY) {
1106 - // we are copying data fron ifd to ofd
1107 - // let it finish copying...
1108 - w->wait_receive = 0;
1109 - debug(D_WEB_CLIENT, "%llu: Disabling input.", w->id);
1110 - }
1111 - else {
1112 - bytes = -1;
1113 - errno = 0;
1114 - }
1115 - }
1116 -
1117 - return(bytes);
1118 -}
1119 -
1120 -
1121 -// --------------------------------------------------------------------------------------
1122 -// the thread of a single client
1123 -
1124 -// 1. waits for input and output, using async I/O
1125 -// 2. it processes HTTP requests
1126 -// 3. it generates HTTP responses
1127 -// 4. it copies data from input to output if mode is FILECOPY
1128 -
1129 -void *new_client(void *ptr)
1130 -{
1131 - if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
1132 - error("Cannot set pthread cancel type to DEFERRED.");
1133 -
1134 - if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
1135 - error("Cannot set pthread cancel state to ENABLE.");
1136 -
1137 - struct timeval tv;
1138 - struct web_client *w = ptr;
1139 - int retval;
1140 - fd_set ifds, ofds, efds;
1141 - int fdmax = 0;
1142 -
1143 - for(;;) {
1144 - FD_ZERO (&ifds);
1145 - FD_ZERO (&ofds);
1146 - FD_ZERO (&efds);
1147 -
1148 - FD_SET(w->ifd, &efds);
1149 -
1150 - if(w->ifd != w->ofd)
1151 - FD_SET(w->ofd, &efds);
1152 -
1153 - if (w->wait_receive) {
1154 - FD_SET(w->ifd, &ifds);
1155 - if(w->ifd > fdmax) fdmax = w->ifd;
1156 - }
1157 -
1158 - if (w->wait_send) {
1159 - FD_SET(w->ofd, &ofds);
1160 - if(w->ofd > fdmax) fdmax = w->ofd;
1161 - }
1162 -
1163 - tv.tv_sec = web_client_timeout;
1164 - tv.tv_usec = 0;
1165 -
1166 - debug(D_WEB_CLIENT, "%llu: Waiting socket async I/O for %s %s", w->id, w->wait_receive?"INPUT":"", w->wait_send?"OUTPUT":"");
1167 - retval = select(fdmax+1, &ifds, &ofds, &efds, &tv);
1168 -
1169 - if(retval == -1) {
1170 - debug(D_WEB_CLIENT_ACCESS, "%llu: LISTENER: select() failed.", w->id);
1171 - continue;
1172 - }
1173 - else if(!retval) {
1174 - // timeout
1175 - web_client_reset(w);
1176 - w->obsolete = 1;
1177 - return NULL;
1178 - }
1179 -
1180 - if(FD_ISSET(w->ifd, &efds)) {
1181 - debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on input socket (%s).", w->id, strerror(errno));
1182 - web_client_reset(w);
1183 - w->obsolete = 1;
1184 - return NULL;
1185 - }
1186 -
1187 - if(FD_ISSET(w->ofd, &efds)) {
1188 - debug(D_WEB_CLIENT_ACCESS, "%llu: Received error on output socket (%s).", w->id, strerror(errno));
1189 - web_client_reset(w);
1190 - w->obsolete = 1;
1191 - return NULL;
1192 - }
1193 -
1194 - if(w->wait_send && FD_ISSET(w->ofd, &ofds)) {
1195 - long bytes;
1196 - if((bytes = web_client_send(w)) < 0) {
1197 - debug(D_WEB_CLIENT, "%llu: Closing client (input: %s).", w->id, strerror(errno));
1198 - web_client_reset(w);
1199 - w->obsolete = 1;
1200 - errno = 0;
1201 - return NULL;
1202 - }
1203 - else {
1204 - global_statistics_lock();
1205 - global_statistics.bytes_sent += bytes;
1206 - global_statistics_unlock();
1207 - }
1208 - }
1209 -
1210 - if(w->wait_receive && FD_ISSET(w->ifd, &ifds)) {
1211 - long bytes;
1212 - if((bytes = web_client_receive(w)) < 0) {
1213 - debug(D_WEB_CLIENT, "%llu: Closing client (output: %s).", w->id, strerror(errno));
1214 - web_client_reset(w);
1215 - w->obsolete = 1;
1216 - errno = 0;
1217 - return NULL;
1218 - }
1219 - else {
1220 - if(w->mode != WEB_CLIENT_MODE_FILECOPY) {
1221 - global_statistics_lock();
1222 - global_statistics.bytes_received += bytes;
1223 - global_statistics_unlock();
1224 - }
1225 - }
1226 -
1227 - if(w->mode == WEB_CLIENT_MODE_NORMAL) web_client_process(w);
1228 - }
1229 - }
1230 - debug(D_WEB_CLIENT, "%llu: done...", w->id);
1231 -
1232 - return NULL;
1233 -}
1234 -
127 // --------------------------------------------------------------------------------------
128 // the main socket listener
129
@@ -1287,7 +179,7 @@ void *socket_listen_main(void *ptr)
179 if(FD_ISSET(listen_fd, &ifds)) {
180 w = web_client_create(listen_fd);
181
1290 - if(pthread_create(&w->thread, NULL, new_client, w) != 0) {
182 + if(pthread_create(&w->thread, NULL, web_client_main, w) != 0) {
183 error("%llu: failed to create new thread for web client.");
184 w->obsolete = 1;
185 }