faery-ring/src/server.rs

123 lines
3.1 KiB
Rust
Raw Normal View History

2021-11-09 04:09:57 +00:00
use std::borrow::Cow;
use std::io::Cursor;
2022-02-08 20:57:42 +00:00
use std::process::exit;
2021-11-09 04:09:57 +00:00
use ascii::AsciiStr;
2021-11-09 04:09:57 +00:00
use tiny_http::Header;
use tiny_http::Request;
use tiny_http::Response;
use tiny_http::Server;
use tiny_http::StatusCode;
/// Start the server on a given port
2021-11-09 16:35:55 +00:00
///
/// Runs the server in a single thread, bound to 0.0.0.0 on the provided port. Requests are
/// handled first-come, first-serve
pub fn go(domains: &[&AsciiStr], port: u16) {
2022-02-08 20:57:42 +00:00
let server = match Server::http(("0.0.0.0", port)) {
Ok(s) => s,
Err(e) => {
eprintln!("Couldn't create server on 0.0.0.0:{port}\n{e}");
exit(1004);
}
};
2021-11-09 04:09:57 +00:00
println!("Running on 0.0.0.0:{port}");
2021-11-09 18:10:40 +00:00
2021-11-09 04:09:57 +00:00
for req in server.incoming_requests() {
let route = Route::parse_request(&req);
let resp = route.render(domains);
if let Err(e) = req.respond(resp) {
eprintln!("WARN: {}", e);
}
}
}
2021-11-09 16:35:04 +00:00
/// An endpoint and the associated data
///
/// Each route represents a set of logic that should be used to respond to the request,
/// and the necessary information from the request in order to do so
2021-11-09 04:09:57 +00:00
pub enum Route<'a> {
2021-11-09 18:29:57 +00:00
PrevOrNext(&'a str, isize),
2021-11-09 04:09:57 +00:00
NotFound,
}
impl<'a> Route<'a> {
2021-11-09 16:35:55 +00:00
/// Given a request, parse out the appropriate Route for that request
2021-11-09 04:09:57 +00:00
pub fn parse_request(r: &'a Request) -> Self {
2021-11-09 18:29:57 +00:00
let (first_segment, referrer_domain) = r.url()
.trim_matches('/')
.split_once('/')
.unwrap_or((r.url(), "missing_referrer_domain"));
2021-11-09 04:09:57 +00:00
2021-11-09 18:29:57 +00:00
match first_segment {
"next" => {
Route::PrevOrNext(referrer_domain, 1)
2021-11-09 04:09:57 +00:00
},
2021-11-09 18:29:57 +00:00
"prev" => {
Route::PrevOrNext(referrer_domain, -1)
2021-11-09 04:09:57 +00:00
},
_ => Route::NotFound,
}
}
2021-11-09 16:35:55 +00:00
/// Generate a Response for this route
pub fn render<'b>(&self, domains: &[&'b AsciiStr]) -> Response<Cursor<Cow<'b, [u8]>>> {
2021-11-09 04:09:57 +00:00
match self {
2021-11-09 18:29:57 +00:00
Self::PrevOrNext(this_domain, offset) => {
2021-11-09 04:09:57 +00:00
let destination = domains.iter()
.position(|d| d.as_str().starts_with(this_domain))
2021-11-09 04:09:57 +00:00
.map(|i| (i as isize + offset).rem_euclid(domains.len() as isize))
.map(|i| domains[i as usize]); // safe to unwrap: we just did a rem_euclid
2021-11-09 04:09:57 +00:00
if let Some(url) = destination {
let destination = format!(
"https://{}",
url
);
Response::new(
StatusCode(303),
vec![
Header::from_bytes(
b"Location".to_owned(),
destination.as_bytes(),
).unwrap(), // safe to unwrap: both of these are guaranteed ASCII
2021-11-09 16:31:13 +00:00
Header::from_bytes(
b"Cache-Control".to_owned(),
b"max-age=86400,stale-while-revalidate=172800,stale-if-error=3155760000".to_owned(),
).unwrap(),// safe to unwrap: both of these are guaranteed ASCII
2021-11-09 04:09:57 +00:00
],
Cursor::new(Cow::Borrowed(b"")),
Some(0),
None,
)
} else {
let bytes = format!(
"Misconfigured server. Website {} not found in webring.",
this_domain,
);
let blen = bytes.len();
Response::new(
StatusCode(400),
Vec::new(),
Cursor::new(Cow::Owned(bytes.into_bytes())),
Some(blen),
None,
)
}
},
Self::NotFound => {
Response::new(
StatusCode(404),
Vec::new(),
Cursor::new(Cow::Borrowed(b"Page not found")),
Some(14),
None,
)
}
}
}
}