diff --git a/Cargo.lock b/Cargo.lock index 5ecbc84b..7c4672c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1472,6 +1472,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "request-tracking" +version = "0.1.0" +dependencies = [ + "rand 0.8.5", + "rwf 0.1.3", + "tokio", +] + [[package]] name = "rest" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 08ba6d0b..f6a825e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,5 @@ members = [ "rwf-cli", "rwf-macros", "rwf-tests", - "examples/django", + "examples/django", "examples/request-tracking", ] diff --git a/examples/request-tracking/Cargo.toml b/examples/request-tracking/Cargo.toml new file mode 100644 index 00000000..2295af83 --- /dev/null +++ b/examples/request-tracking/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "request-tracking" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rwf = { path = "../../rwf" } +tokio = { version = "1", features = ["full"] } +rand = "0.8" diff --git a/examples/request-tracking/migrations/.gitkeep b/examples/request-tracking/migrations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/examples/request-tracking/rwf.toml b/examples/request-tracking/rwf.toml new file mode 100644 index 00000000..a9ef863a --- /dev/null +++ b/examples/request-tracking/rwf.toml @@ -0,0 +1,6 @@ +[general] +track_requests = true +log_queries = true + +[database] +name = "rwf_request_tracking" diff --git a/examples/request-tracking/src/controllers/mod.rs b/examples/request-tracking/src/controllers/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/request-tracking/src/controllers/mod.rs @@ -0,0 +1 @@ + diff --git a/examples/request-tracking/src/main.rs b/examples/request-tracking/src/main.rs new file mode 100644 index 00000000..12411914 --- /dev/null +++ b/examples/request-tracking/src/main.rs @@ -0,0 +1,37 @@ +mod controllers; +mod models; + +use rand::Rng; +use rwf::{http, prelude::*, Server}; + +#[derive(Default)] +struct Index; + +#[async_trait] +impl Controller for Index { + async fn handle(&self, request: &Request) -> Result { + let ok = rand::thread_rng().gen::(); + + if ok { + // This is tracked. + Ok(Response::new().html(" +

All requests are tracked

+

To view requests, connect to the rwf_request_tracking database and run:

+ SELECT * FROM rwf_requests ORDER BY id + ")) + } else { + // This is tracked also. + Err(Error::HttpError(Box::new(http::Error::MissingParameter))) + } + } +} + +#[tokio::main] +async fn main() -> Result<(), http::Error> { + Logger::init(); + Migrations::migrate().await?; + + Server::new(vec![route!("/" => Index)]) + .launch("0.0.0.0:8000") + .await +} diff --git a/examples/request-tracking/src/models/mod.rs b/examples/request-tracking/src/models/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/request-tracking/src/models/mod.rs @@ -0,0 +1 @@ + diff --git a/examples/request-tracking/templates/.gitkeep b/examples/request-tracking/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/rwf-cli/src/setup.rs b/rwf-cli/src/setup.rs index 100f781f..23f3efde 100644 --- a/rwf-cli/src/setup.rs +++ b/rwf-cli/src/setup.rs @@ -1,5 +1,6 @@ use std::path::Path; use tokio::fs::{create_dir_all, read_to_string, File}; +use tokio::process::Command; use crate::logging::created; use rwf::colors::MaybeColorize; @@ -62,4 +63,21 @@ pub async fn setup() { break; } } + + // Add rwf dependencies + Command::new("cargo") + .arg("add") + .arg("tokio@1") + .arg("--features") + .arg("full") + .status() + .await + .unwrap(); + + Command::new("cargo") + .arg("add") + .arg("rwf") + .status() + .await + .unwrap(); } diff --git a/rwf/src/controller/auth.rs b/rwf/src/controller/auth.rs index dcdce848..87c8f0a9 100644 --- a/rwf/src/controller/auth.rs +++ b/rwf/src/controller/auth.rs @@ -146,6 +146,15 @@ impl SessionId { } } +impl std::fmt::Display for SessionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SessionId::Authenticated(id) => write!(f, "{}", id), + SessionId::Guest(id) => write!(f, "{}", id), + } + } +} + impl Default for SessionId { fn default() -> Self { use rand::{distributions::Alphanumeric, thread_rng, Rng}; diff --git a/rwf/src/controller/mod.rs b/rwf/src/controller/mod.rs index d2dc205b..4848f8ee 100644 --- a/rwf/src/controller/mod.rs +++ b/rwf/src/controller/mod.rs @@ -28,7 +28,7 @@ use crate::config::get_config; use tokio::select; use tokio::time::{interval, timeout}; -use tracing::{debug, info}; +use tracing::{debug, error, info}; use serde::{Deserialize, Serialize}; @@ -86,8 +86,6 @@ pub trait Controller: Sync + Send { return auth.auth().denied(&request).await; } - let no_session = request.session().is_none(); - let outcome = self.middleware().handle_request(request).await?; let response = match outcome { (Outcome::Forward(request), executed) => match self.handle(&request).await { @@ -96,7 +94,29 @@ pub trait Controller: Sync + Send { .handle_response(&request, response.from_request(&request)?, executed) .await? } - Err(err) => return Err(err), + Err(err) => { + error!("{}", err); + + let response = match err { + Error::HttpError(err) => match err.code() { + 400 => Response::bad_request(), + 403 => Response::forbidden(), + _ => Response::internal_error(err), + }, + + Error::ViewError(err) => Response::internal_error_pretty( + "Template error", + err.to_string().as_str(), + ), + + err => Response::internal_error(err), + }; + + // Run the middleware chain on the response anyway. + self.middleware() + .handle_response(&request, response, executed) + .await? + } }, (Outcome::Stop(request, response), executed) => { self.middleware() @@ -447,7 +467,7 @@ pub trait WebsocketController: Controller { loop { select! { _ = check.tick() => { - debug!("{} check session \"{:?}\"", "websocket".purple(), session_id); + debug!("{} check session \"{}\"", "websocket".purple(), session_id); let closed = match timeout( config.websocket.ping_timeout.unsigned_abs(), @@ -467,7 +487,9 @@ pub trait WebsocketController: Controller { message = receiver.recv() => { match message { Ok(message) => { - debug!("sending {:?} to {:?}", message, receiver.session_id()); + debug!("{} sending {:?} to session \"{}\"", + "websocket".purple(), + message, receiver.session_id()); message.send(&mut stream).await?; } @@ -485,7 +507,7 @@ pub trait WebsocketController: Controller { let frame = frame?; if frame.is_pong() { - debug!("{} session \"{:?}\" is alive", "websocket".purple(), session_id); + debug!("{} session \"{}\" is alive", "websocket".purple(), session_id); lost_pings -= 1; // Protect against weird clients. diff --git a/rwf/src/http/server.rs b/rwf/src/http/server.rs index 6b82362f..395de90f 100644 --- a/rwf/src/http/server.rs +++ b/rwf/src/http/server.rs @@ -5,7 +5,6 @@ //! //! The server is using Tokio, so it can support millions of concurrent clients. use super::{Error, Handler, Request, Response, Router}; -use crate::controller::Error as ControllerError; use crate::colors::MaybeColorize; @@ -125,22 +124,7 @@ impl Server { Ok(response) => response, Err(err) => { error!("{}", err); - match err { - ControllerError::HttpError(err) => match err.code() { - 400 => Response::bad_request(), - 403 => Response::forbidden(), - _ => Response::internal_error(err), - }, - - ControllerError::ViewError(err) => { - Response::internal_error_pretty( - "Template error", - err.to_string().as_str(), - ) - } - - err => Response::internal_error(err), - } + Response::internal_error(err) } }; diff --git a/rwf/src/model/migrations/mod.rs b/rwf/src/model/migrations/mod.rs index cb0f9b0d..d3121367 100644 --- a/rwf/src/model/migrations/mod.rs +++ b/rwf/src/model/migrations/mod.rs @@ -123,9 +123,18 @@ impl Migrations { while let Some(dir_entry) = dir_entries.next_entry().await? { let metadata = dir_entry.metadata().await?; if metadata.is_file() { - let file = MigrationFile::parse( - dir_entry.file_name().to_str().expect("migration OsString"), - )?; + let file_name = dir_entry + .file_name() + .to_str() + .expect("migration OsString") + .to_string(); + + // Skip hidden files + if file_name.starts_with(".") { + continue; + } + + let file = MigrationFile::parse(&file_name)?; let entry = checks .entry(file.name.clone()) .or_insert_with(Check::default); diff --git a/rwf/src/model/mod.rs b/rwf/src/model/mod.rs index e66d0b2f..180b6b43 100644 --- a/rwf/src/model/mod.rs +++ b/rwf/src/model/mod.rs @@ -524,7 +524,7 @@ impl Query { let result = match result { Ok(result) => result, Err(err) => { - self.log_error(); + self.log_error(&err); return Err(err); } }; @@ -542,7 +542,7 @@ impl Query { match result { Ok(rows) => Ok(rows), Err(err) => { - self.log_error(); + self.log_error(&err); Err(err) } } @@ -659,12 +659,13 @@ impl Query { ); } - fn log_error(&self) { + fn log_error(&self, err: &Error) { error!( - "{} {} {}", + "{} {} {} {}", Self::type_name().green(), self.action().purple(), - self.to_sql() + self.to_sql(), + err, ) } }