1 #include "first.h"
2
3 #include "base.h"
4 #include "fdevent.h"
5 #include "h2.h"
6 #include "http_chunk.h"
7 #include "http_header.h"
8 #include "log.h"
9
10 #include "plugin.h"
11
12 #include <sys/types.h>
13 #include "sys-time.h"
14
15 #include <fcntl.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdio.h>
19
20 typedef struct {
21 const buffer *config_url;
22 const buffer *status_url;
23 const buffer *statistics_url;
24
25 int sort;
26 } plugin_config;
27
28 typedef struct {
29 PLUGIN_DATA;
30 plugin_config defaults;
31 plugin_config conf;
32
33 double traffic_out;
34 double requests;
35
36 double mod_5s_traffic_out[5];
37 double mod_5s_requests[5];
38 size_t mod_5s_ndx;
39
40 double rel_traffic_out;
41 double rel_requests;
42
43 double abs_traffic_out;
44 double abs_requests;
45
46 double bytes_written;
47 } plugin_data;
48
INIT_FUNC(mod_status_init)49 INIT_FUNC(mod_status_init) {
50 return calloc(1, sizeof(plugin_data));
51 }
52
mod_status_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)53 static void mod_status_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
54 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
55 case 0: /* status.status-url */
56 pconf->status_url = cpv->v.b;
57 break;
58 case 1: /* status.config-url */
59 pconf->config_url = cpv->v.b;
60 break;
61 case 2: /* status.statistics-url */
62 pconf->statistics_url = cpv->v.b;
63 break;
64 case 3: /* status.enable-sort */
65 pconf->sort = (int)cpv->v.u;
66 break;
67 default:/* should not happen */
68 return;
69 }
70 }
71
mod_status_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)72 static void mod_status_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
73 do {
74 mod_status_merge_config_cpv(pconf, cpv);
75 } while ((++cpv)->k_id != -1);
76 }
77
mod_status_patch_config(request_st * const r,plugin_data * const p)78 static void mod_status_patch_config(request_st * const r, plugin_data * const p) {
79 p->conf = p->defaults; /* copy small struct instead of memcpy() */
80 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
81 for (int i = 1, used = p->nconfig; i < used; ++i) {
82 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
83 mod_status_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
84 }
85 }
86
SETDEFAULTS_FUNC(mod_status_set_defaults)87 SETDEFAULTS_FUNC(mod_status_set_defaults) {
88 static const config_plugin_keys_t cpk[] = {
89 { CONST_STR_LEN("status.status-url"),
90 T_CONFIG_STRING,
91 T_CONFIG_SCOPE_CONNECTION }
92 ,{ CONST_STR_LEN("status.config-url"),
93 T_CONFIG_STRING,
94 T_CONFIG_SCOPE_CONNECTION }
95 ,{ CONST_STR_LEN("status.statistics-url"),
96 T_CONFIG_STRING,
97 T_CONFIG_SCOPE_CONNECTION }
98 ,{ CONST_STR_LEN("status.enable-sort"),
99 T_CONFIG_BOOL,
100 T_CONFIG_SCOPE_CONNECTION }
101 ,{ NULL, 0,
102 T_CONFIG_UNSET,
103 T_CONFIG_SCOPE_UNSET }
104 };
105
106 plugin_data * const p = p_d;
107 if (!config_plugin_values_init(srv, p, cpk, "mod_status"))
108 return HANDLER_ERROR;
109
110 /* process and validate config directives
111 * (init i to 0 if global context; to 1 to skip empty global context) */
112 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
113 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
114 for (; -1 != cpv->k_id; ++cpv) {
115 switch (cpv->k_id) {
116 case 0: /* status.status-url */
117 case 1: /* status.config-url */
118 case 2: /* status.statistics-url */
119 if (buffer_is_blank(cpv->v.b))
120 cpv->v.b = NULL;
121 break;
122 case 3: /* status.enable-sort */
123 break;
124 default:/* should not happen */
125 break;
126 }
127 }
128 }
129
130 p->defaults.sort = 1;
131
132 /* initialize p->defaults from global config context */
133 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
134 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
135 if (-1 != cpv->k_id)
136 mod_status_merge_config(&p->defaults, cpv);
137 }
138
139 return HANDLER_GO_ON;
140 }
141
142
143
144 static void
mod_status_append_state(buffer * const b,request_state_t state)145 mod_status_append_state (buffer * const b, request_state_t state)
146 {
147 const char *s;
148 size_t n;
149 switch (state) {
150 case CON_STATE_CONNECT:
151 s = "connect"; n = sizeof("connect")-1; break;
152 case CON_STATE_READ:
153 s = "read"; n = sizeof("read")-1; break;
154 case CON_STATE_READ_POST:
155 s = "readpost"; n = sizeof("readpost")-1; break;
156 case CON_STATE_WRITE:
157 s = "write"; n = sizeof("write")-1; break;
158 case CON_STATE_CLOSE:
159 s = "close"; n = sizeof("close")-1; break;
160 case CON_STATE_ERROR:
161 s = "error"; n = sizeof("error")-1; break;
162 case CON_STATE_HANDLE_REQUEST:
163 s = "handle-req"; n = sizeof("handle-req")-1; break;
164 case CON_STATE_REQUEST_START:
165 s = "req-start"; n = sizeof("req-start")-1; break;
166 case CON_STATE_REQUEST_END:
167 s = "req-end"; n = sizeof("req-end")-1; break;
168 case CON_STATE_RESPONSE_START:
169 s = "resp-start"; n = sizeof("resp-start")-1; break;
170 case CON_STATE_RESPONSE_END:
171 s = "resp-end"; n = sizeof("resp-end")-1; break;
172 default:
173 s = "(unknown)"; n = sizeof("(unknown)")-1; break;
174 }
175 buffer_append_string_len(b, s, n);
176 }
177
178
179 static const char *
mod_status_get_short_state(request_state_t state)180 mod_status_get_short_state (request_state_t state)
181 {
182 switch (state) {
183 case CON_STATE_CONNECT: return ".";
184 case CON_STATE_READ: return "r";
185 case CON_STATE_READ_POST: return "R";
186 case CON_STATE_WRITE: return "W";
187 case CON_STATE_CLOSE: return "C";
188 case CON_STATE_ERROR: return "E";
189 case CON_STATE_HANDLE_REQUEST: return "h";
190 case CON_STATE_REQUEST_START: return "q";
191 case CON_STATE_REQUEST_END: return "Q";
192 case CON_STATE_RESPONSE_START: return "s";
193 case CON_STATE_RESPONSE_END: return "S";
194 default: return "x";
195 }
196 }
197
198
mod_status_header_append_sort(buffer * b,plugin_data * p,const char * k,size_t klen)199 static void mod_status_header_append_sort(buffer *b, plugin_data *p, const char* k, size_t klen)
200 {
201 p->conf.sort
202 ? buffer_append_str3(b,
203 CONST_STR_LEN("<th class=\"status\"><a href=\"#\" class=\"sortheader\" onclick=\"resort(this);return false;\">"),
204 k, klen,
205 CONST_STR_LEN("<span class=\"sortarrow\">:</span></a></th>\n"))
206 : buffer_append_str3(b,
207 CONST_STR_LEN("<th class=\"status\">"),
208 k, klen,
209 CONST_STR_LEN("</th>\n"));
210 }
211
mod_status_get_multiplier(buffer * b,double avg,int size)212 static void mod_status_get_multiplier(buffer *b, double avg, int size) {
213 char unit[] = " ";
214
215 if (avg > size) { avg /= size; unit[1] = 'k'; }
216 if (avg > size) { avg /= size; unit[1] = 'M'; }
217 if (avg > size) { avg /= size; unit[1] = 'G'; }
218 if (avg > size) { avg /= size; unit[1] = 'T'; }
219 if (avg > size) { avg /= size; unit[1] = 'P'; }
220 if (avg > size) { avg /= size; unit[1] = 'E'; }
221 if (avg > size) { avg /= size; unit[1] = 'Z'; }
222 if (avg > size) { avg /= size; unit[1] = 'Y'; }
223
224 if (size == 1000) {
225 buffer_append_int(b, (intmax_t)avg);
226 }
227 else { /* (size == 1024) */
228 char buf[32+1];
229 buffer_append_string_len(b, buf, (size_t)
230 snprintf(buf, sizeof(buf), "%.2f", avg));
231 }
232 buffer_append_string_len(b, unit, 2);
233 }
234
mod_status_html_rtable_r(buffer * const b,const request_st * const r,const connection * const con,const unix_time64_t cur_ts)235 static void mod_status_html_rtable_r (buffer * const b, const request_st * const r, const connection * const con, const unix_time64_t cur_ts) {
236 buffer_append_str3(b, CONST_STR_LEN("<tr><td class=\"string\">"),
237 BUF_PTR_LEN(&con->dst_addr_buf),
238 CONST_STR_LEN("</td><td class=\"int\">"));
239
240 if (r->reqbody_length) {
241 buffer_append_int(b, r->reqbody_queue.bytes_in);
242 buffer_append_string_len(b, CONST_STR_LEN("/"));
243 buffer_append_int(b, r->reqbody_length);
244 }
245 else
246 buffer_append_string_len(b, CONST_STR_LEN("0/0"));
247
248 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
249
250 buffer_append_int(b, r->write_queue.bytes_out);
251 buffer_append_string_len(b, CONST_STR_LEN("/"));
252 buffer_append_int(b, r->write_queue.bytes_out + chunkqueue_length(&r->write_queue));
253
254 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
255
256 if (CON_STATE_READ == r->state && !buffer_is_blank(&r->target_orig)) {
257 buffer_append_string_len(b, CONST_STR_LEN("keep-alive"));
258 }
259 else
260 mod_status_append_state(b, r->state);
261
262 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"int\">"));
263
264 buffer_append_int(b, cur_ts - r->start_hp.tv_sec);
265
266 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
267
268 if (buffer_is_blank(r->server_name))
269 buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.authority), ENCODING_HTML);
270 else
271 buffer_append_string_encoded(b, BUF_PTR_LEN(r->server_name), ENCODING_HTML);
272
273 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
274
275 if (!buffer_is_blank(&r->uri.path))
276 buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.path), ENCODING_HTML);
277
278 if (!buffer_is_blank(&r->uri.query)) {
279 buffer_append_string_len(b, CONST_STR_LEN("?"));
280 buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.query), ENCODING_HTML);
281 }
282
283 if (!buffer_is_blank(&r->target_orig)) {
284 buffer_append_string_len(b, CONST_STR_LEN(" ("));
285 buffer_append_string_encoded(b, BUF_PTR_LEN(&r->target_orig), ENCODING_HTML);
286 buffer_append_string_len(b, CONST_STR_LEN(")"));
287 }
288 buffer_append_string_len(b, CONST_STR_LEN("</td><td class=\"string\">"));
289
290 buffer_append_string_encoded(b, BUF_PTR_LEN(&r->physical.path), ENCODING_HTML);
291
292 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
293 }
294
mod_status_html_rtable(request_st * const rq,const server * const srv,const unix_time64_t cur_ts)295 static void mod_status_html_rtable (request_st * const rq, const server * const srv, const unix_time64_t cur_ts) {
296 /* connection table and URLs might be large, so double-buffer to aggregate
297 * before sending to chunkqueue, which might be temporary file
298 * (avoid write() per connection) */
299 buffer * const b = rq->tmp_buf;
300 buffer_clear(b);
301 for (const connection *con = srv->conns; con; con = con->next) {
302 const request_st * const r = &con->request;
303 if (r->http_status <= HTTP_VERSION_1_1) {
304 if (buffer_string_space(b) < 4096) {
305 http_chunk_append_mem(rq, BUF_PTR_LEN(b));
306 buffer_clear(b);
307 }
308 mod_status_html_rtable_r(b, r, con, cur_ts);
309 }
310 else {
311 h2con * const h2c = con->h2;
312 for (uint32_t j = 0, rused = h2c->rused; j < rused; ++j) {
313 if (buffer_string_space(b) < 4096) {
314 http_chunk_append_mem(rq, BUF_PTR_LEN(b));
315 buffer_clear(b);
316 }
317 mod_status_html_rtable_r(b, h2c->r[j], con, cur_ts);
318 }
319 }
320 }
321 http_chunk_append_mem(rq, BUF_PTR_LEN(b));
322 }
323
mod_status_handle_server_status_html(server * srv,request_st * const r,plugin_data * p)324 static handler_t mod_status_handle_server_status_html(server *srv, request_st * const r, plugin_data *p) {
325 buffer * const b = chunkqueue_append_buffer_open(&r->write_queue);
326 buffer_string_prepare_append(b, 8192-1);/*(status page base HTML is ~5.2k)*/
327 double avg;
328 uint32_t j;
329 unix_time64_t ts;
330 const unix_time64_t cur_ts = log_epoch_secs;
331
332 int days, hours, mins, seconds;
333
334 /*(CON_STATE_CLOSE must be last state in enum connection_state_t)*/
335 int cstates[CON_STATE_CLOSE+3];
336 memset(cstates, 0, sizeof(cstates));
337
338 buffer_copy_string_len(b, CONST_STR_LEN(
339 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
340 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
341 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
342 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
343 " <head>\n"
344 " <title>Status</title>\n"
345
346 " <style type=\"text/css\">\n"
347 " table.status { border: black solid thin; }\n"
348 " td { white-space: nowrap; }\n"
349 " td.int { background-color: #f0f0f0; text-align: right }\n"
350 " td.string { background-color: #f0f0f0; text-align: left }\n"
351 " th.status { background-color: black; color: white; font-weight: bold; }\n"
352 " a.sortheader { background-color: black; color: white; font-weight: bold; text-decoration: none; display: block; }\n"
353 " span.sortarrow { color: white; text-decoration: none; }\n"
354 " </style>\n"));
355
356 if (!buffer_is_blank(&r->uri.query) && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("refresh="))) {
357 /* Note: Refresh is an historical, but non-standard HTTP header
358 * References (meta http-equiv="refresh" use is deprecated):
359 * https://www.w3.org/TR/WCAG10-HTML-TECHS/#meta-element
360 * https://www.w3.org/TR/WCAG10-CORE-TECHS/#auto-page-refresh
361 * https://www.w3.org/QA/Tips/reback
362 */
363 const long refresh = strtol(r->uri.query.ptr+sizeof("refresh=")-1, NULL, 10);
364 if (refresh > 0) {
365 buffer_append_string_len(b, CONST_STR_LEN("<meta http-equiv=\"refresh\" content=\""));
366 buffer_append_int(b, refresh < 604800 ? refresh : 604800);
367 buffer_append_string_len(b, CONST_STR_LEN("\">\n"));
368 }
369 }
370
371 if (p->conf.sort) {
372 buffer_append_string_len(b, CONST_STR_LEN(
373 "<script type=\"text/javascript\">\n"
374 "// <!--\n"
375 "var sort_column;\n"
376 "var prev_span = null;\n"
377
378 "function get_inner_text(el) {\n"
379 " if((typeof el == 'string')||(typeof el == 'undefined'))\n"
380 " return el;\n"
381 " if(el.innerText)\n"
382 " return el.innerText;\n"
383 " else {\n"
384 " var str = \"\";\n"
385 " var cs = el.childNodes;\n"
386 " var l = cs.length;\n"
387 " for (i=0;i<l;i++) {\n"
388 " if (cs[i].nodeType==1) str += get_inner_text(cs[i]);\n"
389 " else if (cs[i].nodeType==3) str += cs[i].nodeValue;\n"
390 " }\n"
391 " }\n"
392 " return str;\n"
393 "}\n"
394
395 "function sortfn(a,b) {\n"
396 " var at = get_inner_text(a.cells[sort_column]);\n"
397 " var bt = get_inner_text(b.cells[sort_column]);\n"
398 " if (a.cells[sort_column].className == 'int') {\n"
399 " return parseInt(at)-parseInt(bt);\n"
400 " } else {\n"
401 " aa = at.toLowerCase();\n"
402 " bb = bt.toLowerCase();\n"
403 " if (aa==bb) return 0;\n"
404 " else if (aa<bb) return -1;\n"
405 " else return 1;\n"
406 " }\n"
407 "}\n"
408
409 "function resort(lnk) {\n"
410 " var span = lnk.childNodes[1];\n"
411 " var table = lnk.parentNode.parentNode.parentNode.parentNode;\n"
412 " var rows = new Array();\n"
413 " for (j=1;j<table.rows.length;j++)\n"
414 " rows[j-1] = table.rows[j];\n"
415 " sort_column = lnk.parentNode.cellIndex;\n"
416 " rows.sort(sortfn);\n"
417
418 " if (prev_span != null) prev_span.innerHTML = '';\n"
419 " if (span.getAttribute('sortdir')=='down') {\n"
420 " span.innerHTML = '↑';\n"
421 " span.setAttribute('sortdir','up');\n"
422 " rows.reverse();\n"
423 " } else {\n"
424 " span.innerHTML = '↓';\n"
425 " span.setAttribute('sortdir','down');\n"
426 " }\n"
427 " for (i=0;i<rows.length;i++)\n"
428 " table.tBodies[0].appendChild(rows[i]);\n"
429 " prev_span = span;\n"
430 "}\n"
431 "// -->\n"
432 "</script>\n"));
433 }
434
435 buffer_append_string_len(b, CONST_STR_LEN(
436 " </head>\n"
437 "<body>\n"));
438
439
440
441 /* connection listing */
442 buffer_append_string_len(b,
443 CONST_STR_LEN("<h1>Server-Status"));
444 if (r->conf.server_tag)
445 buffer_append_str3(b, CONST_STR_LEN(
446 " ("),
447 BUF_PTR_LEN(r->conf.server_tag),
448 CONST_STR_LEN(
449 ")"));
450 buffer_append_string_len(b,
451 CONST_STR_LEN("</h1>"
452 "<table summary=\"status\" class=\"status\">"
453 "<tr><td>Hostname</td><td class=\"string\">"));
454 buffer_append_string_encoded(b, BUF_PTR_LEN(&r->uri.authority), ENCODING_HTML);
455 if (!buffer_is_blank(r->server_name) && r->server_name != &r->uri.authority) {
456 buffer_append_string_len(b, CONST_STR_LEN(" ("));
457 buffer_append_string_encoded(b, BUF_PTR_LEN(r->server_name), ENCODING_HTML);
458 buffer_append_string_len(b, CONST_STR_LEN(")"));
459 }
460 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"
461 "<tr><td>Uptime</td><td class=\"string\">"));
462
463 ts = cur_ts - srv->startup_ts;
464
465 days = ts / (60 * 60 * 24);
466 ts %= (60 * 60 * 24);
467
468 hours = ts / (60 * 60);
469 ts %= (60 * 60);
470
471 mins = ts / (60);
472 ts %= (60);
473
474 seconds = ts;
475
476 if (days) {
477 buffer_append_int(b, days);
478 buffer_append_string_len(b, CONST_STR_LEN(" days "));
479 }
480
481 if (hours) {
482 buffer_append_int(b, hours);
483 buffer_append_string_len(b, CONST_STR_LEN(" hours "));
484 }
485
486 if (mins) {
487 buffer_append_int(b, mins);
488 buffer_append_string_len(b, CONST_STR_LEN(" min "));
489 }
490
491 buffer_append_int(b, seconds);
492 buffer_append_string_len(b, CONST_STR_LEN(" s"
493 "</td></tr>\n"
494 "<tr><td>Started at</td><td class=\"string\">"));
495
496 ts = srv->startup_ts;
497
498 struct tm tm;
499 buffer_append_strftime(b, "%F %T", localtime64_r(&ts, &tm));
500 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"
501 "<tr><th colspan=\"2\">absolute (since start)</th></tr>\n"
502 "<tr><td>Requests</td><td class=\"string\">"));
503 avg = p->abs_requests;
504 mod_status_get_multiplier(b, avg, 1000);
505 buffer_append_string_len(b, CONST_STR_LEN("req</td></tr>\n"
506 "<tr><td>Traffic</td><td class=\"string\">"));
507 avg = p->abs_traffic_out;
508 mod_status_get_multiplier(b, avg, 1024);
509 buffer_append_string_len(b, CONST_STR_LEN("byte</td></tr>\n"
510 "<tr><th colspan=\"2\">average (since start)</th></tr>\n"
511 "<tr><td>Requests</td><td class=\"string\">"));
512 avg = p->abs_requests / (cur_ts - srv->startup_ts);
513 mod_status_get_multiplier(b, avg, 1000);
514 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"
515 "<tr><td>Traffic</td><td class=\"string\">"));
516 avg = p->abs_traffic_out / (cur_ts - srv->startup_ts);
517 mod_status_get_multiplier(b, avg, 1024);
518 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"
519 "<tr><th colspan=\"2\">average (5s sliding average)</th></tr>\n"));
520 for (j = 0, avg = 0; j < 5; j++) {
521 avg += p->mod_5s_requests[j];
522 }
523
524 avg /= 5;
525
526 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Requests</td><td class=\"string\">"));
527
528 mod_status_get_multiplier(b, avg, 1000);
529 buffer_append_string_len(b, CONST_STR_LEN("req/s</td></tr>\n"));
530
531 for (j = 0, avg = 0; j < 5; j++) {
532 avg += p->mod_5s_traffic_out[j];
533 }
534
535 avg /= 5;
536
537 buffer_append_string_len(b, CONST_STR_LEN("<tr><td>Traffic</td><td class=\"string\">"));
538
539 mod_status_get_multiplier(b, avg, 1024);
540 buffer_append_string_len(b, CONST_STR_LEN("byte/s</td></tr>\n"
541 "</table>\n"
542 "<hr />\n<pre>\n"
543 "<b>"));
544 buffer_append_int(b, srv->srvconf.max_conns - srv->lim_conns);
545 buffer_append_string_len(b, CONST_STR_LEN(" connections</b>\n"));
546
547 int per_line = 50;
548 for (const connection *c = srv->conns; c; c = c->next) {
549 const request_st * const cr = &c->request;
550 const char *state;
551
552 if ((c->h2 && 0 == c->h2->rused)
553 || (CON_STATE_READ == cr->state && !buffer_is_blank(&cr->target_orig))) {
554 state = "k";
555 ++cstates[CON_STATE_CLOSE+2];
556 } else {
557 state = mod_status_get_short_state(cr->state);
558 ++cstates[(cr->state <= CON_STATE_CLOSE ? cr->state : CON_STATE_CLOSE+1)];
559 }
560
561 buffer_append_string_len(b, state, 1);
562
563 if (0 == --per_line) {
564 per_line = 50;
565 buffer_append_string_len(b, CONST_STR_LEN("\n"));
566 }
567 }
568 buffer_append_string_len(b, CONST_STR_LEN("\n\n<table>\n"
569 "<tr><td style=\"text-align:right\">"));
570 buffer_append_int(b, cstates[CON_STATE_CLOSE+2]);
571 buffer_append_string_len(b, CONST_STR_LEN("<td> k = keep-alive</td></tr>\n"));
572 for (j = 0; j < CON_STATE_CLOSE+2; ++j) {
573 /*(skip "unknown" state if there are none; there should not be any unknown)*/
574 if (0 == cstates[j] && j == CON_STATE_CLOSE+1) continue;
575 buffer_append_string_len(b, CONST_STR_LEN("<tr><td style=\"text-align:right\">"));
576 buffer_append_int(b, cstates[j]);
577 buffer_append_str3(b, CONST_STR_LEN("</td><td> "),
578 mod_status_get_short_state(j), 1,
579 CONST_STR_LEN(" = "));
580 mod_status_append_state(b, j);
581 buffer_append_string_len(b, CONST_STR_LEN("</td></tr>\n"));
582 }
583 buffer_append_string_len(b, CONST_STR_LEN(
584 "</table>\n"
585 "</pre><hr />\n<h2>Connections</h2>\n"
586 "<table summary=\"status\" class=\"status\">\n"
587 "<tr>"));
588 mod_status_header_append_sort(b, p, CONST_STR_LEN("Client IP"));
589 mod_status_header_append_sort(b, p, CONST_STR_LEN("Read"));
590 mod_status_header_append_sort(b, p, CONST_STR_LEN("Written"));
591 mod_status_header_append_sort(b, p, CONST_STR_LEN("State"));
592 mod_status_header_append_sort(b, p, CONST_STR_LEN("Time"));
593 mod_status_header_append_sort(b, p, CONST_STR_LEN("Host"));
594 mod_status_header_append_sort(b, p, CONST_STR_LEN("URI"));
595 mod_status_header_append_sort(b, p, CONST_STR_LEN("File"));
596 buffer_append_string_len(b, CONST_STR_LEN("</tr>\n"));
597
598 chunkqueue_append_buffer_commit(&r->write_queue);
599 /* connection table might be large, so buffer separately */
600
601 mod_status_html_rtable(r, srv, cur_ts);
602
603 http_chunk_append_mem(r, CONST_STR_LEN(
604 "</table>\n"
605 "</body>\n"
606 "</html>\n"
607 ));
608
609 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
610
611 return 0;
612 }
613
614
mod_status_handle_server_status_text(server * srv,request_st * const r,plugin_data * p)615 static handler_t mod_status_handle_server_status_text(server *srv, request_st * const r, plugin_data *p) {
616 buffer *b = chunkqueue_append_buffer_open(&r->write_queue);
617
618 /* output total number of requests */
619 buffer_append_string_len(b, CONST_STR_LEN("Total Accesses: "));
620 buffer_append_int(b, (intmax_t)p->abs_requests);
621
622 buffer_append_string_len(b, CONST_STR_LEN("\nTotal kBytes: "));
623 buffer_append_int(b, (intmax_t)(p->abs_traffic_out / 1024));
624
625 buffer_append_string_len(b, CONST_STR_LEN("\nUptime: "));
626 buffer_append_int(b, log_epoch_secs - srv->startup_ts);
627
628 buffer_append_string_len(b, CONST_STR_LEN("\nBusyServers: "));
629 buffer_append_int(b, srv->srvconf.max_conns - srv->lim_conns);
630
631 buffer_append_string_len(b, CONST_STR_LEN("\nIdleServers: "));
632 buffer_append_int(b, srv->lim_conns); /*(could omit)*/
633
634 buffer_append_string_len(b, CONST_STR_LEN("\nScoreboard: "));
635 for (const connection *c = srv->conns; c; c = c->next) {
636 const request_st * const cr = &c->request;
637 const char *state =
638 ((c->h2 && 0 == c->h2->rused)
639 || (CON_STATE_READ == cr->state && !buffer_is_blank(&cr->target_orig)))
640 ? "k"
641 : mod_status_get_short_state(cr->state);
642 buffer_append_string_len(b, state, 1);
643 }
644 for (uint32_t i = 0; i < srv->lim_conns; ++i) { /*(could omit)*/
645 buffer_append_string_len(b, CONST_STR_LEN("_"));
646 }
647 buffer_append_string_len(b, CONST_STR_LEN("\n"));
648
649 chunkqueue_append_buffer_commit(&r->write_queue);
650
651 /* set text/plain output */
652 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
653
654 return 0;
655 }
656
657
mod_status_handle_server_status_json(server * srv,request_st * const r,plugin_data * p)658 static handler_t mod_status_handle_server_status_json(server *srv, request_st * const r, plugin_data *p) {
659 buffer *b = chunkqueue_append_buffer_open(&r->write_queue);
660 double avg;
661 uint32_t j;
662 unsigned int jsonp = 0;
663
664 if (buffer_clen(&r->uri.query) >= sizeof("jsonp=")-1
665 && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("jsonp="))) {
666 /* not a full parse of query string for multiple parameters,
667 * not URL-decoding param and not XML-encoding (XSS protection),
668 * so simply ensure that json function name isalnum() or '_' */
669 const char *f = r->uri.query.ptr + sizeof("jsonp=")-1;
670 int len = 0;
671 while (light_isalnum(f[len]) || f[len] == '_') ++len;
672 if (0 != len && light_isalpha(f[0]) && f[len] == '\0') {
673 buffer_append_str2(b, f, len, CONST_STR_LEN("("));
674 jsonp = 1;
675 }
676 }
677
678 buffer_append_string_len(b, CONST_STR_LEN("{\n\t\"RequestsTotal\": "));
679 buffer_append_int(b, (intmax_t)p->abs_requests);
680
681 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"TrafficTotal\": "));
682 buffer_append_int(b, (intmax_t)(p->abs_traffic_out / 1024));
683
684 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"Uptime\": "));
685 buffer_append_int(b, log_epoch_secs - srv->startup_ts);
686
687 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"BusyServers\": "));
688 buffer_append_int(b, srv->srvconf.max_conns - srv->lim_conns);
689
690 buffer_append_string_len(b, CONST_STR_LEN(",\n\t\"IdleServers\": "));
691 buffer_append_int(b, srv->lim_conns); /*(could omit)*/
692 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
693
694 for (j = 0, avg = 0; j < 5; j++) {
695 avg += p->mod_5s_requests[j];
696 }
697
698 avg /= 5;
699
700 buffer_append_string_len(b, CONST_STR_LEN("\t\"RequestAverage5s\":"));
701 buffer_append_int(b, avg);
702 buffer_append_string_len(b, CONST_STR_LEN(",\n"));
703
704 for (j = 0, avg = 0; j < 5; j++) {
705 avg += p->mod_5s_traffic_out[j];
706 }
707
708 avg /= 5;
709
710 buffer_append_string_len(b, CONST_STR_LEN("\t\"TrafficAverage5s\":"));
711 buffer_append_int(b, avg / 1024); /* kbps */
712 buffer_append_string_len(b, CONST_STR_LEN("\n}"));
713
714 if (jsonp) buffer_append_string_len(b, CONST_STR_LEN(");"));
715
716 chunkqueue_append_buffer_commit(&r->write_queue);
717
718 /* set text/plain output */
719 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/javascript"));
720
721 return 0;
722 }
723
724
mod_status_handle_server_statistics(request_st * const r)725 static handler_t mod_status_handle_server_statistics(request_st * const r) {
726 buffer *b;
727 size_t i;
728 array *st = &plugin_stats;
729
730 if (0 == st->used) {
731 /* we have nothing to send */
732 r->http_status = 204;
733 r->resp_body_finished = 1;
734
735 return HANDLER_FINISHED;
736 }
737
738 b = chunkqueue_append_buffer_open(&r->write_queue);
739 for (i = 0; i < st->used; i++) {
740 buffer_append_str2(b, BUF_PTR_LEN(&st->sorted[i]->key),
741 CONST_STR_LEN(": "));
742 buffer_append_int(b, ((data_integer *)st->sorted[i])->value);
743 buffer_append_string_len(b, CONST_STR_LEN("\n"));
744 }
745 chunkqueue_append_buffer_commit(&r->write_queue);
746
747 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/plain"));
748
749 r->http_status = 200;
750 r->resp_body_finished = 1;
751
752 return HANDLER_FINISHED;
753 }
754
755
mod_status_handle_server_status(request_st * const r,plugin_data * const p)756 static handler_t mod_status_handle_server_status(request_st * const r, plugin_data * const p) {
757 server * const srv = r->con->srv;
758 if (buffer_is_equal_string(&r->uri.query, CONST_STR_LEN("auto"))) {
759 mod_status_handle_server_status_text(srv, r, p);
760 } else if (buffer_clen(&r->uri.query) >= sizeof("json")-1
761 && 0 == memcmp(r->uri.query.ptr, CONST_STR_LEN("json"))) {
762 mod_status_handle_server_status_json(srv, r, p);
763 } else {
764 mod_status_handle_server_status_html(srv, r, p);
765 }
766
767 r->http_status = 200;
768 r->resp_body_finished = 1;
769
770 return HANDLER_FINISHED;
771 }
772
773
mod_status_row_append(buffer * b,const char * k,size_t klen,const char * v,size_t vlen)774 static void mod_status_row_append(buffer *b, const char *k, size_t klen, const char *v, size_t vlen)
775 {
776 struct const_iovec iov[] = {
777 { CONST_STR_LEN(" <tr>\n"
778 " <td><b>") }
779 ,{ k, klen }
780 ,{ CONST_STR_LEN("</b></td>\n"
781 " <td>") }
782 ,{ v, vlen }
783 ,{ CONST_STR_LEN("</td>\n"
784 " </tr>\n") }
785 };
786 buffer_append_iovec(b, iov, sizeof(iov)/sizeof(*iov));
787 }
788
mod_status_header_append(buffer * b,const char * k,size_t klen)789 static void mod_status_header_append(buffer *b, const char *k, size_t klen)
790 {
791 buffer_append_str3(b,
792 CONST_STR_LEN(" <tr>\n"
793 " <th colspan=\"2\">"),
794 k, klen,
795 CONST_STR_LEN("</th>\n"
796 " </tr>\n"));
797 }
798
mod_status_handle_server_config(request_st * const r)799 static handler_t mod_status_handle_server_config(request_st * const r) {
800 server * const srv = r->con->srv;
801 buffer * const tb = r->tmp_buf;
802 buffer_clear(tb);
803 for (uint32_t i = 0; i < srv->plugins.used; ++i) {
804 const char *name = ((plugin **)srv->plugins.ptr)[i]->name;
805 if (i != 0) {
806 buffer_append_string_len(tb, CONST_STR_LEN("<br />"));
807 }
808 buffer_append_string_len(tb, name, strlen(name));
809 }
810
811 buffer *b = chunkqueue_append_buffer_open(&r->write_queue);
812
813 /*(could expand the following into a single buffer_append_iovec(),
814 * but this routine is not expected to be under high load)*/
815
816 buffer_append_string_len(b, CONST_STR_LEN(
817 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
818 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
819 " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
820 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
821 " <head>\n"
822 " <title>Status</title>\n"
823 " </head>\n"
824 " <body>\n"));
825
826 if (r->conf.server_tag)
827 buffer_append_str3(b, CONST_STR_LEN(
828 " <h1>"),
829 BUF_PTR_LEN(r->conf.server_tag),
830 CONST_STR_LEN(
831 " </h1>\n"));
832
833 buffer_append_string_len(b, CONST_STR_LEN(
834 " <table summary=\"status\" border=\"1\">\n"));
835
836 mod_status_header_append(b, CONST_STR_LEN("Server-Features"));
837 #ifdef HAVE_PCRE
838 mod_status_row_append(b, CONST_STR_LEN("RegEx Conditionals"), CONST_STR_LEN("enabled"));
839 #else
840 mod_status_row_append(b, CONST_STR_LEN("RegEx Conditionals"), CONST_STR_LEN("disabled - pcre missing"));
841 #endif
842 mod_status_header_append(b, CONST_STR_LEN("Network Engine"));
843
844 mod_status_row_append(b, CONST_STR_LEN("fd-Event-Handler"),
845 srv->srvconf.event_handler,
846 strlen(srv->srvconf.event_handler));
847
848 mod_status_header_append(b, CONST_STR_LEN("Config-File-Settings"));
849
850 mod_status_row_append(b, CONST_STR_LEN("Loaded Modules"), BUF_PTR_LEN(tb));
851
852 buffer_append_string_len(b, CONST_STR_LEN(
853 " </table>\n"
854 " </body>\n"
855 "</html>\n"
856 ));
857
858 chunkqueue_append_buffer_commit(&r->write_queue);
859
860 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
861
862 r->http_status = 200;
863 r->resp_body_finished = 1;
864
865 return HANDLER_FINISHED;
866 }
867
mod_status_handler(request_st * const r,void * p_d)868 static handler_t mod_status_handler(request_st * const r, void *p_d) {
869 plugin_data *p = p_d;
870
871 if (NULL != r->handler_module) return HANDLER_GO_ON;
872
873 mod_status_patch_config(r, p);
874
875 if (p->conf.status_url &&
876 buffer_is_equal(p->conf.status_url, &r->uri.path)) {
877 return mod_status_handle_server_status(r, p);
878 } else if (p->conf.config_url &&
879 buffer_is_equal(p->conf.config_url, &r->uri.path)) {
880 return mod_status_handle_server_config(r);
881 } else if (p->conf.statistics_url &&
882 buffer_is_equal(p->conf.statistics_url, &r->uri.path)) {
883 return mod_status_handle_server_statistics(r);
884 }
885
886 return HANDLER_GO_ON;
887 }
888
TRIGGER_FUNC(mod_status_trigger)889 TRIGGER_FUNC(mod_status_trigger) {
890 plugin_data *p = p_d;
891
892 /* check all connections */
893 for (const connection *c = srv->conns; c; c = c->next)
894 p->bytes_written += c->bytes_written_cur_second;
895
896 /* a sliding average */
897 p->mod_5s_traffic_out[p->mod_5s_ndx] = p->bytes_written;
898 p->mod_5s_requests [p->mod_5s_ndx] = p->requests;
899
900 p->mod_5s_ndx = (p->mod_5s_ndx+1) % 5;
901
902 p->abs_traffic_out += p->bytes_written;
903 p->rel_traffic_out += p->bytes_written;
904
905 p->bytes_written = 0;
906
907 /* reset storage - second */
908 p->traffic_out = 0;
909 p->requests = 0;
910
911 return HANDLER_GO_ON;
912 }
913
REQUESTDONE_FUNC(mod_status_account)914 REQUESTDONE_FUNC(mod_status_account) {
915 plugin_data *p = p_d;
916
917 p->requests++;
918 p->rel_requests++;
919 p->abs_requests++;
920
921 p->bytes_written += r->con->bytes_written_cur_second;
922
923 return HANDLER_GO_ON;
924 }
925
926
927 int mod_status_plugin_init(plugin *p);
mod_status_plugin_init(plugin * p)928 int mod_status_plugin_init(plugin *p) {
929 p->version = LIGHTTPD_VERSION_ID;
930 p->name = "status";
931
932 p->init = mod_status_init;
933 p->set_defaults= mod_status_set_defaults;
934
935 p->handle_uri_clean = mod_status_handler;
936 p->handle_trigger = mod_status_trigger;
937 p->handle_request_done = mod_status_account;
938
939 return 0;
940 }
941