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