1mod classify;
19pub mod config;
20mod data;
21pub mod device;
22pub mod geo;
23#[doc(hidden)]
24pub mod persist;
25pub mod record;
26pub mod script;
27#[cfg(feature = "model-postgres")]
28pub mod store;
29pub mod summary;
30
31pub use classify::{classify, Class, Group, Kind};
32pub use record::{enable, enabled};
33pub use script::TrafficScriptLayer;
34
35#[doc(hidden)]
38pub fn observe(request: &crate::request::Request, response: &crate::response::Response, ip: std::net::IpAddr) {
39 if !enabled() {
40 return;
41 }
42 let header = |name: &str| request.header(name).map(|h| h.value.as_ref()).filter(|v: &&str| !v.is_empty());
43 let content_type = response
44 .content_range_list
45 .first()
46 .map(|c| c.content_type.as_str())
47 .or_else(|| response.headers.iter().find(|h| h.name.eq_ignore_ascii_case("content-type")).map(|h| h.value.as_ref()));
48 record::record(&record::Hit {
49 ip,
50 host: header("host").unwrap_or(""),
51 user_agent: header("user-agent"),
52 referer: header("referer"),
53 method: &request.method,
54 path: &request.request_uri,
55 status: response.status_code,
56 content_type,
57 headers: &request.headers,
58 now: record::now(),
59 });
60}
61
62pub fn start() {
66 if enabled() {
67 return;
68 }
69 enable();
70 let _ = std::thread::Builder::new().name("rws-traffic-geo".to_string()).spawn(|| {
72 geo::table();
73 });
74 start_store();
75 let Some(path) = config::persist_path() else { return };
76 persist::load(&path);
77 let _ = std::thread::Builder::new().name("rws-traffic".to_string()).spawn(move || loop {
78 std::thread::sleep(std::time::Duration::from_secs(300));
79 if let Err(e) = persist::save(&path) {
80 eprintln!("traffic: can't save {}: {e}", path.display());
81 }
82 });
83}
84
85fn start_store() {
87 let Some(url) = config::database_url() else { return };
88 #[cfg(feature = "model-postgres")]
89 match store::start(&url) {
90 Ok(()) => println!("traffic: writing page views to PostgreSQL."),
91 Err(e) => eprintln!("traffic: can't use the traffic database: {e}; counting without it"),
92 }
93 #[cfg(not(feature = "model-postgres"))]
94 {
95 let _ = url;
96 eprintln!("traffic: [traffic] database_url_env is set, but this rws is built without the model-postgres feature; counting without it");
97 }
98}
99
100pub fn save() {
102 #[cfg(feature = "model-postgres")]
103 store::stop();
104 if let (true, Some(path)) = (enabled(), config::persist_path()) {
105 if let Err(e) = persist::save(&path) {
106 eprintln!("traffic: can't save {}: {e}", path.display());
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests;