Viewing:
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 }