♻️ Simple & Efficient Gemini-to-HTTP Proxy fuwn.net
proxy gemini-protocol protocol gemini http rust
at main 100 lines 2.6 kB view raw
1use { 2 crate::{environment::ENVIRONMENT, url::from_path}, 3 tokio::{ 4 io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 5 net::TcpListener, 6 }, 7}; 8 9pub async fn serve() { 10 let address = format!("0.0.0.0:{}", ENVIRONMENT.http09_port); 11 let listener = match TcpListener::bind(&address).await { 12 Ok(listener) => { 13 info!("HTTP/0.9 server listening on {address}"); 14 15 listener 16 } 17 Err(error) => { 18 error!("failed to bind HTTP/0.9 server to {address}: {error}"); 19 20 return; 21 } 22 }; 23 24 loop { 25 let (stream, peer) = match listener.accept().await { 26 Ok(connection) => connection, 27 Err(error) => { 28 warn!("HTTP/0.9 accept error: {error}"); 29 30 continue; 31 } 32 }; 33 34 tokio::spawn(async move { 35 if let Err(error) = handle(stream).await { 36 warn!("HTTP/0.9 error from {peer}: {error}"); 37 } 38 }); 39 } 40} 41 42async fn handle( 43 stream: tokio::net::TcpStream, 44) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 45 let (reader, mut writer) = stream.into_split(); 46 let mut reader = BufReader::new(reader); 47 let mut request_line = String::new(); 48 49 reader.read_line(&mut request_line).await?; 50 51 let path = parse_request(&request_line)?; 52 let mut configuration = crate::response::configuration::Configuration::new(); 53 let url = from_path(&path, false, &mut configuration)?; 54 let mut response = germ::request::request(&url).await?; 55 56 if *response.status() == germ::request::Status::PermanentRedirect 57 || *response.status() == germ::request::Status::TemporaryRedirect 58 { 59 let redirect = if response.meta().starts_with('/') { 60 format!( 61 "gemini://{}{}", 62 url.domain().unwrap_or_default(), 63 response.meta() 64 ) 65 } else { 66 response.meta().to_string() 67 }; 68 69 response = germ::request::request(&url::Url::parse(&redirect)?).await?; 70 } 71 72 if response.meta().starts_with("image/") { 73 if let Some(bytes) = response.content_bytes() { 74 writer.write_all(bytes).await?; 75 } 76 } else if let Some(content) = response.content() { 77 writer.write_all(content.as_bytes()).await?; 78 } 79 80 writer.shutdown().await?; 81 82 Ok(()) 83} 84 85fn parse_request( 86 line: &str, 87) -> Result<String, Box<dyn std::error::Error + Send + Sync>> { 88 let line = line.trim(); 89 90 line.strip_prefix("GET ").map_or_else( 91 || { 92 if line.starts_with('/') { 93 Ok(line.to_string()) 94 } else { 95 Err(format!("invalid HTTP/0.9 request: {line}").into()) 96 } 97 }, 98 |path| Ok(path.split_whitespace().next().unwrap_or("/").to_string()), 99 ) 100}