1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
use std::{
ffi::{self, CStr, CString},
sync::Mutex,
};
use rusqlite::{Connection, params};
use time::{Duration, OffsetDateTime};
#[repr(C)]
struct ModuleRequest<'req> {
headers_len: ffi::c_ulong,
headers: &'req [[*const ffi::c_char; 2]],
body_len: ffi::c_ulong,
body: *const u8,
}
#[repr(C)]
struct ModuleResponse {
status: ffi::c_ushort,
headers_len: ffi::c_ulong,
headers: &'static [[*const ffi::c_char; 2]],
body_len: ffi::c_ulong,
body: *const u8,
}
const HEADERS: &'static [[*const ffi::c_char; 2]] =
&[[c"Content-Type".as_ptr(), c"text/html".as_ptr()]];
#[unsafe(no_mangle)]
extern "C" fn cgi_handle(req: *const ModuleRequest) -> *const ModuleResponse {
let mut ret = String::new();
// unwrap is bad here
let reqref = unsafe { req.as_ref() }.unwrap();
let mut stats_path = None;
for idx in 0..reqref.headers_len {
let kvarr = reqref.headers[idx as usize];
let k = unsafe { CStr::from_ptr(kvarr[0]) }.to_string_lossy();
let v = unsafe { CStr::from_ptr(kvarr[1]) }.to_string_lossy();
match k.as_ref() {
"CORGI_STATS_DB" => stats_path = Some(v.into_owned()),
_ => (),
}
}
let db = if let Some(path) = stats_path {
Connection::open(path).unwrap()
} else {
return make_error(500, c"could not open stats database");
};
let now = OffsetDateTime::now_utc();
let fifteen_ago = now - Duration::minutes(15);
let query = "SELECT count(requests.id) AS request_count, agents.agent FROM requests \
INNER JOIN agents ON requests.agent_id = agents.id \
WHERE requests.timestamp > ?1 \
GROUP BY requests.agent_id;";
let mut prepared = db.prepare(query).unwrap();
let mut agents: Vec<(usize, String)> = prepared
.query_map(params![fifteen_ago], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap()
.map(|r| r.unwrap())
.collect();
agents.sort_by(|a, b| a.0.cmp(&b.0).reverse());
ret.push_str("<p>In the last fifteen minutes:<br/><code><pre>");
ret.push_str("total | req/m | agent\n");
for (count, agent) in &agents {
ret.push_str(&format!(
"{count:<5} | {:<5.1} | {agent}\n",
*count as f32 / 15.0
));
}
ret.push_str("</pre></code></p>");
let body = CString::new(ret).unwrap();
let resp = ModuleResponse {
status: 200,
headers_len: 1,
headers: HEADERS,
body_len: body.as_bytes().len() as u64,
body: body.into_raw() as *const u8,
};
let boxed = Box::new(resp);
Box::<ModuleResponse>::into_raw(boxed)
}
fn make_error<S: Into<CString>>(code: u16, msg: S) -> *const ModuleResponse {
let body = msg.into();
let resp = ModuleResponse {
status: code,
headers_len: 1,
headers: HEADERS,
body_len: body.as_bytes().len() as u64,
body: body.into_raw() as *const u8,
};
let boxed = Box::new(resp);
Box::<ModuleResponse>::into_raw(boxed)
}
#[unsafe(no_mangle)]
extern "C" fn cgi_cleanup(req: *const ModuleResponse) {
// from_raw what we need to here so that these get dropped
let boxed = unsafe { Box::from_raw(req as *mut ModuleResponse) };
let body = unsafe { CString::from_raw(boxed.body as *mut i8) };
// Explicitly calling drop here to feel good about myself
drop(body);
drop(boxed);
}
|