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
use std::collections::HashMap;
use chrono::prelude::{DateTime, Utc};
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use serde_json::{self, Value as JsonValue};
use std::io::prelude::*;
#[derive(PartialEq, Debug, Clone)]
pub struct PingRequest {
pub document_id: String,
pub path: String,
pub body: Vec<u8>,
pub headers: HashMap<&'static str, String>,
}
impl PingRequest {
pub fn new(document_id: &str, path: &str, body: JsonValue) -> Self {
let original_as_string = body.to_string();
let gzipped_content = Self::gzip_content(path, original_as_string.as_bytes());
let add_gzip_header = gzipped_content.is_some();
let body = gzipped_content.unwrap_or_else(|| original_as_string.into_bytes());
let body_len = body.len();
Self {
document_id: document_id.into(),
path: path.into(),
body,
headers: Self::create_request_headers(add_gzip_header, body_len),
}
}
pub fn is_deletion_request(&self) -> bool {
self.path
.split('/')
.nth(3)
.map(|url| url == "deletion-request")
.unwrap_or(false)
}
pub fn pretty_body(&self) -> Option<String> {
let mut gz = GzDecoder::new(&self.body[..]);
let mut s = String::with_capacity(self.body.len());
gz.read_to_string(&mut s)
.ok()
.map(|_| &s[..])
.or_else(|| std::str::from_utf8(&self.body).ok())
.and_then(|payload| serde_json::from_str::<JsonValue>(payload).ok())
.and_then(|json| serde_json::to_string_pretty(&json).ok())
}
fn gzip_content(path: &str, content: &[u8]) -> Option<Vec<u8>> {
let mut gzipper = GzEncoder::new(Vec::new(), Compression::default());
if let Err(e) = gzipper.write_all(content) {
log::error!("Failed to write to the gzipper: {} - {:?}", path, e);
return None;
}
gzipper.finish().ok()
}
fn create_date_header_value(current_time: DateTime<Utc>) -> String {
current_time.format("%a, %d %b %Y %T GMT").to_string()
}
fn create_request_headers(is_gzipped: bool, body_len: usize) -> HashMap<&'static str, String> {
let mut headers = HashMap::new();
headers.insert("Date", Self::create_date_header_value(Utc::now()));
headers.insert("X-Client-Type", "Glean".to_string());
headers.insert(
"Content-Type",
"application/json; charset=utf-8".to_string(),
);
headers.insert("Content-Length", body_len.to_string());
if is_gzipped {
headers.insert("Content-Encoding", "gzip".to_string());
}
headers.insert("X-Client-Version", crate::GLEAN_VERSION.to_string());
headers
}
}
#[cfg(test)]
mod test {
use super::*;
use chrono::offset::TimeZone;
#[test]
fn test_date_header_resolution() {
let date: DateTime<Utc> = Utc.ymd(2018, 2, 25).and_hms(11, 10, 37);
let test_value = PingRequest::create_date_header_value(date);
assert_eq!("Sun, 25 Feb 2018 11:10:37 GMT", test_value);
}
}