aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-04-16 03:10:05 +0000
committerFuwn <[email protected]>2023-04-16 03:10:05 +0000
commit38b68b5b7990fd43a74256a5ad513945866c0617 (patch)
treeb53514da95f460c09543033515ad7efec4e9b410
parentrefactor: optimise return types (diff)
downloadgerm-38b68b5b7990fd43a74256a5ad513945866c0617.tar.xz
germ-38b68b5b7990fd43a74256a5ad513945866c0617.zip
feat(sync): asynchronous request
-rw-r--r--README.md1
-rw-r--r--crates/germ/Cargo.toml3
-rw-r--r--crates/germ/examples/async_request.rs29
-rw-r--r--crates/germ/src/request.rs5
-rw-r--r--crates/germ/src/request/response.rs2
-rw-r--r--crates/germ/src/request/sync.rs77
-rw-r--r--crates/germ/src/request/verifier.rs3
7 files changed, 117 insertions, 3 deletions
diff --git a/README.md b/README.md
index db464b2..26b0e01 100644
--- a/README.md
+++ b/README.md
@@ -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 {} }