basic client operations
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
b6d062ff8e
commit
c7d8767bf6
|
@ -12,6 +12,7 @@ num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
rustls = "0.18"
|
rustls = "0.18"
|
||||||
webpki = "0.21.0"
|
webpki = "0.21.0"
|
||||||
|
webpki-roots = "0.20"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
url = "2"
|
url = "2"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::{Response, StatusCode};
|
||||||
|
use rustls::{ClientConfig, ClientSession, Stream, TLSError};
|
||||||
|
use std::{io::prelude::*, net::TcpStream, sync::Arc};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
fn config() -> ClientConfig {
|
||||||
|
let mut config = ClientConfig::new();
|
||||||
|
config
|
||||||
|
.root_store
|
||||||
|
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("TLS error: {0:?}")]
|
||||||
|
TLS(#[from] TLSError),
|
||||||
|
|
||||||
|
#[error("URL error: {0:?}")]
|
||||||
|
URL(#[from] url::ParseError),
|
||||||
|
|
||||||
|
#[error("Invalid DNS name: {0:?}")]
|
||||||
|
InvalidDNSName(#[from] webpki::InvalidDNSNameError),
|
||||||
|
|
||||||
|
#[error("IO error: {0:?}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("Response parsing error: {0:?}")]
|
||||||
|
ResponseParse(#[from] crate::ResponseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(u: String) -> Result<crate::Response, Error> {
|
||||||
|
let mut ur = Url::parse(&u.clone())?;
|
||||||
|
if ur.port().is_none() {
|
||||||
|
ur.set_port(Some(1965)).unwrap();
|
||||||
|
}
|
||||||
|
let cfg = Arc::new(config());
|
||||||
|
let host = ur.host_str().unwrap();
|
||||||
|
let mut sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap()))?;
|
||||||
|
let name_ref = webpki::DNSNameRef::try_from_ascii_str(host)?;
|
||||||
|
let mut client = ClientSession::new(&cfg, name_ref);
|
||||||
|
let mut tls = Stream::new(&mut client, &mut sock);
|
||||||
|
|
||||||
|
let req = format!("{}\r\n", u);
|
||||||
|
log::trace!("writing request {:?}", req);
|
||||||
|
tls.write(req.as_bytes())?;
|
||||||
|
Ok(Response::parse(&mut tls)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn gemini_homepage() -> Result<(), Error> {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let resp = get("gemini://gemini.circumlunar.space/".to_string())?;
|
||||||
|
|
||||||
|
assert_eq!(resp.status, StatusCode::Success);
|
||||||
|
assert_eq!(resp.meta, "text/gemini");
|
||||||
|
assert_ne!(resp.body.len(), 0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
|
mod client;
|
||||||
mod status_code;
|
mod status_code;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
pub use status_code::StatusCode;
|
pub use status_code::StatusCode;
|
||||||
pub use response::{Response, Error as ResponseError};
|
pub use response::{Response, Error as ResponseError};
|
||||||
|
pub use client::{get, Error as ClientError};
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::StatusCode;
|
use crate::StatusCode;
|
||||||
use num::FromPrimitive;
|
use num::FromPrimitive;
|
||||||
use std::io::prelude::*;
|
use std::io::{prelude::*, ErrorKind};
|
||||||
|
|
||||||
/// A Gemini response as specified in [the spec](https://gemini.circumlunar.space/docs/specification.html).
|
/// A Gemini response as specified in [the spec](https://gemini.circumlunar.space/docs/specification.html).
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
status: StatusCode,
|
pub status: StatusCode,
|
||||||
meta: String,
|
pub meta: String,
|
||||||
body: Vec<u8>,
|
pub body: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The parser state.
|
/// The parser state.
|
||||||
|
@ -39,6 +39,9 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("None found when none should not be found")]
|
#[error("None found when none should not be found")]
|
||||||
NoneFound,
|
NoneFound,
|
||||||
|
|
||||||
|
#[error("TLS error: {0:?}")]
|
||||||
|
TLS(#[from] rustls::TLSError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
@ -57,8 +60,19 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::trace!("buf: {:?}: {:?}", buf, buf[0] as char);
|
log::trace!("buf: {:?}: {:?}", buf, buf[0] as char);
|
||||||
|
log::trace!("n: {}", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(why) => {
|
||||||
|
if why.kind() == ErrorKind::ConnectionAborted {
|
||||||
|
if let State::ReadBody { data } = state {
|
||||||
|
result.body = data;
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(Error::IO(why));
|
||||||
}
|
}
|
||||||
Err(why) => return Err(Error::IO(why)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// log::trace!("state: {:?}", state);
|
// log::trace!("state: {:?}", state);
|
||||||
|
|
Loading…
Reference in New Issue