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
}