maj/site/src/http.rs

124 lines
4.5 KiB
Rust

use crate::templates::{self, statics::StaticFile, Html, RenderRucte, ToHtml};
use maj::server::Handler;
use maj::{gemini, server::Request as GemRequest, StatusCode};
use std::io::Write;
use std::sync::Arc;
use url::Url;
use warp::{filters::path::FullPath, http::Response, path, Filter, Rejection, Reply};
const HOST: &'static str = "cetacean.club"; // XXX(cadey): HACK
const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
async fn route(args: (FullPath, Arc<crate::server::Handler>)) -> Result<impl Reply, Rejection> {
let (path, h) = args;
let u = Url::parse(&format!("gemini://{}{}", HOST, path.as_str())).unwrap();
let req = GemRequest {
url: u.clone(),
addr: "127.0.0.1:8080".parse().unwrap(),
certs: None,
};
let resp = h.clone().handle(req).await.unwrap();
match resp.status {
StatusCode::Success => {
if resp.meta.starts_with("text/gemini") {
let (title, body) = gemtext_to_html(resp.body);
Response::builder().html(|o| templates::page_html(o, title, body))
} else {
Response::builder()
.status(warp::http::StatusCode::INTERNAL_SERVER_ERROR)
.html(|o| {
templates::error_html(o, u.to_string(), "cannot proxy this yet".to_string())
})
}
}
StatusCode::PermanentRedirect => {
let uu = Url::parse(&resp.meta).expect("url parsing to work");
log::info!("uu: {}", uu.to_string());
Response::builder()
.status(warp::http::StatusCode::PERMANENT_REDIRECT)
.header("Location", uu.path())
.html(|o| {
templates::error_html(
o,
u.to_string(),
format!("forwarding you to {}", uu.path()),
)
})
}
_ => Response::builder()
.status(warp::http::StatusCode::INTERNAL_SERVER_ERROR)
.html(|o| templates::error_html(o, u.to_string(), resp.meta)),
}
}
fn gemtext_to_html(inp: Vec<u8>) -> (String, impl ToHtml) {
use gemini::Node::*;
let mut title: String = "Unknown Title".to_string();
let inp = std::str::from_utf8(&inp).unwrap();
let nodes = gemini::parse(inp);
let mut buf: Vec<u8> = Vec::new();
for node in &nodes {
match node {
Heading { level, body } => {
if *level == 1 {
title = body.to_string();
}
write!(buf, "<h{0}>{1}</h{0}>", level, body).unwrap();
}
Text(body) => write!(buf, "{}\n<br />", body).unwrap(),
Link { to, name } => write!(
buf,
r#"<a href="{}">{}</a><br />"#,
to,
name.as_ref().or(Some(&to.to_string())).unwrap()
)
.unwrap(),
Preformatted { alt, body } => write!(
buf,
"<code><pre title = \"{}\">{}</pre></code>",
alt.replace("\"", "\\\"").replace("\\", "\\\\"),
body
).unwrap(),
ListItem(body) => write!(buf, "<li>{}</li>", body).unwrap(),
Quote(body) => write!(buf, "<blockquote>{}</blockquote>", body).unwrap(),
}
}
(title, Html(String::from_utf8(buf).unwrap()))
}
pub fn run(h: Arc<crate::server::Handler>, port: u16) {
smol::run(async {
let h = h.clone();
let handler = warp::path::full()
.map(move |path: FullPath| (path, h.clone()))
.and_then(route);
let statics = path("static").and(path::param()).and_then(static_file);
let site = statics.or(handler).with(warp::log(APPLICATION_NAME));
warp::serve(site).run(([0, 0, 0, 0], port)).await;
});
}
/// Handler for static files.
/// Create a response from the file data with a correct content type
/// and a far expires header (or a 404 if the file does not exist).
async fn static_file(name: String) -> Result<impl Reply, Rejection> {
if let Some(data) = StaticFile::get(&name) {
Ok(Response::builder()
.status(warp::http::StatusCode::OK)
.header("content-type", data.mime.as_ref())
// TODO .header("expires", _far_expires)
.body(data.content))
} else {
println!("Static file {} not found", name);
Err(warp::reject::not_found())
}
}