馃 The Definitive Gemini Protocol Toolkit
gemini
gemini-protocol
gemtext
parser
zero-dependency
toolkit
ast
converter
html
markdown
cli
networking
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}