forked from cadey/maj
basic client operations
This commit is contained in:
parent
b6d062ff8e
commit
c7d8767bf6
|
@ -12,6 +12,7 @@ num-derive = "0.3"
|
|||
num-traits = "0.2"
|
||||
rustls = "0.18"
|
||||
webpki = "0.21.0"
|
||||
webpki-roots = "0.20"
|
||||
log = "0.4"
|
||||
url = "2"
|
||||
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 response;
|
||||
|
||||
pub use status_code::StatusCode;
|
||||
pub use response::{Response, Error as ResponseError};
|
||||
pub use client::{get, Error as ClientError};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::StatusCode;
|
||||
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).
|
||||
#[derive(Default)]
|
||||
pub struct Response {
|
||||
status: StatusCode,
|
||||
meta: String,
|
||||
body: Vec<u8>,
|
||||
pub status: StatusCode,
|
||||
pub meta: String,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The parser state.
|
||||
|
@ -39,6 +39,9 @@ pub enum Error {
|
|||
|
||||
#[error("None found when none should not be found")]
|
||||
NoneFound,
|
||||
|
||||
#[error("TLS error: {0:?}")]
|
||||
TLS(#[from] rustls::TLSError),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
|
@ -57,8 +60,19 @@ impl Response {
|
|||
}
|
||||
}
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue