Support multiple Content-Encoding

This commit is contained in:
Fabrice Reix 2020-10-20 13:51:50 +02:00
parent 9617cba31d
commit 1464a5d908
11 changed files with 344 additions and 147 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -24,4 +24,11 @@ HTTP/1.0 200
Content-Length: 17
Content-Encoding: br
Content-Type: text/html; charset=utf-8
```Hello World!```
GET http://localhost:8000/compressed/brotli_identity
HTTP/1.0 200
Content-Length: 17
Content-Encoding: br, identity
Content-Type: text/html; charset=utf-8
```Hello World!```

View File

@ -30,6 +30,17 @@ def compressed_brotli():
resp.headers['Content-Encoding'] = 'br'
return resp
@app.route("/compressed/brotli_identity")
def compressed_brotli_identity():
result = BytesIO()
result.write(b'\x21\x2c\x00\x04\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x03')
data = result.getvalue()
resp = make_response(data)
resp.headers['Content-Encoding'] = 'br, identity'
return resp
@app.route("/compressed/none")
def compressed_none():
return 'Hello World!'

View File

@ -0,0 +1,7 @@
error: Decompression Error
--> tests/error_assert_content_encoding.hurl:4:1
|
4 | ```Hello World!```
| ^ Compression unknown is not supported
|

View File

@ -0,0 +1 @@
4

View File

@ -0,0 +1,4 @@
# Return an unsupported content encoding
GET http://localhost:8000/error/content-encoding
HTTP/1.0 200
```Hello World!```

View File

@ -0,0 +1,7 @@
from tests import app
from flask import Response
@app.route("/error/content-encoding")
def error_assert_content_encoding():
headers = {'Content-Encoding': 'unknown'}
return Response('Hello', headers=headers)

View File

@ -1,7 +1,7 @@
error: Decompression Error
--> tests/error_assert_decompress.hurl:1:1
--> tests/error_assert_decompress.hurl:3:1
|
1 | GET http://localhost:8000/error-assert-decompress
3 | ```Hello World!```
| ^ Could not uncompress response with gzip
|

View File

@ -34,31 +34,49 @@ pub enum Encoding {
Identity,
}
impl Encoding {
pub fn parse(s: &str) -> Result<Encoding, RunnerError> {
match s {
"br" => Ok(Encoding::Brotli),
"gzip" => Ok(Encoding::Gzip),
"deflate" => Ok(Encoding::Deflate),
"identity" => Ok(Encoding::Identity),
v => Err(RunnerError::UnsupportedContentEncoding(v.to_string())),
}
}
pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, RunnerError> {
match self {
Encoding::Identity => Ok(data.to_vec()),
Encoding::Gzip => uncompress_gzip(&data[..]),
Encoding::Deflate => uncompress_zlib(&data[..]),
Encoding::Brotli => uncompress_brotli(&data[..]),
}
}
}
impl http::Response {
fn content_encoding(&self) -> Result<Option<Encoding>, RunnerError> {
fn content_encoding(&self) -> Result<Vec<Encoding>, RunnerError> {
for header in self.headers.clone() {
if header.name.as_str().to_ascii_lowercase() == "content-encoding" {
return match header.value.as_str() {
"br" => Ok(Some(Encoding::Brotli)),
"gzip" => Ok(Some(Encoding::Gzip)),
"deflate" => Ok(Some(Encoding::Deflate)),
"identity" => Ok(Some(Encoding::Identity)),
v => Err(RunnerError::UnsupportedContentEncoding(v.to_string())),
};
let mut encodings = vec![];
for value in header.value.as_str().split(',') {
let encoding = Encoding::parse(value.trim())?;
encodings.push(encoding);
}
return Ok(encodings);
}
}
Ok(None)
Ok(vec![])
}
pub fn uncompress_body(&self) -> Result<Vec<u8>, RunnerError> {
let encoding = self.content_encoding()?;
match encoding {
Some(Encoding::Identity) => Ok(self.body.clone()),
Some(Encoding::Gzip) => uncompress_gzip(&self.body[..]),
Some(Encoding::Deflate) => uncompress_zlib(&self.body[..]),
Some(Encoding::Brotli) => uncompress_brotli(&self.body[..]),
None => Ok(self.body.clone()),
let encodings = self.content_encoding()?;
let mut data = self.body.clone();
for encoding in encodings {
data = encoding.decode(&data)?
}
Ok(data)
}
}
@ -104,6 +122,15 @@ fn uncompress_zlib(data: &[u8]) -> Result<Vec<u8>, RunnerError> {
pub mod tests {
use super::*;
#[test]
fn test_parse_encoding() {
assert_eq!(Encoding::parse("br").unwrap(), Encoding::Brotli);
assert_eq!(
Encoding::parse("xx").err().unwrap(),
RunnerError::UnsupportedContentEncoding("xx".to_string())
);
}
#[test]
fn test_content_encoding() {
let response = http::Response {
@ -112,7 +139,7 @@ pub mod tests {
headers: vec![],
body: vec![],
};
assert_eq!(response.content_encoding().unwrap(), None);
assert_eq!(response.content_encoding().unwrap(), vec![]);
let response = http::Response {
version: http::Version::Http10,
@ -137,9 +164,23 @@ pub mod tests {
}],
body: vec![],
};
assert_eq!(response.content_encoding().unwrap(), vec![Encoding::Brotli]);
}
#[test]
fn test_multiple_content_encoding() {
let response = http::Response {
version: http::Version::Http10,
status: 200,
headers: vec![http::Header {
name: "Content-Encoding".to_string(),
value: "br, identity".to_string(),
}],
body: vec![],
};
assert_eq!(
response.content_encoding().unwrap().unwrap(),
Encoding::Brotli
response.content_encoding().unwrap(),
vec![Encoding::Brotli, Encoding::Identity]
);
}
@ -159,6 +200,20 @@ pub mod tests {
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
let response = http::Response {
version: http::Version::Http10,
status: 200,
headers: vec![http::Header {
name: "Content-Encoding".to_string(),
value: "br, identity".to_string(),
}],
body: vec![
0x21, 0x2c, 0x00, 0x04, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c,
0x64, 0x21,
],
};
assert_eq!(response.uncompress_body().unwrap(), b"Hello World!");
let response = http::Response {
version: http::Version::Http10,
status: 200,

View File

@ -115,8 +115,8 @@ impl Response {
Ok(s) => Ok(Value::String(s)),
Err(e) => Err(Error {
source_info: SourceInfo {
start: Pos { line: 1, column: 1 },
end: Pos { line: 1, column: 1 },
start: body.space0.source_info.end.clone(),
end: body.space0.source_info.end.clone(),
},
inner: e,
assert: true,
@ -134,8 +134,8 @@ impl Response {
Ok(s) => Ok(Value::String(s)),
Err(e) => Err(Error {
source_info: SourceInfo {
start: Pos { line: 1, column: 1 },
end: Pos { line: 1, column: 1 },
start: body.space0.source_info.end.clone(),
end: body.space0.source_info.end.clone(),
},
inner: e,
assert: true,
@ -156,8 +156,8 @@ impl Response {
Ok(s) => Ok(Value::String(s)),
Err(e) => Err(Error {
source_info: SourceInfo {
start: Pos { line: 1, column: 1 },
end: Pos { line: 1, column: 1 },
start: body.space0.source_info.end.clone(),
end: body.space0.source_info.end.clone(),
},
inner: e,
assert: true,