use actix_web::{
get,
http::header::{HeaderValue, LOCATION},
web, App, HttpResponse, HttpServer, Responder,
};
use std::collections::HashMap;
#[get("/")]
async fn get_index(app_data: web::Data<AppState>) -> impl Responder {
let mut body = String::from(
r#"<!doctype html>
<head>
<meta charset="utf-8">
<title>jio</title>
<meta name="description" content="jio url shortener">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<table>"#,
);
let mut redirects_vec = app_data
.redirects
.iter()
.collect::<Vec<(&String, &String)>>();
redirects_vec.sort();
for (k, v) in redirects_vec {
body = body + "<tr><td>" + k + "</td><td>" + v + "</td></tr>";
}
body += "</table></body></html>";
HttpResponse::Ok().content_type("text/html").body(body)
}
#[get("/{to}/{rest:.*}")]
async fn redirect_with_details(
app_data: web::Data<AppState>,
path: web::Path<(String, String)>,
) -> impl Responder {
let (to, rest) = path.into_inner();
do_redirect(&app_data.redirects, &to, &rest)
}
#[get("/{to}")]
async fn redirect_plain(app_data: web::Data<AppState>, path: web::Path<String>) -> impl Responder {
let to = path.into_inner();
do_redirect(&app_data.redirects, &to, "")
}
fn do_redirect(redirects: &HashMap<String, String>, to: &str, rest: &str) -> impl Responder {
if let Some(to_href) = redirects.get(to) {
let mut additional_path = String::from("");
if rest != "" {
additional_path = format!("/{}", rest);
}
if let Ok(header_value_to) = HeaderValue::from_str(&(to_href.to_owned() + &additional_path))
{
let mut redirect = HttpResponse::PermanentRedirect().body(());
redirect.headers_mut().insert(LOCATION, header_value_to);
return redirect;
}
}
if let Ok(go_to_index) = HeaderValue::from_str("/") {
let mut redirect = HttpResponse::PermanentRedirect().body(());
redirect.headers_mut().insert(LOCATION, go_to_index);
return redirect;
}
return HttpResponse::NotFound().body(());
}
struct AppState {
redirects: HashMap<String, String>,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let address = std::env::var("JIO_LISTEN").unwrap_or("127.0.0.1".to_string());
let redirects_file_path = std::env::var("JIO_REDIRECTS_FILE").unwrap_or(
std::env::var("HOME").expect("Expected that if $JIO_REDIRECTS_FILE was not set, then $HOME would be available to infer a redirects file") + "/.jio_redirects",
);
let port = std::env::var("JIO_PORT")
.unwrap_or("8080".to_string())
.parse::<u16>()
.unwrap_or(8080);
let mut redirects = HashMap::new();
for line in std::fs::read_to_string(&redirects_file_path)
.expect(&format!(
"Expected {} redirects file to exist and be readable",
redirects_file_path,
))
.lines()
{
if let Some((k, v)) = line.split_once(",") {
redirects.insert(k.into(), v.into());
}
}
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(AppState {
redirects: redirects.clone(),
}))
.service(get_index)
.service(redirect_plain)
.service(redirect_with_details)
})
.bind((address, port))?
.run()
.await
}