Added support for Accept-Encoding

Signed-off-by: Alexandru Cihodaru <cihodar@amazon.com>
Signed-off-by: YUAN LYU <lyuyuan92@gmail.com>
This commit is contained in:
Alexandru Cihodaru
2020-09-03 18:23:24 +03:00
committed by Adrian Catangiu
parent 8a8d7bb5b1
commit 683b85d07e
5 changed files with 157 additions and 8 deletions

View File

@@ -21,6 +21,8 @@ pub enum Header {
Server,
/// Header `Accept`
Accept,
/// Header `Accept-Encoding`
AcceptEncoding,
}
impl Header {
@@ -33,6 +35,7 @@ impl Header {
Self::TransferEncoding => b"Transfer-Encoding",
Self::Server => b"Server",
Self::Accept => b"Accept",
Self::AcceptEncoding => b"Accept-Encoding",
}
}
@@ -52,6 +55,7 @@ impl Header {
"transfer-encoding" => Ok(Self::TransferEncoding),
"server" => Ok(Self::Server),
"accept" => Ok(Self::Accept),
"accept-encoding" => Ok(Self::AcceptEncoding),
invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName(
invalid_key.to_string(),
))),
@@ -199,6 +203,7 @@ impl Headers {
)),
},
Header::Server => Ok(()),
Header::AcceptEncoding => Encoding::try_from(entry[1].trim().as_bytes()),
}
} else {
Err(RequestError::HeaderError(
@@ -286,6 +291,63 @@ impl Headers {
}
}
/// Wrapper over supported AcceptEncoding.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Encoding {}
impl Encoding {
/// Parses a byte slice and checks if identity encoding is invalidated. Encoding
/// must be ASCII, so also UTF-8 valid.
///
/// # Errors
/// `InvalidRequest` is returned when the byte stream is empty.
///
/// `InvalidValue` is returned when the identity encoding is invalidated.
///
/// `InvalidUtf8String` is returned when the byte stream contains invalid characters.
///
/// # Examples
///
/// ```
/// use micro_http::Encoding;
///
/// assert!(Encoding::try_from(b"deflate").is_ok());
/// assert!(Encoding::try_from(b"identity;q=0").is_err());
/// ```
pub fn try_from(bytes: &[u8]) -> Result<(), RequestError> {
if bytes.is_empty() {
return Err(RequestError::InvalidRequest);
}
match std::str::from_utf8(bytes) {
Ok(headers_str) => {
let entry = headers_str.split(',').collect::<Vec<&str>>();
for encoding in entry {
match encoding.trim() {
"identity;q=0" => {
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Accept-Encoding".to_string(),
encoding.to_string(),
)))
}
"*;q=0" if !headers_str.contains("identity") => {
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Accept-Encoding".to_string(),
encoding.to_string(),
)))
}
_ => Ok(()),
}?;
}
Ok(())
}
Err(utf8_err) => Err(RequestError::HeaderError(
HttpHeaderError::InvalidUtf8String(utf8_err),
)),
}
}
}
/// Wrapper over supported Media Types.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MediaType {
@@ -404,6 +466,42 @@ mod tests {
assert_eq!(media_type.as_str(), "text/plain");
}
#[test]
fn test_try_from_encoding() {
assert_eq!(
Encoding::try_from(b"").unwrap_err(),
RequestError::InvalidRequest
);
assert_eq!(
Encoding::try_from(b"identity;q=0").unwrap_err(),
RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Accept-Encoding".to_string(),
"identity;q=0".to_string()
))
);
assert!(Encoding::try_from(b"identity;q").is_ok());
assert_eq!(
Encoding::try_from(b"*;q=0").unwrap_err(),
RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Accept-Encoding".to_string(),
"*;q=0".to_string()
))
);
let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
assert!(Encoding::try_from(&bytes[..]).is_err());
assert!(Encoding::try_from(b"identity;q=1").is_ok());
assert!(Encoding::try_from(b"identity;q=0.1").is_ok());
assert!(Encoding::try_from(b"deflate, identity, *;q=0").is_ok());
assert!(Encoding::try_from(b"br").is_ok());
assert!(Encoding::try_from(b"compress").is_ok());
assert!(Encoding::try_from(b"gzip").is_ok());
}
#[test]
fn test_try_from_headers() {
// Valid headers.
@@ -526,9 +624,9 @@ mod tests {
assert!(header
.parse_header_line(b"Accept: application/json")
.is_ok());
assert!(header.accept == MediaType::ApplicationJson);
assert_eq!(header.accept, MediaType::ApplicationJson);
assert!(header.parse_header_line(b"Accept: text/plain").is_ok());
assert!(header.accept == MediaType::PlainText);
assert_eq!(header.accept, MediaType::PlainText);
// Test invalid accept media type.
assert!(header
@@ -543,6 +641,17 @@ mod tests {
" -1".to_string()
)))
);
assert!(header
.parse_header_line(b"Accept-Encoding: deflate")
.is_ok());
assert_eq!(
header.parse_header_line(b"Accept-Encoding: compress, identity;q=0"),
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Accept-Encoding".to_string(),
" identity;q=0".to_string()
)))
);
}
#[test]

View File

@@ -120,5 +120,5 @@ pub use crate::request::{Request, RequestError};
pub use crate::response::{Response, StatusCode};
pub use crate::server::{HttpServer, ServerError, ServerRequest, ServerResponse};
pub use crate::common::headers::{Headers, MediaType};
pub use crate::common::headers::{Encoding, Headers, MediaType};
pub use crate::common::{Body, HttpHeaderError, Method, Version};

View File

@@ -510,5 +510,15 @@ mod tests {
assert_eq!(request.headers.expect(), false);
assert_eq!(request.headers.content_length(), 0);
assert!(request.body.is_none());
let request = Request::try_from(b"GET http://localhost/ HTTP/1.0\r\n\
Accept-Encoding: identity;q=0\r\n\r\n");
assert_eq!(
request.unwrap_err(),
RequestError::HeaderError(HttpHeaderError::InvalidValue(
"Accept-Encoding".to_string(),
"identity;q=0".to_string()
))
);
}
}

View File

@@ -84,6 +84,7 @@ pub struct ResponseHeaders {
content_type: MediaType,
server: String,
allow: Vec<Method>,
accept_encoding: bool,
}
impl Default for ResponseHeaders {
@@ -93,6 +94,7 @@ impl Default for ResponseHeaders {
content_type: Default::default(),
server: String::from("Firecracker API"),
allow: Vec::new(),
accept_encoding: false,
}
}
}
@@ -140,6 +142,13 @@ impl ResponseHeaders {
buf.write_all(&[COLON, SP])?;
buf.write_all(self.content_length.to_string().as_bytes())?;
buf.write_all(&[CR, LF])?;
if self.accept_encoding {
buf.write_all(Header::AcceptEncoding.raw())?;
buf.write_all(&[COLON, SP])?;
buf.write_all(b"identity")?;
buf.write_all(&[CR, LF])?;
}
}
buf.write_all(&[CR, LF])
@@ -159,6 +168,12 @@ impl ResponseHeaders {
pub fn set_content_type(&mut self, content_type: MediaType) {
self.content_type = content_type;
}
/// Sets the encoding type to be written in the HTTP response.
#[allow(unused)]
pub fn set_encoding(&mut self) {
self.accept_encoding = true;
}
}
/// Wrapper over an HTTP Response.
@@ -198,6 +213,11 @@ impl Response {
self.headers.set_content_type(content_type);
}
/// Updates the encoding type of `Response`.
pub fn set_encoding(&mut self) {
self.headers.set_encoding();
}
/// Sets the HTTP response server.
pub fn set_server(&mut self, server: &str) {
self.headers.set_server(server);
@@ -274,8 +294,9 @@ mod tests {
let body = "This is a test";
response.set_body(Body::new(body));
response.set_content_type(MediaType::PlainText);
response.set_encoding();
assert!(response.status() == StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().unwrap(), Body::new(body));
assert_eq!(response.http_version(), Version::Http10);
assert_eq!(response.content_length(), 14);
@@ -285,12 +306,13 @@ mod tests {
Server: Firecracker API\r\n\
Connection: keep-alive\r\n\
Content-Type: text/plain\r\n\
Content-Length: 14\r\n\r\n\
Content-Length: 14\r\n\
Accept-Encoding: identity\r\n\r\n\
This is a test";
let mut response_buf: [u8; 126] = [0; 126];
let mut response_buf: [u8; 153] = [0; 153];
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
assert!(response_buf.as_ref() == expected_response);
assert_eq!(response_buf.as_ref(), expected_response);
// Test response `Allow` header.
let mut response = Response::new(Version::Http10, StatusCode::OK);
@@ -320,7 +342,7 @@ mod tests {
response.set_content_type(MediaType::PlainText);
response.set_server(server);
assert!(response.status() == StatusCode::OK);
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().unwrap(), Body::new(body));
assert_eq!(response.http_version(), Version::Http10);
assert_eq!(response.content_length(), 14);

View File

@@ -773,6 +773,14 @@ mod tests {
Content-Length: 136\r\n\r\n{ \"error\": \"Invalid header. \
Reason: Invalid value. Key:Content-Length; Value: alpha\nAll previous unanswered requests will be dropped.\" }";
assert_eq!(&buf[..], &error_message[..]);
socket
.write_all(
b"PATCH /machine-config HTTP/1.1\r\n\
Content-Length: alpha\r\n\
Content-Type: application/json\r\n\r\nwhatever body",
)
.unwrap();
}
#[test]