diff options
| author | Fuwn <[email protected]> | 2023-04-16 03:10:05 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-04-16 03:10:05 +0000 |
| commit | 38b68b5b7990fd43a74256a5ad513945866c0617 (patch) | |
| tree | b53514da95f460c09543033515ad7efec4e9b410 | |
| parent | refactor: optimise return types (diff) | |
| download | germ-38b68b5b7990fd43a74256a5ad513945866c0617.tar.xz germ-38b68b5b7990fd43a74256a5ad513945866c0617.zip | |
feat(sync): asynchronous request
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | crates/germ/Cargo.toml | 3 | ||||
| -rw-r--r-- | crates/germ/examples/async_request.rs | 29 | ||||
| -rw-r--r-- | crates/germ/src/request.rs | 5 | ||||
| -rw-r--r-- | crates/germ/src/request/response.rs | 2 | ||||
| -rw-r--r-- | crates/germ/src/request/sync.rs | 77 | ||||
| -rw-r--r-- | crates/germ/src/request/verifier.rs | 3 |
7 files changed, 117 insertions, 3 deletions
@@ -47,6 +47,7 @@ features = ["ast"] # Enable the features you would like to use! | `meta` | Structure-ize a Gemini response's meta section | | `macros` | Macros to aid with various Germ-related functionalities | | `quick` | Quick functions to create valid Gemtext elements from input | +| `sync` | An asynchronous version of `request` | ### Examples diff --git a/crates/germ/Cargo.toml b/crates/germ/Cargo.toml index 2bde5c9..9c289cb 100644 --- a/crates/germ/Cargo.toml +++ b/crates/germ/Cargo.toml @@ -22,6 +22,7 @@ macros = ["ast", "convert", "germ-macros-impl"] meta = [] request = ["rustls", "url", "anyhow"] quick = [] +sync = ["tokio", "tokio-rustls"] [dependencies] anyhow = { version = "1.0.70", optional = true } # `Result` @@ -29,4 +30,6 @@ germ-macros-impl = { path = "../germ-macros-impl", version = "0.1.0", optional = rustls = { version = "0.21.0", features = [ "dangerous_configuration" ], optional = true } # TLS +tokio-rustls = { version = "0.24.0", optional = true } # Non-blocking TLS +tokio = { version = "1.27.0", optional = true, default-features = false, features = ["net", "io-util", "rt-multi-thread", "macros"] } # Non-blocking I/O url = { version = "2.3.1", optional = true } # URL Validation diff --git a/crates/germ/examples/async_request.rs b/crates/germ/examples/async_request.rs new file mode 100644 index 0000000..604d6d4 --- /dev/null +++ b/crates/germ/examples/async_request.rs @@ -0,0 +1,29 @@ +// This file is part of Germ <https://github.com/gemrest/germ>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +#[tokio::main] +async fn main() { + match germ::request::sync::request( + &url::Url::parse("gemini://fuwn.me").unwrap(), + ) + .await + { + Ok(response) => println!("{:?}", response), + Err(_) => {} + } +} diff --git a/crates/germ/src/request.rs b/crates/germ/src/request.rs index 85ee72d..2c7c273 100644 --- a/crates/germ/src/request.rs +++ b/crates/germ/src/request.rs @@ -22,11 +22,14 @@ mod response; mod status; mod verifier; +#[cfg(feature = "sync")] +pub mod sync; + use std::io::{Read, Write}; pub use response::Response; pub use status::Status; -use verifier::GermVerifier; +pub(crate) use verifier::GermVerifier; /// Make a request to a Gemini server. The `url` **should** be prefixed with a /// scheme (e.g. "gemini://"). diff --git a/crates/germ/src/request/response.rs b/crates/germ/src/request/response.rs index dd76e92..4c822e1 100644 --- a/crates/germ/src/request/response.rs +++ b/crates/germ/src/request/response.rs @@ -32,7 +32,7 @@ pub struct Response { } impl Response { - pub(super) fn new(data: &[u8], suite: Option<SupportedCipherSuite>) -> Self { + pub(crate) fn new(data: &[u8], suite: Option<SupportedCipherSuite>) -> Self { let string_form = String::from_utf8_lossy(data).to_string(); let mut content = None; let header; diff --git a/crates/germ/src/request/sync.rs b/crates/germ/src/request/sync.rs new file mode 100644 index 0000000..68c8a0f --- /dev/null +++ b/crates/germ/src/request/sync.rs @@ -0,0 +1,77 @@ +// This file is part of Germ <https://github.com/gemrest/germ>. +// Copyright (C) 2022-2023 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use crate::request::Response; + +/// Make a request to a Gemini server +/// +/// The `url` **should** be prefixed with a scheme (e.g. "gemini://"). +/// +/// # Example +/// +/// ```rust +/// match germ::request::request(&url::Url::parse("gemini://fuwn.me").unwrap()) +/// .await +/// { +/// Ok(response) => println!("{:?}", response), +/// Err(_) => {} +/// } +/// ``` +/// +/// # Errors +/// +/// - May error if the URL is invalid +/// - May error if the server is unreachable +/// - May error if the TLS write fails +/// - May error if the TLS read fails +pub async fn request(url: &url::Url) -> anyhow::Result<Response> { + let mut tls = tokio_rustls::TlsConnector::from(std::sync::Arc::new( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(std::sync::Arc::new( + crate::request::GermVerifier::new(), + )) + .with_no_client_auth(), + )) + .connect( + rustls::ServerName::try_from(url.domain().unwrap_or_default())?, + tokio::net::TcpStream::connect(format!( + "{}:{}", + url.domain().unwrap_or(""), + url.port().unwrap_or(1965) + )) + .await?, + ) + .await?; + let cipher_suite = tls.get_mut().1.negotiated_cipher_suite(); + + tls.write_all(format!("{url}\r\n").as_bytes()).await?; + + Ok(Response::new( + &{ + let mut plain_text = Vec::new(); + + tls.read_to_end(&mut plain_text).await?; + + plain_text + }, + cipher_suite, + )) +} diff --git a/crates/germ/src/request/verifier.rs b/crates/germ/src/request/verifier.rs index c1b763c..b0120bd 100644 --- a/crates/germ/src/request/verifier.rs +++ b/crates/germ/src/request/verifier.rs @@ -20,7 +20,8 @@ use std::time::SystemTime; use rustls::{client, client::ServerCertVerified, Certificate}; -pub(super) struct GermVerifier; +#[allow(clippy::module_name_repetitions)] +pub struct GermVerifier; impl GermVerifier { pub const fn new() -> Self { Self {} } |