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
+111
-2
@@ -21,6 +21,8 @@ pub enum Header {
|
|||||||
Server,
|
Server,
|
||||||
/// Header `Accept`
|
/// Header `Accept`
|
||||||
Accept,
|
Accept,
|
||||||
|
/// Header `Accept-Encoding`
|
||||||
|
AcceptEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
@@ -33,6 +35,7 @@ impl Header {
|
|||||||
Self::TransferEncoding => b"Transfer-Encoding",
|
Self::TransferEncoding => b"Transfer-Encoding",
|
||||||
Self::Server => b"Server",
|
Self::Server => b"Server",
|
||||||
Self::Accept => b"Accept",
|
Self::Accept => b"Accept",
|
||||||
|
Self::AcceptEncoding => b"Accept-Encoding",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ impl Header {
|
|||||||
"transfer-encoding" => Ok(Self::TransferEncoding),
|
"transfer-encoding" => Ok(Self::TransferEncoding),
|
||||||
"server" => Ok(Self::Server),
|
"server" => Ok(Self::Server),
|
||||||
"accept" => Ok(Self::Accept),
|
"accept" => Ok(Self::Accept),
|
||||||
|
"accept-encoding" => Ok(Self::AcceptEncoding),
|
||||||
invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName(
|
invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName(
|
||||||
invalid_key.to_string(),
|
invalid_key.to_string(),
|
||||||
))),
|
))),
|
||||||
@@ -199,6 +203,7 @@ impl Headers {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Header::Server => Ok(()),
|
Header::Server => Ok(()),
|
||||||
|
Header::AcceptEncoding => Encoding::try_from(entry[1].trim().as_bytes()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(RequestError::HeaderError(
|
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.
|
/// Wrapper over supported Media Types.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum MediaType {
|
pub enum MediaType {
|
||||||
@@ -404,6 +466,42 @@ mod tests {
|
|||||||
assert_eq!(media_type.as_str(), "text/plain");
|
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]
|
#[test]
|
||||||
fn test_try_from_headers() {
|
fn test_try_from_headers() {
|
||||||
// Valid headers.
|
// Valid headers.
|
||||||
@@ -526,9 +624,9 @@ mod tests {
|
|||||||
assert!(header
|
assert!(header
|
||||||
.parse_header_line(b"Accept: application/json")
|
.parse_header_line(b"Accept: application/json")
|
||||||
.is_ok());
|
.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.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.
|
// Test invalid accept media type.
|
||||||
assert!(header
|
assert!(header
|
||||||
@@ -543,6 +641,17 @@ mod tests {
|
|||||||
" -1".to_string()
|
" -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]
|
#[test]
|
||||||
|
|||||||
+1
-1
@@ -120,5 +120,5 @@ pub use crate::request::{Request, RequestError};
|
|||||||
pub use crate::response::{Response, StatusCode};
|
pub use crate::response::{Response, StatusCode};
|
||||||
pub use crate::server::{HttpServer, ServerError, ServerRequest, ServerResponse};
|
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};
|
pub use crate::common::{Body, HttpHeaderError, Method, Version};
|
||||||
|
|||||||
@@ -510,5 +510,15 @@ mod tests {
|
|||||||
assert_eq!(request.headers.expect(), false);
|
assert_eq!(request.headers.expect(), false);
|
||||||
assert_eq!(request.headers.content_length(), 0);
|
assert_eq!(request.headers.content_length(), 0);
|
||||||
assert!(request.body.is_none());
|
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()
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-5
@@ -84,6 +84,7 @@ pub struct ResponseHeaders {
|
|||||||
content_type: MediaType,
|
content_type: MediaType,
|
||||||
server: String,
|
server: String,
|
||||||
allow: Vec<Method>,
|
allow: Vec<Method>,
|
||||||
|
accept_encoding: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ResponseHeaders {
|
impl Default for ResponseHeaders {
|
||||||
@@ -93,6 +94,7 @@ impl Default for ResponseHeaders {
|
|||||||
content_type: Default::default(),
|
content_type: Default::default(),
|
||||||
server: String::from("Firecracker API"),
|
server: String::from("Firecracker API"),
|
||||||
allow: Vec::new(),
|
allow: Vec::new(),
|
||||||
|
accept_encoding: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,6 +142,13 @@ impl ResponseHeaders {
|
|||||||
buf.write_all(&[COLON, SP])?;
|
buf.write_all(&[COLON, SP])?;
|
||||||
buf.write_all(self.content_length.to_string().as_bytes())?;
|
buf.write_all(self.content_length.to_string().as_bytes())?;
|
||||||
buf.write_all(&[CR, LF])?;
|
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])
|
buf.write_all(&[CR, LF])
|
||||||
@@ -159,6 +168,12 @@ impl ResponseHeaders {
|
|||||||
pub fn set_content_type(&mut self, content_type: MediaType) {
|
pub fn set_content_type(&mut self, content_type: MediaType) {
|
||||||
self.content_type = content_type;
|
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.
|
/// Wrapper over an HTTP Response.
|
||||||
@@ -198,6 +213,11 @@ impl Response {
|
|||||||
self.headers.set_content_type(content_type);
|
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.
|
/// Sets the HTTP response server.
|
||||||
pub fn set_server(&mut self, server: &str) {
|
pub fn set_server(&mut self, server: &str) {
|
||||||
self.headers.set_server(server);
|
self.headers.set_server(server);
|
||||||
@@ -274,8 +294,9 @@ mod tests {
|
|||||||
let body = "This is a test";
|
let body = "This is a test";
|
||||||
response.set_body(Body::new(body));
|
response.set_body(Body::new(body));
|
||||||
response.set_content_type(MediaType::PlainText);
|
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.body().unwrap(), Body::new(body));
|
||||||
assert_eq!(response.http_version(), Version::Http10);
|
assert_eq!(response.http_version(), Version::Http10);
|
||||||
assert_eq!(response.content_length(), 14);
|
assert_eq!(response.content_length(), 14);
|
||||||
@@ -285,12 +306,13 @@ mod tests {
|
|||||||
Server: Firecracker API\r\n\
|
Server: Firecracker API\r\n\
|
||||||
Connection: keep-alive\r\n\
|
Connection: keep-alive\r\n\
|
||||||
Content-Type: text/plain\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";
|
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.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.
|
// Test response `Allow` header.
|
||||||
let mut response = Response::new(Version::Http10, StatusCode::OK);
|
let mut response = Response::new(Version::Http10, StatusCode::OK);
|
||||||
@@ -320,7 +342,7 @@ mod tests {
|
|||||||
response.set_content_type(MediaType::PlainText);
|
response.set_content_type(MediaType::PlainText);
|
||||||
response.set_server(server);
|
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.body().unwrap(), Body::new(body));
|
||||||
assert_eq!(response.http_version(), Version::Http10);
|
assert_eq!(response.http_version(), Version::Http10);
|
||||||
assert_eq!(response.content_length(), 14);
|
assert_eq!(response.content_length(), 14);
|
||||||
|
|||||||
@@ -773,6 +773,14 @@ mod tests {
|
|||||||
Content-Length: 136\r\n\r\n{ \"error\": \"Invalid header. \
|
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.\" }";
|
Reason: Invalid value. Key:Content-Length; Value: alpha\nAll previous unanswered requests will be dropped.\" }";
|
||||||
assert_eq!(&buf[..], &error_message[..]);
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user