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:
committed by
Adrian Catangiu
parent
8a8d7bb5b1
commit
683b85d07e
@@ -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]
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user