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