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