Skip to content

Commit a786fac

Browse files
committed
Add Accept-Encoding negotiation
1 parent 308a1af commit a786fac

File tree

3 files changed

+103
-5
lines changed

3 files changed

+103
-5
lines changed

src/content/accept_encoding.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//! Client header advertising available compression algorithms.
22
3-
use crate::content::EncodingProposal;
3+
use crate::content::{ContentEncoding, Encoding, EncodingProposal};
44
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, ACCEPT_ENCODING};
55
use crate::utils::sort_by_weight;
6+
use crate::{Error, StatusCode};
67

78
use std::fmt::{self, Debug, Write};
89
use std::option;
@@ -71,11 +72,43 @@ impl AcceptEncoding {
7172
self.wildcard = wildcard
7273
}
7374

74-
/// Sort the entries in-place.
75+
/// Sort the header directives by weight.
76+
///
77+
/// Headers with a higher `q=` value will be returned first. If two
78+
/// directives have the same weight, the directive that was declared later
79+
/// will be returned first.
7580
pub fn sort(&mut self) {
7681
sort_by_weight(&mut self.entries);
7782
}
7883

84+
/// Determine the most suitable `Content-Type` encoding.
85+
///
86+
/// # Errors
87+
///
88+
/// If no suitable encoding is found, an error with the status of `406` will be returned.
89+
pub fn negotiate(&mut self, encodings: &[Encoding]) -> crate::Result<ContentEncoding> {
90+
// Start by ordering the encodings.
91+
self.sort();
92+
93+
// Try and find the first encoding that matches.
94+
for encoding in &self.entries {
95+
if encodings.contains(&encoding) {
96+
return Ok(encoding.into());
97+
}
98+
}
99+
100+
// If no encoding matches and wildcard is set, send whichever encoding we got.
101+
if self.wildcard {
102+
if let Some(encoding) = encodings.iter().next() {
103+
return Ok(encoding.into());
104+
}
105+
}
106+
107+
let mut err = Error::new_adhoc("No suitable ContentEncoding found");
108+
err.set_status(StatusCode::NotAcceptable);
109+
Err(err)
110+
}
111+
79112
/// Insert a `HeaderName` + `HeaderValue` pair into a `Headers` instance.
80113
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
81114
headers.as_mut().insert(ACCEPT_ENCODING, self.value());
@@ -294,7 +327,7 @@ mod test {
294327
}
295328

296329
#[test]
297-
fn reorder_iter_based_on_weight() -> crate::Result<()> {
330+
fn reorder_based_on_weight() -> crate::Result<()> {
298331
let mut accept = AcceptEncoding::new();
299332
accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
300333
accept.push(EncodingProposal::new(Encoding::Identity, None)?);
@@ -311,4 +344,23 @@ mod test {
311344
assert_eq!(accept.next().unwrap(), Encoding::Identity);
312345
Ok(())
313346
}
347+
348+
#[test]
349+
fn reorder_based_on_weight_and_location() -> crate::Result<()> {
350+
let mut accept = AcceptEncoding::new();
351+
accept.push(EncodingProposal::new(Encoding::Identity, None)?);
352+
accept.push(EncodingProposal::new(Encoding::Gzip, None)?);
353+
accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
354+
355+
let mut headers = Response::new(200);
356+
accept.apply(&mut headers);
357+
358+
let mut accept = AcceptEncoding::from_headers(headers)?.unwrap();
359+
accept.sort();
360+
let mut accept = accept.iter();
361+
assert_eq!(accept.next().unwrap(), Encoding::Brotli);
362+
assert_eq!(accept.next().unwrap(), Encoding::Gzip);
363+
assert_eq!(accept.next().unwrap(), Encoding::Identity);
364+
Ok(())
365+
}
314366
}

src/content/content_encoding.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Specify the compression algorithm.
22
3-
use crate::content::Encoding;
3+
use crate::content::{Encoding, EncodingProposal};
44
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, CONTENT_ENCODING};
55

66
use std::fmt::{self, Debug, Write};
@@ -207,6 +207,38 @@ impl ToHeaderValues for ContentEncoding {
207207
}
208208
}
209209

210+
impl From<Encoding> for ContentEncoding {
211+
fn from(encoding: Encoding) -> Self {
212+
Self {
213+
entries: vec![encoding],
214+
}
215+
}
216+
}
217+
218+
impl From<&Encoding> for ContentEncoding {
219+
fn from(encoding: &Encoding) -> Self {
220+
Self {
221+
entries: vec![*encoding],
222+
}
223+
}
224+
}
225+
226+
impl From<EncodingProposal> for ContentEncoding {
227+
fn from(encoding: EncodingProposal) -> Self {
228+
Self {
229+
entries: vec![encoding.encoding],
230+
}
231+
}
232+
}
233+
234+
impl From<&EncodingProposal> for ContentEncoding {
235+
fn from(encoding: &EncodingProposal) -> Self {
236+
Self {
237+
entries: vec![encoding.encoding],
238+
}
239+
}
240+
}
241+
210242
impl Debug for ContentEncoding {
211243
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212244
let mut list = f.debug_list();

src/content/encoding_proposal.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use crate::headers::HeaderValue;
44
use crate::utils::parse_weight;
55

66
use std::cmp::{Ordering, PartialEq};
7+
use std::ops::{Deref, DerefMut};
78

89
/// A proposed `Encoding` in `AcceptEncoding`.
910
#[derive(Debug, Clone, Copy, PartialEq)]
1011
pub struct EncodingProposal {
1112
/// The proposed encoding.
12-
encoding: Encoding,
13+
pub(crate) encoding: Encoding,
1314

1415
/// The weight of the proposal.
1516
///
@@ -76,6 +77,19 @@ impl PartialEq<Encoding> for &EncodingProposal {
7677
}
7778
}
7879

80+
impl Deref for EncodingProposal {
81+
type Target = Encoding;
82+
fn deref(&self) -> &Self::Target {
83+
&self.encoding
84+
}
85+
}
86+
87+
impl DerefMut for EncodingProposal {
88+
fn deref_mut(&mut self) -> &mut Self::Target {
89+
&mut self.encoding
90+
}
91+
}
92+
7993
// NOTE: Firefox populates Accept-Encoding as `gzip, deflate, br`. This means
8094
// when parsing encodings we should choose the last value in the list under
8195
// equal weights. This impl doesn't know which value was passed later, so that

0 commit comments

Comments
 (0)