馃 The Definitive Gemini Protocol Toolkit
gemini gemini-protocol gemtext parser zero-dependency toolkit ast converter html markdown cli networking
at main 69 lines 1.7 kB view raw
1use { 2 crate::request::Response, 3 tokio::io::{AsyncReadExt, AsyncWriteExt}, 4}; 5 6/// Make a request to a Gemini server 7/// 8/// The `url` **should** be prefixed with a scheme (e.g. "gemini://"). 9/// 10/// # Example 11/// 12/// ```rust 13/// #[tokio::main] 14/// async fn main() { 15/// match germ::request::request(&url::Url::parse("gemini://fuwn.me").unwrap()) 16/// .await 17/// { 18/// Ok(response) => println!("{:?}", response), 19/// Err(_) => {} 20/// } 21/// } 22/// ``` 23/// 24/// # Errors 25/// 26/// - May error if the URL is invalid 27/// - May error if the server is unreachable 28/// - May error if the TLS write fails 29/// - May error if the TLS read fails 30pub async fn request(url: &url::Url) -> anyhow::Result<Response> { 31 let mut tls = tokio_rustls::TlsConnector::from(std::sync::Arc::new( 32 rustls::ClientConfig::builder() 33 .with_safe_defaults() 34 .with_custom_certificate_verifier(std::sync::Arc::new( 35 crate::request::GermVerifier::new(), 36 )) 37 .with_no_client_auth(), 38 )) 39 .connect( 40 rustls::ServerName::try_from( 41 url 42 .domain() 43 .ok_or_else(|| anyhow::anyhow!("Invalid URL: missing domain"))?, 44 )?, 45 tokio::net::TcpStream::connect(format!( 46 "{}:{}", 47 url 48 .domain() 49 .ok_or_else(|| anyhow::anyhow!("Invalid URL: missing domain"))?, 50 url.port().unwrap_or(1965) 51 )) 52 .await?, 53 ) 54 .await?; 55 let cipher_suite = tls.get_mut().1.negotiated_cipher_suite(); 56 57 tls.write_all(format!("{url}\r\n").as_bytes()).await?; 58 59 Ok(Response::new( 60 &{ 61 let mut plain_text = Vec::new(); 62 63 tls.read_to_end(&mut plain_text).await?; 64 65 plain_text 66 }, 67 cipher_suite, 68 )) 69}