1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use once_cell::sync::Lazy;
use std::fmt::{Debug, Display};
use std::sync::Mutex;
use url::Url;
use crate::entry::Entry;
use crate::error::source::HttpError;
use crate::error::transform::{HttpError as HttpTransformError, InvalidUrlError};
use crate::sink::Message;
const USER_AGENT: &str =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0";
static CLIENT: Lazy<Mutex<Option<Result<reqwest::Client, HttpError>>>> = Lazy::new(|| {
Mutex::new(Some(
reqwest::ClientBuilder::new()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(HttpError::TlsInitFailed),
))
});
pub struct Http {
pub(crate) url: Url,
client: reqwest::Client,
}
#[derive(Clone, Copy, Debug)]
pub enum TransformFromField {
MessageLink,
RawContents,
}
impl Http {
pub fn new(url: Url) -> Result<Self, HttpError> {
let mut client_lock = CLIENT.lock().unwrap();
let client = match client_lock
.as_ref()
.ok_or(HttpError::ClientNotInitialized)?
{
Ok(client) => client.clone(),
Err(_) => {
let e = client_lock
.take()
.expect("Option should be not empty because we have just ok_or()'ed it")
.unwrap_err();
return Err(e);
}
};
Ok(Self { url, client })
}
#[tracing::instrument(skip_all)]
pub async fn get(&self) -> Result<Entry, HttpError> {
tracing::debug!("Fetching HTTP source");
tracing::trace!("Making a request to {:?}", self.url.as_str());
let request = self
.client
.get(self.url.as_str())
.header(reqwest::header::USER_AGENT, USER_AGENT)
.send()
.await
.map_err(|e| HttpError::Get(e, self.url.to_string()))?;
tracing::trace!("Getting text body of the responce");
let page = request
.text()
.await
.map_err(|e| HttpError::Get(e, self.url.to_string()))?;
tracing::trace!("Done");
Ok(Entry {
raw_contents: Some(page),
msg: Message {
link: Some(self.url.clone()),
..Default::default()
},
..Default::default()
})
}
pub async fn transform(
entry: &Entry,
from_field: TransformFromField,
) -> Result<Entry, HttpTransformError> {
let link = match from_field {
TransformFromField::MessageLink => entry.msg.link.clone(),
TransformFromField::RawContents => entry
.raw_contents
.as_ref()
.map(|s| Url::try_from(s.as_str()).map_err(|e| InvalidUrlError(e, s.to_owned())))
.transpose()?,
};
let link = link.ok_or(HttpTransformError::MissingUrl(from_field))?;
Ok(Self::new(link)?.get().await?)
}
}
impl Debug for Http {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Http")
.field("url", &self.url.as_str())
.finish_non_exhaustive()
}
}
impl Display for TransformFromField {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
TransformFromField::MessageLink => "message's link",
TransformFromField::RawContents => "raw_contents",
})
}
}