Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle requests without file extension #794

Merged
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 }
lovasoa marked this conversation as resolved.
Show resolved Hide resolved
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