mirror of https://github.com/http-rs/http-types
Add `Transfer-Encoding` and `TE` headers
This commit is contained in:
parent
ef5d1c84c0
commit
3eda9dfbe8
|
@ -126,6 +126,9 @@ pub mod mime;
|
|||
pub mod other;
|
||||
pub mod proxies;
|
||||
pub mod server;
|
||||
pub mod trace;
|
||||
pub mod transfer;
|
||||
pub mod upgrade;
|
||||
|
||||
mod body;
|
||||
mod error;
|
||||
|
@ -139,9 +142,6 @@ mod status;
|
|||
mod status_code;
|
||||
mod version;
|
||||
|
||||
pub mod trace;
|
||||
pub mod upgrade;
|
||||
|
||||
pub use body::Body;
|
||||
pub use error::{Error, Result};
|
||||
pub use method::Method;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
use crate::headers::HeaderValue;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
/// Available compression algorithms.
|
||||
///
|
||||
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding#Directives)
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Encoding {
|
||||
/// Send a series of chunks.
|
||||
Chunked,
|
||||
/// The Gzip encoding.
|
||||
Gzip,
|
||||
/// The Deflate encoding.
|
||||
Deflate,
|
||||
/// The Brotli encoding.
|
||||
Brotli,
|
||||
/// The Zstd encoding.
|
||||
Zstd,
|
||||
/// No encoding.
|
||||
Identity,
|
||||
}
|
||||
|
||||
impl Encoding {
|
||||
/// Parses a given string into its corresponding encoding.
|
||||
pub(crate) fn from_str(s: &str) -> Option<Encoding> {
|
||||
let s = s.trim();
|
||||
|
||||
// We're dealing with an empty string.
|
||||
if s.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match s {
|
||||
"chunked" => Some(Encoding::Chunked),
|
||||
"gzip" => Some(Encoding::Gzip),
|
||||
"deflate" => Some(Encoding::Deflate),
|
||||
"br" => Some(Encoding::Brotli),
|
||||
"zstd" => Some(Encoding::Zstd),
|
||||
"identity" => Some(Encoding::Identity),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Encoding {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Encoding::Gzip => write!(f, "gzip"),
|
||||
Encoding::Deflate => write!(f, "deflate"),
|
||||
Encoding::Brotli => write!(f, "br"),
|
||||
Encoding::Zstd => write!(f, "zstd"),
|
||||
Encoding::Identity => write!(f, "identity"),
|
||||
Encoding::Chunked => write!(f, "chunked"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Encoding> for HeaderValue {
|
||||
fn from(directive: Encoding) -> Self {
|
||||
let s = directive.to_string();
|
||||
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
use crate::ensure;
|
||||
use crate::headers::HeaderValue;
|
||||
use crate::transfer::Encoding;
|
||||
use crate::utils::parse_weight;
|
||||
|
||||
use std::cmp::{Ordering, PartialEq};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A proposed `Encoding` in `AcceptEncoding`.
|
||||
///
|
||||
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/TE#Directives)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct EncodingProposal {
|
||||
/// The proposed encoding.
|
||||
pub(crate) encoding: Encoding,
|
||||
|
||||
/// The weight of the proposal.
|
||||
///
|
||||
/// This is a number between 0.0 and 1.0, and is max 3 decimal points.
|
||||
weight: Option<f32>,
|
||||
}
|
||||
|
||||
impl EncodingProposal {
|
||||
/// Create a new instance of `EncodingProposal`.
|
||||
pub fn new(encoding: impl Into<Encoding>, weight: Option<f32>) -> crate::Result<Self> {
|
||||
if let Some(weight) = weight {
|
||||
ensure!(
|
||||
weight.is_sign_positive() && weight <= 1.0,
|
||||
"EncodingProposal should have a weight between 0.0 and 1.0"
|
||||
)
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
encoding: encoding.into(),
|
||||
weight,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the proposed encoding.
|
||||
pub fn encoding(&self) -> &Encoding {
|
||||
&self.encoding
|
||||
}
|
||||
|
||||
/// Get the weight of the proposal.
|
||||
pub fn weight(&self) -> Option<f32> {
|
||||
self.weight
|
||||
}
|
||||
|
||||
pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
|
||||
let mut parts = s.split(';');
|
||||
let encoding = match Encoding::from_str(parts.next().unwrap()) {
|
||||
Some(encoding) => encoding,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let weight = parts.next().map(parse_weight).transpose()?;
|
||||
|
||||
Ok(Some(Self::new(encoding, weight)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Encoding> for EncodingProposal {
|
||||
fn from(encoding: Encoding) -> Self {
|
||||
Self {
|
||||
encoding,
|
||||
weight: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Encoding> for EncodingProposal {
|
||||
fn eq(&self, other: &Encoding) -> bool {
|
||||
self.encoding == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Encoding> for &EncodingProposal {
|
||||
fn eq(&self, other: &Encoding) -> bool {
|
||||
self.encoding == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for EncodingProposal {
|
||||
type Target = Encoding;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.encoding
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for EncodingProposal {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.encoding
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Firefox populates Accept-Encoding as `gzip, deflate, br`. This means
|
||||
// when parsing encodings we should choose the last value in the list under
|
||||
// equal weights. This impl doesn't know which value was passed later, so that
|
||||
// behavior needs to be handled separately.
|
||||
//
|
||||
// NOTE: This comparison does not include a notion of `*` (any value is valid).
|
||||
// that needs to be handled separately.
|
||||
impl PartialOrd for EncodingProposal {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match (self.weight, other.weight) {
|
||||
(Some(left), Some(right)) => left.partial_cmp(&right),
|
||||
(Some(_), None) => Some(Ordering::Greater),
|
||||
(None, Some(_)) => Some(Ordering::Less),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncodingProposal> for HeaderValue {
|
||||
fn from(entry: EncodingProposal) -> HeaderValue {
|
||||
let s = match entry.weight {
|
||||
Some(weight) => format!("{};q={:.3}", entry.encoding, weight),
|
||||
None => entry.encoding.to_string(),
|
||||
};
|
||||
unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn smoke() -> crate::Result<()> {
|
||||
let _ = EncodingProposal::new(Encoding::Gzip, Some(0.0)).unwrap();
|
||||
let _ = EncodingProposal::new(Encoding::Gzip, Some(0.5)).unwrap();
|
||||
let _ = EncodingProposal::new(Encoding::Gzip, Some(1.0)).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_code_500() -> crate::Result<()> {
|
||||
let err = EncodingProposal::new(Encoding::Gzip, Some(1.1)).unwrap_err();
|
||||
assert_eq!(err.status(), 500);
|
||||
|
||||
let err = EncodingProposal::new(Encoding::Gzip, Some(-0.1)).unwrap_err();
|
||||
assert_eq!(err.status(), 500);
|
||||
|
||||
let err = EncodingProposal::new(Encoding::Gzip, Some(-0.0)).unwrap_err();
|
||||
assert_eq!(err.status(), 500);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//! HTTP transfer headers.
|
||||
//!
|
||||
//! [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#Transfer_coding)
|
||||
|
||||
mod encoding;
|
||||
mod encoding_proposal;
|
||||
mod te;
|
||||
mod transfer_encoding;
|
||||
|
||||
pub use encoding::Encoding;
|
||||
pub use encoding_proposal::EncodingProposal;
|
||||
pub use te::TE;
|
||||
pub use transfer_encoding::TransferEncoding;
|
|
@ -0,0 +1,430 @@
|
|||
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, ACCEPT_ENCODING};
|
||||
use crate::transfer::{Encoding, EncodingProposal, TransferEncoding};
|
||||
use crate::utils::sort_by_weight;
|
||||
use crate::{Error, StatusCode};
|
||||
|
||||
use std::fmt::{self, Debug, Write};
|
||||
use std::option;
|
||||
use std::slice;
|
||||
|
||||
/// Client header advertising the transfer encodings the user agent is willing to
|
||||
/// accept.
|
||||
///
|
||||
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/TE)
|
||||
///
|
||||
/// # Specifications
|
||||
///
|
||||
/// - [RFC 7230, section 4.3: TE](https://tools.ietf.org/html/rfc7230#section-4.3)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> http_types::Result<()> {
|
||||
/// #
|
||||
/// use http_types::transfer::{TE, TransferEncoding, Encoding, EncodingProposal};
|
||||
/// use http_types::Response;
|
||||
///
|
||||
/// let mut te = TE::new();
|
||||
/// te.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
|
||||
/// te.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
|
||||
/// te.push(EncodingProposal::new(Encoding::Identity, None)?);
|
||||
///
|
||||
/// let mut res = Response::new(200);
|
||||
/// let encoding = te.negotiate(&[Encoding::Brotli, Encoding::Gzip])?;
|
||||
/// encoding.apply(&mut res);
|
||||
///
|
||||
/// assert_eq!(res["Content-Encoding"], "br");
|
||||
/// #
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub struct TE {
|
||||
wildcard: bool,
|
||||
entries: Vec<EncodingProposal>,
|
||||
}
|
||||
|
||||
impl TE {
|
||||
/// Create a new instance of `TE`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: vec![],
|
||||
wildcard: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an instance of `TE` from a `Headers` instance.
|
||||
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
|
||||
let mut entries = vec![];
|
||||
let headers = match headers.as_ref().get(ACCEPT_ENCODING) {
|
||||
Some(headers) => headers,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let mut wildcard = false;
|
||||
|
||||
for value in headers {
|
||||
for part in value.as_str().trim().split(',') {
|
||||
let part = part.trim();
|
||||
|
||||
// Handle empty strings, and wildcard directives.
|
||||
if part.is_empty() {
|
||||
continue;
|
||||
} else if part == "*" {
|
||||
wildcard = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try and parse a directive from a str. If the directive is
|
||||
// unkown we skip it.
|
||||
if let Some(entry) = EncodingProposal::from_str(part)? {
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Self { entries, wildcard }))
|
||||
}
|
||||
|
||||
/// Push a directive into the list of entries.
|
||||
pub fn push(&mut self, prop: impl Into<EncodingProposal>) {
|
||||
self.entries.push(prop.into());
|
||||
}
|
||||
|
||||
/// Returns `true` if a wildcard directive was passed.
|
||||
pub fn wildcard(&self) -> bool {
|
||||
self.wildcard
|
||||
}
|
||||
|
||||
/// Set the wildcard directive.
|
||||
pub fn set_wildcard(&mut self, wildcard: bool) {
|
||||
self.wildcard = wildcard
|
||||
}
|
||||
|
||||
/// Sort the header directives by weight.
|
||||
///
|
||||
/// Headers with a higher `q=` value will be returned first. If two
|
||||
/// directives have the same weight, the directive that was declared later
|
||||
/// will be returned first.
|
||||
pub fn sort(&mut self) {
|
||||
sort_by_weight(&mut self.entries);
|
||||
}
|
||||
|
||||
/// Determine the most suitable `Content-Type` encoding.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If no suitable encoding is found, an error with the status of `406` will be returned.
|
||||
pub fn negotiate(&mut self, available: &[Encoding]) -> crate::Result<TransferEncoding> {
|
||||
// Start by ordering the encodings.
|
||||
self.sort();
|
||||
|
||||
// Try and find the first encoding that matches.
|
||||
for encoding in &self.entries {
|
||||
if available.contains(&encoding) {
|
||||
return Ok(encoding.into());
|
||||
}
|
||||
}
|
||||
|
||||
// If no encoding matches and wildcard is set, send whichever encoding we got.
|
||||
if self.wildcard {
|
||||
if let Some(encoding) = available.iter().next() {
|
||||
return Ok(encoding.into());
|
||||
}
|
||||
}
|
||||
|
||||
let mut err = Error::new_adhoc("No suitable ContentEncoding found");
|
||||
err.set_status(StatusCode::NotAcceptable);
|
||||
Err(err)
|
||||
}
|
||||
|
||||
/// Sets the `Accept-Encoding` header.
|
||||
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
|
||||
headers.as_mut().insert(ACCEPT_ENCODING, self.value());
|
||||
}
|
||||
|
||||
/// Get the `HeaderName`.
|
||||
pub fn name(&self) -> HeaderName {
|
||||
ACCEPT_ENCODING
|
||||
}
|
||||
|
||||
/// Get the `HeaderValue`.
|
||||
pub fn value(&self) -> HeaderValue {
|
||||
let mut output = String::new();
|
||||
for (n, directive) in self.entries.iter().enumerate() {
|
||||
let directive: HeaderValue = directive.clone().into();
|
||||
match n {
|
||||
0 => write!(output, "{}", directive).unwrap(),
|
||||
_ => write!(output, ", {}", directive).unwrap(),
|
||||
};
|
||||
}
|
||||
|
||||
if self.wildcard {
|
||||
match output.len() {
|
||||
0 => write!(output, "*").unwrap(),
|
||||
_ => write!(output, ", *").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: the internal string is validated to be ASCII.
|
||||
unsafe { HeaderValue::from_bytes_unchecked(output.into()) }
|
||||
}
|
||||
|
||||
/// An iterator visiting all entries.
|
||||
pub fn iter(&self) -> Iter<'_> {
|
||||
Iter {
|
||||
inner: self.entries.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator visiting all entries.
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_> {
|
||||
IterMut {
|
||||
inner: self.entries.iter_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for TE {
|
||||
type Item = EncodingProposal;
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter {
|
||||
inner: self.entries.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a TE {
|
||||
type Item = &'a EncodingProposal;
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut TE {
|
||||
type Item = &'a mut EncodingProposal;
|
||||
type IntoIter = IterMut<'a>;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// A borrowing iterator over entries in `TE`.
|
||||
#[derive(Debug)]
|
||||
pub struct IntoIter {
|
||||
inner: std::vec::IntoIter<EncodingProposal>,
|
||||
}
|
||||
|
||||
impl Iterator for IntoIter {
|
||||
type Item = EncodingProposal;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
/// A lending iterator over entries in `TE`.
|
||||
#[derive(Debug)]
|
||||
pub struct Iter<'a> {
|
||||
inner: slice::Iter<'a, EncodingProposal>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = &'a EncodingProposal;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutable iterator over entries in `TE`.
|
||||
#[derive(Debug)]
|
||||
pub struct IterMut<'a> {
|
||||
inner: slice::IterMut<'a, EncodingProposal>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IterMut<'a> {
|
||||
type Item = &'a mut EncodingProposal;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHeaderValues for TE {
|
||||
type Iter = option::IntoIter<HeaderValue>;
|
||||
fn to_header_values(&self) -> crate::Result<Self::Iter> {
|
||||
// A HeaderValue will always convert into itself.
|
||||
Ok(self.value().to_header_values().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for TE {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut list = f.debug_list();
|
||||
for directive in &self.entries {
|
||||
list.entry(directive);
|
||||
}
|
||||
list.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::transfer::Encoding;
|
||||
use crate::Response;
|
||||
|
||||
#[test]
|
||||
fn smoke() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(Encoding::Gzip);
|
||||
|
||||
let mut headers = Response::new(200);
|
||||
accept.apply(&mut headers);
|
||||
|
||||
let accept = TE::from_headers(headers)?.unwrap();
|
||||
assert_eq!(accept.iter().next().unwrap(), Encoding::Gzip);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.set_wildcard(true);
|
||||
|
||||
let mut headers = Response::new(200);
|
||||
accept.apply(&mut headers);
|
||||
|
||||
let accept = TE::from_headers(headers)?.unwrap();
|
||||
assert!(accept.wildcard());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_and_header() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(Encoding::Gzip);
|
||||
accept.set_wildcard(true);
|
||||
|
||||
let mut headers = Response::new(200);
|
||||
accept.apply(&mut headers);
|
||||
|
||||
let accept = TE::from_headers(headers)?.unwrap();
|
||||
assert!(accept.wildcard());
|
||||
assert_eq!(accept.iter().next().unwrap(), Encoding::Gzip);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(Encoding::Gzip);
|
||||
accept.push(Encoding::Brotli);
|
||||
|
||||
let mut headers = Response::new(200);
|
||||
accept.apply(&mut headers);
|
||||
|
||||
let accept = TE::from_headers(headers)?.unwrap();
|
||||
let mut accept = accept.iter();
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Gzip);
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Brotli);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorder_based_on_weight() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
|
||||
accept.push(EncodingProposal::new(Encoding::Identity, None)?);
|
||||
accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
|
||||
|
||||
let mut headers = Response::new(200);
|
||||
accept.apply(&mut headers);
|
||||
|
||||
let mut accept = TE::from_headers(headers)?.unwrap();
|
||||
accept.sort();
|
||||
let mut accept = accept.iter();
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Brotli);
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Gzip);
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Identity);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reorder_based_on_weight_and_location() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(EncodingProposal::new(Encoding::Identity, None)?);
|
||||
accept.push(EncodingProposal::new(Encoding::Gzip, None)?);
|
||||
accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
|
||||
|
||||
let mut res = Response::new(200);
|
||||
accept.apply(&mut res);
|
||||
|
||||
let mut accept = TE::from_headers(res)?.unwrap();
|
||||
accept.sort();
|
||||
let mut accept = accept.iter();
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Brotli);
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Gzip);
|
||||
assert_eq!(accept.next().unwrap(), Encoding::Identity);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negotiate() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
|
||||
accept.push(EncodingProposal::new(Encoding::Gzip, Some(0.4))?);
|
||||
accept.push(EncodingProposal::new(Encoding::Identity, None)?);
|
||||
|
||||
assert_eq!(
|
||||
accept.negotiate(&[Encoding::Brotli, Encoding::Gzip])?,
|
||||
Encoding::Brotli,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negotiate_not_acceptable() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
let err = accept.negotiate(&[Encoding::Gzip]).unwrap_err();
|
||||
assert_eq!(err.status(), 406);
|
||||
|
||||
let mut accept = TE::new();
|
||||
accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
|
||||
let err = accept.negotiate(&[Encoding::Gzip]).unwrap_err();
|
||||
assert_eq!(err.status(), 406);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negotiate_wildcard() -> crate::Result<()> {
|
||||
let mut accept = TE::new();
|
||||
accept.push(EncodingProposal::new(Encoding::Brotli, Some(0.8))?);
|
||||
accept.set_wildcard(true);
|
||||
|
||||
assert_eq!(accept.negotiate(&[Encoding::Gzip])?, Encoding::Gzip);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
use crate::headers::{HeaderName, HeaderValue, Headers, ToHeaderValues, CONTENT_ENCODING};
|
||||
use crate::transfer::{Encoding, EncodingProposal};
|
||||
|
||||
use std::fmt::{self, Debug};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::option;
|
||||
|
||||
/// The form of encoding used to safely transfer the payload body to the user.
|
||||
///
|
||||
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding)
|
||||
///
|
||||
/// # Specifications
|
||||
///
|
||||
/// - [RFC 7230, section 3.3.1: Transfer-Encoding](https://tools.ietf.org/html/rfc7230#section-3.3.1)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> http_types::Result<()> {
|
||||
/// #
|
||||
/// use http_types::Response;
|
||||
/// use http_types::transfer::{TransferEncoding, Encoding};
|
||||
/// let mut encoding = TransferEncoding::new(Encoding::Chunked);
|
||||
///
|
||||
/// let mut res = Response::new(200);
|
||||
/// encoding.apply(&mut res);
|
||||
///
|
||||
/// let encoding = TransferEncoding::from_headers(res)?.unwrap();
|
||||
/// assert_eq!(encoding, &Encoding::Chunked);
|
||||
/// #
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub struct TransferEncoding {
|
||||
inner: Encoding,
|
||||
}
|
||||
|
||||
impl TransferEncoding {
|
||||
/// Create a new instance of `CacheControl`.
|
||||
pub fn new(encoding: Encoding) -> Self {
|
||||
Self { inner: encoding }
|
||||
}
|
||||
|
||||
/// Create a new instance from headers.
|
||||
pub fn from_headers(headers: impl AsRef<Headers>) -> crate::Result<Option<Self>> {
|
||||
let headers = match headers.as_ref().get(CONTENT_ENCODING) {
|
||||
Some(headers) => headers,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let mut inner = None;
|
||||
|
||||
for value in headers {
|
||||
if let Some(entry) = Encoding::from_str(value.as_str()) {
|
||||
inner = Some(entry);
|
||||
}
|
||||
}
|
||||
|
||||
let inner = inner.expect("Headers instance with no entries found");
|
||||
Ok(Some(Self { inner }))
|
||||
}
|
||||
|
||||
/// Sets the `Content-Encoding` header.
|
||||
pub fn apply(&self, mut headers: impl AsMut<Headers>) {
|
||||
headers.as_mut().insert(CONTENT_ENCODING, self.value());
|
||||
}
|
||||
|
||||
/// Get the `HeaderName`.
|
||||
pub fn name(&self) -> HeaderName {
|
||||
CONTENT_ENCODING
|
||||
}
|
||||
|
||||
/// Get the `HeaderValue`.
|
||||
pub fn value(&self) -> HeaderValue {
|
||||
self.inner.into()
|
||||
}
|
||||
|
||||
/// Access the encoding kind.
|
||||
pub fn encoding(&self) -> Encoding {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHeaderValues for TransferEncoding {
|
||||
type Iter = option::IntoIter<HeaderValue>;
|
||||
fn to_header_values(&self) -> crate::Result<Self::Iter> {
|
||||
// A HeaderValue will always convert into itself.
|
||||
Ok(self.value().to_header_values().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TransferEncoding {
|
||||
type Target = Encoding;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TransferEncoding {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Encoding> for TransferEncoding {
|
||||
fn eq(&self, other: &Encoding) -> bool {
|
||||
&self.inner == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&Encoding> for TransferEncoding {
|
||||
fn eq(&self, other: &&Encoding) -> bool {
|
||||
&&self.inner == other
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Encoding> for TransferEncoding {
|
||||
fn from(encoding: Encoding) -> Self {
|
||||
Self { inner: encoding }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Encoding> for TransferEncoding {
|
||||
fn from(encoding: &Encoding) -> Self {
|
||||
Self { inner: *encoding }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncodingProposal> for TransferEncoding {
|
||||
fn from(encoding: EncodingProposal) -> Self {
|
||||
Self {
|
||||
inner: encoding.encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EncodingProposal> for TransferEncoding {
|
||||
fn from(encoding: &EncodingProposal) -> Self {
|
||||
Self {
|
||||
inner: encoding.encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for TransferEncoding {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue