♻️ Simple & Efficient Gemini-to-HTTP Proxy fuwn.net
proxy gemini-protocol protocol gemini http rust
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf: Cache environment variables at startup

Fuwn d815570d 3b9b895d

+85 -56
+56
src/environment.rs
··· 1 + use std::sync::LazyLock; 2 + 3 + pub static ENVIRONMENT: LazyLock<Environment> = 4 + LazyLock::new(Environment::from_environment); 5 + 6 + pub struct Environment { 7 + pub root: String, 8 + pub css_external: Option<String>, 9 + pub primary_colour: Option<String>, 10 + pub favicon_external: Option<String>, 11 + pub mathjax: bool, 12 + pub head: Option<String>, 13 + pub header: Option<String>, 14 + pub plain_text_route: Option<String>, 15 + pub condense_links: Vec<String>, 16 + pub condense_links_at_headings: Vec<String>, 17 + pub proxy_by_default: bool, 18 + pub keep_gemini: Option<Vec<String>>, 19 + pub embed_images: Option<String>, 20 + } 21 + 22 + impl Environment { 23 + fn from_environment() -> Self { 24 + Self { 25 + root: std::env::var("ROOT").unwrap_or_else(|_| { 26 + log::warn!( 27 + "could not use ROOT from environment variables, proceeding with \ 28 + default root: gemini://fuwn.me" 29 + ); 30 + "gemini://fuwn.me".to_string() 31 + }), 32 + css_external: std::env::var("CSS_EXTERNAL").ok(), 33 + primary_colour: std::env::var("PRIMARY_COLOUR").ok(), 34 + favicon_external: std::env::var("FAVICON_EXTERNAL").ok(), 35 + mathjax: std::env::var("MATHJAX") 36 + .map(|v| v.to_lowercase() == "true") 37 + .unwrap_or(true), 38 + head: std::env::var("HEAD").ok(), 39 + header: std::env::var("HEADER").ok(), 40 + plain_text_route: std::env::var("PLAIN_TEXT_ROUTE").ok(), 41 + condense_links: std::env::var("CONDENSE_LINKS") 42 + .map(|s| s.split(',').map(String::from).collect()) 43 + .unwrap_or_default(), 44 + condense_links_at_headings: std::env::var("CONDENSE_LINKS_AT_HEADINGS") 45 + .map(|s| s.split(',').map(String::from).collect()) 46 + .unwrap_or_default(), 47 + proxy_by_default: std::env::var("PROXY_BY_DEFAULT") 48 + .map(|v| v.to_lowercase() == "true") 49 + .unwrap_or(true), 50 + keep_gemini: std::env::var("KEEP_GEMINI") 51 + .ok() 52 + .map(|s| s.split(',').map(String::from).collect()), 53 + embed_images: std::env::var("EMBED_IMAGES").ok(), 54 + } 55 + } 56 + }
+13 -30
src/html.rs
··· 1 1 use { 2 - crate::url::matches_pattern, 2 + crate::{environment::ENVIRONMENT, url::matches_pattern}, 3 3 germ::ast::Node, 4 - std::{env::var, fmt::Write}, 4 + std::fmt::Write, 5 5 url::Url, 6 6 }; 7 7 ··· 47 47 let mut title = String::new(); 48 48 let mut previous_link = false; 49 49 let mut previous_link_count = 0; 50 - let condense_links = { 51 - let links = var("CONDENSE_LINKS").map_or_else( 52 - |_| vec![], 53 - |condense_links| { 54 - condense_links 55 - .split(',') 56 - .map(std::string::ToString::to_string) 57 - .collect() 58 - }, 59 - ); 60 - 61 - links.contains(&url.path().to_string()) || links.contains(&"*".to_string()) 62 - }; 63 - let condensible_headings_value = 64 - var("CONDENSE_LINKS_AT_HEADINGS").unwrap_or_default(); 65 - let condensible_headings = if condensible_headings_value.is_empty() { 66 - vec![] 67 - } else { 68 - condensible_headings_value.split(',').collect::<Vec<_>>() 69 - }; 50 + let condense_links = 51 + ENVIRONMENT.condense_links.contains(&url.path().to_string()) 52 + || ENVIRONMENT.condense_links.contains(&"*".to_string()); 53 + let condensible_headings = ENVIRONMENT 54 + .condense_links_at_headings 55 + .iter() 56 + .map(String::as_str) 57 + .collect::<Vec<_>>(); 70 58 let mut in_condense_links_flag_trap = !condensible_headings.is_empty(); 71 59 72 60 for node in ast { ··· 154 142 href = link_from_host_href(url, &href)?; 155 143 } 156 144 157 - if var("PROXY_BY_DEFAULT") 158 - .unwrap_or_else(|_| "true".to_string()) 159 - .to_lowercase() 160 - == "true" 145 + if ENVIRONMENT.proxy_by_default 161 146 && href.contains("gemini://") 162 147 && !surface 163 148 { ··· 190 175 } 191 176 } 192 177 193 - if let Ok(keeps) = var("KEEP_GEMINI") { 194 - let patterns = keeps.split(',').collect::<Vec<_>>(); 195 - 178 + if let Some(patterns) = &ENVIRONMENT.keep_gemini { 196 179 if (href.starts_with('/') || !href.contains("://")) && !surface { 197 180 let temporary_href = link_from_host_href(url, &href)?; 198 181 let should_exclude = patterns ··· 213 196 } 214 197 } 215 198 216 - if let Ok(embed_images) = var("EMBED_IMAGES") { 199 + if let Some(embed_images) = &ENVIRONMENT.embed_images { 217 200 if let Some(extension) = std::path::Path::new(&href).extension() { 218 201 if extension == "png" 219 202 || extension == "jpg"
+1
src/main.rs
··· 10 10 #![recursion_limit = "128"] 11 11 #![allow(clippy::cast_precision_loss)] 12 12 13 + mod environment; 13 14 mod html; 14 15 mod response; 15 16 mod url;
+14 -16
src/response.rs
··· 1 1 pub mod configuration; 2 2 3 3 use { 4 - crate::url::{from_path as url_from_path, matches_pattern}, 4 + crate::{ 5 + environment::ENVIRONMENT, 6 + url::{from_path as url_from_path, matches_pattern}, 7 + }, 5 8 actix_web::{Error, HttpResponse}, 6 - std::{env::var, fmt::Write, time::Instant}, 9 + std::{fmt::Write, time::Instant}, 7 10 }; 8 11 9 12 const CSS: &str = include_str!("../default.css"); ··· 142 145 ); 143 146 } 144 147 145 - if let Ok(css) = var("CSS_EXTERNAL") { 146 - let stylesheets = 147 - css.split(',').filter(|s| !s.is_empty()).collect::<Vec<_>>(); 148 - 149 - for stylesheet in stylesheets { 148 + if let Some(css) = &ENVIRONMENT.css_external { 149 + for stylesheet in css.split(',').filter(|s| !s.is_empty()) { 150 150 let _ = write!( 151 151 &mut html_context, 152 152 "<link rel=\"stylesheet\" type=\"text/css\" href=\"{stylesheet}\">", ··· 158 158 r#"<link rel="stylesheet" href="https://latex.vercel.app/style.css"><style>{CSS}</style>"# 159 159 ); 160 160 161 - if let Ok(primary) = var("PRIMARY_COLOUR") { 161 + if let Some(primary) = &ENVIRONMENT.primary_colour { 162 162 let _ = write!( 163 163 &mut html_context, 164 164 "<style>:root {{ --primary: {primary} }}</style>" ··· 171 171 } 172 172 } 173 173 174 - if let Ok(favicon) = var("FAVICON_EXTERNAL") { 174 + if let Some(favicon) = &ENVIRONMENT.favicon_external { 175 175 let _ = write!( 176 176 &mut html_context, 177 177 "<link rel=\"icon\" type=\"image/x-icon\" href=\"{favicon}\">", 178 178 ); 179 179 } 180 180 181 - if var("MATHJAX").unwrap_or_else(|_| "true".to_string()).to_lowercase() 182 - == "true" 183 - { 181 + if ENVIRONMENT.mathjax { 184 182 html_context.push_str( 185 183 r#"<script type="text/javascript" id="MathJax-script" async 186 184 src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"> ··· 188 186 ); 189 187 } 190 188 191 - if let Ok(head) = var("HEAD") { 192 - html_context.push_str(&head); 189 + if let Some(head) = &ENVIRONMENT.head { 190 + html_context.push_str(head); 193 191 } 194 192 195 193 let _ = write!(&mut html_context, "<title>{gemini_title}</title>"); 196 194 let _ = write!(&mut html_context, "</head><body>"); 197 195 198 196 if !http_request.path().starts_with("/proxy") { 199 - if let Ok(header) = var("HEADER") { 197 + if let Some(header) = &ENVIRONMENT.header { 200 198 let _ = write!( 201 199 &mut html_context, 202 200 "<big><blockquote>{header}</blockquote></big>" ··· 260 258 env!("VERGEN_GIT_SHA").get(0..5).unwrap_or("UNKNOWN"), 261 259 ); 262 260 263 - if let Ok(plain_texts) = var("PLAIN_TEXT_ROUTE") { 261 + if let Some(plain_texts) = &ENVIRONMENT.plain_text_route { 264 262 if plain_texts.split(',').any(|r| { 265 263 matches_pattern(r, http_request.path()) 266 264 || matches_pattern(r, http_request.path().trim_end_matches('/'))
+1 -10
src/url.rs
··· 42 42 } else { 43 43 format!( 44 44 "{}{}{}", 45 - { 46 - std::env::var("ROOT").unwrap_or_else(|_| { 47 - warn!( 48 - "could not use ROOT from environment variables, proceeding with \ 49 - default root: gemini://fuwn.me" 50 - ); 51 - 52 - "gemini://fuwn.me".to_string() 53 - }) 54 - }, 45 + &crate::environment::ENVIRONMENT.root, 55 46 path, 56 47 if fallback { "/" } else { "" } 57 48 )