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