馃 The Definitive Gemini Protocol Toolkit
gemini gemini-protocol gemtext parser zero-dependency toolkit ast converter html markdown cli networking
at main 50 lines 1.4 kB view raw
1use { 2 crate::request::{GermVerifier, Response}, 3 std::io::{Read, Write}, 4}; 5 6/// Make a request to a Gemini server. The `url` **should** be prefixed with a 7/// scheme (e.g. "gemini://"). 8/// 9/// # Example 10/// 11/// ```rust 12/// match germ::request::blocking::request( 13/// &url::Url::parse("gemini://fuwn.me").unwrap(), 14/// ) { 15/// Ok(response) => println!("{:?}", response), 16/// Err(_) => {} 17/// } 18/// ``` 19/// 20/// # Errors 21/// - May error if the URL is invalid 22/// - May error if the TLS write fails 23/// - May error if the TLS read fails 24pub fn request(url: &url::Url) -> anyhow::Result<Response> { 25 let config = rustls::ClientConfig::builder() 26 .with_safe_defaults() 27 .with_custom_certificate_verifier(std::sync::Arc::new(GermVerifier::new())) 28 .with_no_client_auth(); 29 let domain = url 30 .domain() 31 .ok_or_else(|| anyhow::anyhow!("Invalid URL: missing domain"))?; 32 let mut connection = rustls::ClientConnection::new( 33 std::sync::Arc::new(config), 34 domain.try_into()?, 35 )?; 36 let mut stream = std::net::TcpStream::connect(format!( 37 "{}:{}", 38 domain, 39 url.port().unwrap_or(1965) 40 ))?; 41 let mut tls = rustls::Stream::new(&mut connection, &mut stream); 42 43 tls.write_all(format!("{url}\r\n").as_bytes())?; 44 45 let mut plain_text = Vec::new(); 46 47 tls.read_to_end(&mut plain_text)?; 48 49 Ok(Response::new(&plain_text, tls.conn.negotiated_cipher_suite())) 50}