Skip to content
7 changes: 7 additions & 0 deletions src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};
use crate::webserver::routing::RoutingConfig;

#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
Expand Down Expand Up @@ -266,6 +267,12 @@ impl AppConfig {
}
}

impl RoutingConfig for AppConfig {
fn prefix(&self) -> &str {
&self.site_prefix
}
}

/// The directory where the `sqlpage.json` file is located.
/// Determined by the `SQLPAGE_CONFIGURATION_DIRECTORY` environment variable
fn configuration_directory() -> PathBuf {
Expand Down
8 changes: 8 additions & 0 deletions src/file_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::sync::atomic::{
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::RwLock;
use crate::webserver::routing::FileStore;

/// The maximum time in milliseconds that a file can be cached before its freshness is checked
/// (in production mode)
Expand Down Expand Up @@ -74,6 +75,13 @@ pub struct FileCache<T: AsyncFromStrWithState> {
static_files: HashMap<PathBuf, Cached<T>>,
}

impl<T: AsyncFromStrWithState> FileStore for FileCache<T> {
async fn contains(&self, path: &PathBuf) -> bool {
self.cache.read().await.contains_key(path)
|| self.static_files.contains_key(path)
}
}

impl<T: AsyncFromStrWithState> Default for FileCache<T> {
fn default() -> Self {
Self::new()
Expand Down
7 changes: 7 additions & 0 deletions src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use sqlx::postgres::types::PgTimeTz;
use sqlx::{Postgres, Statement, Type};
use std::io::ErrorKind;
use std::path::{Component, Path, PathBuf};
use crate::webserver::routing::FileStore;

pub(crate) struct FileSystem {
local_root: PathBuf,
Expand Down Expand Up @@ -133,6 +134,12 @@ impl FileSystem {
}
}

impl FileStore for FileSystem {
async fn contains(&self, path: &PathBuf) -> bool {
tokio::fs::try_exists(self.local_root.join(path)).await.unwrap_or(false)
}
}

async fn file_modified_since_local(path: &Path, since: DateTime<Utc>) -> tokio::io::Result<bool> {
tokio::fs::metadata(path)
.await
Expand Down
87 changes: 53 additions & 34 deletions src/webserver/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ use std::pin::Pin;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::mpsc;
use crate::webserver::routing::{calculate_route, AppFileStore};
use crate::webserver::routing::RoutingAction::{NotFound, Execute, CustomNotFound, Redirect, Serve};

#[derive(Clone)]
pub struct RequestContext {
Expand Down Expand Up @@ -423,41 +425,58 @@ async fn serve_fallback(
pub async fn main_handler(
mut service_request: ServiceRequest,
) -> actix_web::Result<ServiceResponse> {
if let Some(redirect) = redirect_missing_prefix(&service_request) {
return Ok(service_request.into_response(redirect));
}

let path = req_path(&service_request);
let sql_file_path = path_to_sql_file(&path);
let maybe_response = if let Some(sql_path) = sql_file_path {
if let Some(redirect) = redirect_missing_trailing_slash(service_request.uri()) {
Ok(redirect)
} else {
log::debug!("Processing SQL request: {:?}", sql_path);
process_sql_request(&mut service_request, sql_path).await
}
} else {
log::debug!("Serving file: {:?}", path);
let path = req_path(&service_request);
let if_modified_since = IfModifiedSince::parse(&service_request).ok();
let app_state: &web::Data<AppState> = service_request.app_data().expect("app_state");
serve_file(&path, app_state, if_modified_since).await
};

// On 404/NOT_FOUND error, fall back to `404.sql` handler if it exists
let response = match maybe_response {
// File could not be served due to a 404 error. Try to find a user provide 404 handler in
// the form of a `404.sql` in the current directory. If there is none, look in the parent
// directeory, and its parent directory, ...
Err(e) if e.as_response_error().status_code() == StatusCode::NOT_FOUND => {
serve_fallback(&mut service_request, e).await?
let app_state: &web::Data<AppState> = service_request.app_data().expect("app_state");
let store = AppFileStore::new(&app_state.sql_file_cache, &app_state.file_system);
let path_and_query = service_request.uri().path_and_query().expect("expected valid path with query from request");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want to crash the app here in any case; would be nice to avoid expect.

return match calculate_route(path_and_query, &store, &app_state.config).await {
NotFound => { serve_fallback(&mut service_request, ErrorWithStatus { status: StatusCode::NOT_FOUND }.into()).await }
Execute(path) => { process_sql_request(&mut service_request, path).await }
CustomNotFound(path) => { process_sql_request(&mut service_request, path).await }
Redirect(uri) => { Ok(HttpResponse::MovedPermanently()
.insert_header((header::LOCATION, uri.to_string()))
.finish()) }
Serve(path) => {
let if_modified_since = IfModifiedSince::parse(&service_request).ok();
let app_state: &web::Data<AppState> = service_request.app_data().expect("app_state");
serve_file(path.as_os_str().to_str().unwrap(), app_state, if_modified_since).await
}

// Either a valid response, or an unrelated error that shall be bubbled up.
e => e?,
};

Ok(service_request.into_response(response))
}.map(|response| service_request.into_response(response));

// if let Some(redirect) = redirect_missing_prefix(&service_request) {
// return Ok(service_request.into_response(redirect));
// }
//
// let path = req_path(&service_request);
// let sql_file_path = path_to_sql_file(&path);
// let maybe_response = if let Some(sql_path) = sql_file_path {
// if let Some(redirect) = redirect_missing_trailing_slash(service_request.uri()) {
// Ok(redirect)
// } else {
// log::debug!("Processing SQL request: {:?}", sql_path);
// process_sql_request(&mut service_request, sql_path).await
// }
// } else {
// log::debug!("Serving file: {:?}", path);
// let path = req_path(&service_request);
// let if_modified_since = IfModifiedSince::parse(&service_request).ok();
// let app_state: &web::Data<AppState> = service_request.app_data().expect("app_state");
// serve_file(&path, app_state, if_modified_since).await
// };
//
// // On 404/NOT_FOUND error, fall back to `404.sql` handler if it exists
// let response = match maybe_response {
// // File could not be served due to a 404 error. Try to find a user provide 404 handler in
// // the form of a `404.sql` in the current directory. If there is none, look in the parent
// // directeory, and its parent directory, ...
// Err(e) if e.as_response_error().status_code() == StatusCode::NOT_FOUND => {
// serve_fallback(&mut service_request, e).await?
// }
//
// // Either a valid response, or an unrelated error that shall be bubbled up.
// e => e?,
// };
//
// Ok(service_request.into_response(response))
}

fn redirect_missing_prefix(service_request: &ServiceRequest) -> Option<HttpResponse> {
Expand Down
1 change: 1 addition & 0 deletions src/webserver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ pub use error_with_status::ErrorWithStatus;
pub use database::make_placeholder;
pub use database::migrations::apply;
pub mod response_writer;
pub mod routing;
mod static_content;
Loading
Loading