Skip to content

Commit

Permalink
Server swapping (#101)
Browse files Browse the repository at this point in the history
* Server re-serving, fix sitemap_hash
  • Loading branch information
Jerboa-app authored May 25, 2024
1 parent 84bba59 commit 7a1192a
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 101 deletions.
7 changes: 5 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl ThrottleConfig
/// - ```server_cache_period_seconds: u16```: internal cache period if content is not static
/// - ```static_content: Option<bool>```: all content is immutably cached at launch
/// - ```ignore_regexes: Option<Vec<String>>```: do not serve content matching any of these patterns
/// - ```generate_sitemap: Option<bool>```: sitemap.xml will be automatically generated (and updated)
#[derive(Clone, Serialize, Deserialize)]
pub struct ContentConfig
{
Expand All @@ -83,7 +84,8 @@ pub struct ContentConfig
pub ignore_regexes: Option<Vec<String>>,
pub browser_cache_period_seconds: u16,
pub server_cache_period_seconds: u16,
pub static_content: Option<bool>
pub static_content: Option<bool>,
pub generate_sitemap: Option<bool>
}

impl ContentConfig
Expand All @@ -98,7 +100,8 @@ impl ContentConfig
ignore_regexes: None,
browser_cache_period_seconds: 3600,
server_cache_period_seconds: 3600,
static_content: Some(false)
static_content: Some(false),
generate_sitemap: Some(true)
}
}
}
Expand Down
104 changes: 77 additions & 27 deletions src/content/sitemap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

use std::{clone, collections::BTreeMap, sync::Arc, time::{Duration, Instant, SystemTime}, vec};
use openssl::{conf::Conf, sha::Sha256};
use std::{collections::BTreeMap, sync::Arc, time::{Duration, Instant, SystemTime}, vec};
use openssl::sha::Sha256;
use tokio::sync::Mutex;

use axum::{response::IntoResponse, routing::get, Router};
Expand All @@ -19,6 +19,7 @@ use super::{get_content, mime_type::Mime, Content};
/// [crate::config::ContentConfig::static_content] is false. If
/// so and the server cache has expired ([crate::config::ContentConfig::server_cache_period_seconds])
/// then content is automatically refreshed when served
#[derive(Clone)]
pub struct ContentTree
{
uri_stem: String,
Expand All @@ -38,6 +39,7 @@ impl ContentTree
let mut router: Router<(), axum::body::Body> = Router::new();
for (mut content, mutex) in self.contents.clone()
{
content.refresh();
router = router.route
(
&content.get_uri(),
Expand Down Expand Up @@ -67,22 +69,24 @@ impl ContentTree
router
}

fn calculate_hash(&self) -> Vec<u8>
fn calculate_hash(&self, with_bodies: bool) -> Vec<u8>
{
let mut sha = Sha256::new();
for (mut content, _) in self.contents.clone()
let mut content: Vec<Content> = self.contents.clone().into_iter().map(|(x, _)| x).collect();
content.sort_by(|a, b| a.get_uri().cmp(&b.get_uri()));
for mut content in content
{
if content.is_stale()
sha.update(content.get_uri().as_bytes());
if with_bodies && content.is_stale()
{
content.refresh();
sha.update(&content.byte_body());
sha.update(content.get_uri().as_bytes());
}
}

for (_, child) in &self.children
{
sha.update(&child.calculate_hash());
sha.update(&child.calculate_hash(with_bodies));
}

sha.finish().to_vec()
Expand Down Expand Up @@ -207,6 +211,7 @@ impl ContentTree
///
/// Convertable to a router, see [ContentTree] for dynamic
/// options
#[derive(Clone)]
pub struct SiteMap
{
contents: ContentTree,
Expand All @@ -222,20 +227,67 @@ impl SiteMap
SiteMap { contents: ContentTree::new("/"), content_path, domain, hash: vec![] }
}

pub fn from_config(config: &Config, insert_tag: bool, silent: bool) -> SiteMap
{
let mut sitemap = SiteMap::new(config.domain.clone(), config.content.path.clone());

match config.content.ignore_regexes.clone()
{
Some(p) =>
{
sitemap.build
(
insert_tag,
silent,
Some(&ContentFilter::new(p))
);
},
None =>
{
sitemap.build
(
insert_tag,
silent,
None
);
}
};

let mut home = Content::new
(
"/",
&config.content.home.clone(),
config.content.server_cache_period_seconds,
config.content.browser_cache_period_seconds,
insert_tag
);
match home.load_from_file()
{
Ok(()) =>
{
sitemap.push(home);
},
Err(e) => {crate::debug(format!("Error serving home page resource {}", e), None);}
}

sitemap
}

pub fn push(&mut self, content: Content)
{
self.contents.push(content.uri.clone(), content);
self.calculate_hash();
}

/// Hash a sitemap by detected uri's
pub fn get_hash(&self) -> Vec<u8>
{
self.hash.clone()
}

fn calculate_hash(&mut self)
{
self.hash = self.contents.calculate_hash();
self.hash = self.contents.calculate_hash(false);
}

/// Searches the content path from [SiteMap::new] for [Content]
Expand Down Expand Up @@ -288,7 +340,6 @@ impl SiteMap
}
contents.sort_by_key(|x|x.get_uri());

let mut no_sitemap = true;
let mut no_robots = true;

tic = Instant::now();
Expand All @@ -302,32 +353,31 @@ impl SiteMap
None
};

for mut content in contents
let generate_sitemap = match config.content.generate_sitemap
{
Some(b) => b,
None => true
};

for content in contents
{
if content.get_uri().contains("sitemap.xml")
{
no_sitemap = false;
}
if content.get_uri().contains("robots.txt")
{
no_robots = false;
}
crate::debug(format!("Attempting to add content {:?}", content.preview(64)), None);
if generate_sitemap && content.get_uri().contains("sitemap.xml")
{
continue
}
crate::debug(format!("Adding content {:?}", content.preview(64)), None);
let path = self.content_path.clone()+"/";
let uri = parse_uri(content.get_uri(), path);

match content.load_from_file()
if short_urls && content.get_content_type().is_html()
{
Ok(()) =>
{
if short_urls && content.get_content_type().is_html()
{
let short_uri = Regex::new(r"\.\S+$").unwrap().replacen(&uri, 1, "");
crate::debug(format!("Adding content as short url: {}", short_uri), None);
self.contents.push(short_uri.to_string(), Content::new(&short_uri, &content.path(), server_cache_period, browser_cache_period, tag));
}
}
Err(e) => {crate::debug(format!("Error adding content {}\n{}", content.get_uri(), e), None);}
let short_uri = Regex::new(r"\.\S+$").unwrap().replacen(&uri, 1, "");
crate::debug(format!("Adding content as short url: {}", short_uri), None);
self.contents.push(short_uri.to_string(), Content::new(&short_uri, &content.path(), server_cache_period, browser_cache_period, tag));
}

self.contents.push(content.uri.clone(), content);
Expand All @@ -347,7 +397,7 @@ impl SiteMap
crate::debug(format!("No robots.txt specified, generating robots.txt"), None);
}

if no_sitemap
if generate_sitemap
{
let path = format!("{}/{}",self.content_path,"sitemap.xml");
write_file_bytes(&path, &self.to_xml());
Expand Down
62 changes: 57 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::time::Duration;

use busser::config::{Config, CONFIG_PATH};
use busser::content::sitemap::SiteMap;
use busser::server::http::ServerHttp;
use busser::server::https::Server;
use busser::{openssl_version, program_version};

use tokio::task::spawn;

#[tokio::main]
Expand Down Expand Up @@ -34,12 +37,61 @@ async fn main() {
true
};

let server = Server::new(0,0,0,0, insert_tag);

let http_server = ServerHttp::new(0,0,0,0);

let _http_redirect = spawn(http_server.serve());

server.serve().await;
if args.iter().any(|x| x == "--static-sitemap")
{
busser::debug(format!("Serving with static sitemap"), None);
serve(insert_tag).await;
}
else
{
busser::debug(format!("Serving with dynamic sitemap"), None);
serve_observed(insert_tag).await;
}
}

/// Serve by observing the site content found at the path [busser::config::ContentConfig]
/// every [busser::config::ContentConfig::server_cache_period_seconds] the sitemap
/// hash (see [busser::content::sitemap::SiteMap::get_hash]) is checked, if it is
/// different the server is re-served.
async fn serve_observed(insert_tag: bool)
{
let sitemap = SiteMap::from_config(&Config::load_or_default(CONFIG_PATH), insert_tag, false);
let mut hash = sitemap.get_hash();

let server = Server::new(0,0,0,0,sitemap);
let mut server_handle = server.get_handle();
let mut thread_handle = spawn(async move {server.serve()}.await);

loop
{
let config = Config::load_or_default(CONFIG_PATH);
let sitemap = SiteMap::from_config(&config, insert_tag, false);
let sitemap_hash = sitemap.get_hash();

if sitemap_hash != hash
{
busser::debug(format!("Sitemap changed, shutting down"), None);
server_handle.shutdown();
thread_handle.abort();

let server = Server::new(0,0,0,0,sitemap);
server_handle = server.get_handle();
thread_handle = spawn(async move {server.serve()}.await);
hash = sitemap_hash;
busser::debug(format!("Re-served"), None);
}
busser::debug(format!("Next sitemap check: {}s", config.content.server_cache_period_seconds), None);
tokio::time::sleep(Duration::from_secs(config.content.server_cache_period_seconds.into())).await;
}
}

/// Serve without checking for sitemap changes
async fn serve(insert_tag: bool)
{
let sitemap = SiteMap::from_config(&Config::load_or_default(CONFIG_PATH), insert_tag, false);
let server = Server::new(0,0,0,0,sitemap);
server.serve().await;
}
Loading

0 comments on commit 7a1192a

Please sign in to comment.