Skip to content

Commit 683b85d

Browse files
AlexandruCihodaruacatangiu
authored andcommitted
Added support for Accept-Encoding
Signed-off-by: Alexandru Cihodaru <[email protected]> Signed-off-by: YUAN LYU <[email protected]>
1 parent 8a8d7bb commit 683b85d

File tree

5 files changed

+157
-8
lines changed

5 files changed

+157
-8
lines changed

src/common/headers.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub enum Header {
2121
Server,
2222
/// Header `Accept`
2323
Accept,
24+
/// Header `Accept-Encoding`
25+
AcceptEncoding,
2426
}
2527

2628
impl Header {
@@ -33,6 +35,7 @@ impl Header {
3335
Self::TransferEncoding => b"Transfer-Encoding",
3436
Self::Server => b"Server",
3537
Self::Accept => b"Accept",
38+
Self::AcceptEncoding => b"Accept-Encoding",
3639
}
3740
}
3841

@@ -52,6 +55,7 @@ impl Header {
5255
"transfer-encoding" => Ok(Self::TransferEncoding),
5356
"server" => Ok(Self::Server),
5457
"accept" => Ok(Self::Accept),
58+
"accept-encoding" => Ok(Self::AcceptEncoding),
5559
invalid_key => Err(RequestError::HeaderError(HttpHeaderError::UnsupportedName(
5660
invalid_key.to_string(),
5761
))),
@@ -199,6 +203,7 @@ impl Headers {
199203
)),
200204
},
201205
Header::Server => Ok(()),
206+
Header::AcceptEncoding => Encoding::try_from(entry[1].trim().as_bytes()),
202207
}
203208
} else {
204209
Err(RequestError::HeaderError(
@@ -286,6 +291,63 @@ impl Headers {
286291
}
287292
}
288293

294+
/// Wrapper over supported AcceptEncoding.
295+
#[derive(Clone, Copy, Debug, PartialEq)]
296+
pub struct Encoding {}
297+
298+
impl Encoding {
299+
/// Parses a byte slice and checks if identity encoding is invalidated. Encoding
300+
/// must be ASCII, so also UTF-8 valid.
301+
///
302+
/// # Errors
303+
/// `InvalidRequest` is returned when the byte stream is empty.
304+
///
305+
/// `InvalidValue` is returned when the identity encoding is invalidated.
306+
///
307+
/// `InvalidUtf8String` is returned when the byte stream contains invalid characters.
308+
///
309+
/// # Examples
310+
///
311+
/// ```
312+
/// use micro_http::Encoding;
313+
///
314+
/// assert!(Encoding::try_from(b"deflate").is_ok());
315+
/// assert!(Encoding::try_from(b"identity;q=0").is_err());
316+
/// ```
317+
pub fn try_from(bytes: &[u8]) -> Result<(), RequestError> {
318+
if bytes.is_empty() {
319+
return Err(RequestError::InvalidRequest);
320+
}
321+
match std::str::from_utf8(bytes) {
322+
Ok(headers_str) => {
323+
let entry = headers_str.split(',').collect::<Vec<&str>>();
324+
325+
for encoding in entry {
326+
match encoding.trim() {
327+
"identity;q=0" => {
328+
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
329+
"Accept-Encoding".to_string(),
330+
encoding.to_string(),
331+
)))
332+
}
333+
"*;q=0" if !headers_str.contains("identity") => {
334+
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
335+
"Accept-Encoding".to_string(),
336+
encoding.to_string(),
337+
)))
338+
}
339+
_ => Ok(()),
340+
}?;
341+
}
342+
Ok(())
343+
}
344+
Err(utf8_err) => Err(RequestError::HeaderError(
345+
HttpHeaderError::InvalidUtf8String(utf8_err),
346+
)),
347+
}
348+
}
349+
}
350+
289351
/// Wrapper over supported Media Types.
290352
#[derive(Clone, Copy, Debug, PartialEq)]
291353
pub enum MediaType {
@@ -404,6 +466,42 @@ mod tests {
404466
assert_eq!(media_type.as_str(), "text/plain");
405467
}
406468

469+
#[test]
470+
fn test_try_from_encoding() {
471+
assert_eq!(
472+
Encoding::try_from(b"").unwrap_err(),
473+
RequestError::InvalidRequest
474+
);
475+
476+
assert_eq!(
477+
Encoding::try_from(b"identity;q=0").unwrap_err(),
478+
RequestError::HeaderError(HttpHeaderError::InvalidValue(
479+
"Accept-Encoding".to_string(),
480+
"identity;q=0".to_string()
481+
))
482+
);
483+
484+
assert!(Encoding::try_from(b"identity;q").is_ok());
485+
486+
assert_eq!(
487+
Encoding::try_from(b"*;q=0").unwrap_err(),
488+
RequestError::HeaderError(HttpHeaderError::InvalidValue(
489+
"Accept-Encoding".to_string(),
490+
"*;q=0".to_string()
491+
))
492+
);
493+
494+
let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
495+
assert!(Encoding::try_from(&bytes[..]).is_err());
496+
497+
assert!(Encoding::try_from(b"identity;q=1").is_ok());
498+
assert!(Encoding::try_from(b"identity;q=0.1").is_ok());
499+
assert!(Encoding::try_from(b"deflate, identity, *;q=0").is_ok());
500+
assert!(Encoding::try_from(b"br").is_ok());
501+
assert!(Encoding::try_from(b"compress").is_ok());
502+
assert!(Encoding::try_from(b"gzip").is_ok());
503+
}
504+
407505
#[test]
408506
fn test_try_from_headers() {
409507
// Valid headers.
@@ -526,9 +624,9 @@ mod tests {
526624
assert!(header
527625
.parse_header_line(b"Accept: application/json")
528626
.is_ok());
529-
assert!(header.accept == MediaType::ApplicationJson);
627+
assert_eq!(header.accept, MediaType::ApplicationJson);
530628
assert!(header.parse_header_line(b"Accept: text/plain").is_ok());
531-
assert!(header.accept == MediaType::PlainText);
629+
assert_eq!(header.accept, MediaType::PlainText);
532630

533631
// Test invalid accept media type.
534632
assert!(header
@@ -543,6 +641,17 @@ mod tests {
543641
" -1".to_string()
544642
)))
545643
);
644+
645+
assert!(header
646+
.parse_header_line(b"Accept-Encoding: deflate")
647+
.is_ok());
648+
assert_eq!(
649+
header.parse_header_line(b"Accept-Encoding: compress, identity;q=0"),
650+
Err(RequestError::HeaderError(HttpHeaderError::InvalidValue(
651+
"Accept-Encoding".to_string(),
652+
" identity;q=0".to_string()
653+
)))
654+
);
546655
}
547656

548657
#[test]

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,5 @@ pub use crate::request::{Request, RequestError};
120120
pub use crate::response::{Response, StatusCode};
121121
pub use crate::server::{HttpServer, ServerError, ServerRequest, ServerResponse};
122122

123-
pub use crate::common::headers::{Headers, MediaType};
123+
pub use crate::common::headers::{Encoding, Headers, MediaType};
124124
pub use crate::common::{Body, HttpHeaderError, Method, Version};

src/request.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,5 +510,15 @@ mod tests {
510510
assert_eq!(request.headers.expect(), false);
511511
assert_eq!(request.headers.content_length(), 0);
512512
assert!(request.body.is_none());
513+
514+
let request = Request::try_from(b"GET http://localhost/ HTTP/1.0\r\n\
515+
Accept-Encoding: identity;q=0\r\n\r\n");
516+
assert_eq!(
517+
request.unwrap_err(),
518+
RequestError::HeaderError(HttpHeaderError::InvalidValue(
519+
"Accept-Encoding".to_string(),
520+
"identity;q=0".to_string()
521+
))
522+
);
513523
}
514524
}

src/response.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub struct ResponseHeaders {
8484
content_type: MediaType,
8585
server: String,
8686
allow: Vec<Method>,
87+
accept_encoding: bool,
8788
}
8889

8990
impl Default for ResponseHeaders {
@@ -93,6 +94,7 @@ impl Default for ResponseHeaders {
9394
content_type: Default::default(),
9495
server: String::from("Firecracker API"),
9596
allow: Vec::new(),
97+
accept_encoding: false,
9698
}
9799
}
98100
}
@@ -140,6 +142,13 @@ impl ResponseHeaders {
140142
buf.write_all(&[COLON, SP])?;
141143
buf.write_all(self.content_length.to_string().as_bytes())?;
142144
buf.write_all(&[CR, LF])?;
145+
146+
if self.accept_encoding {
147+
buf.write_all(Header::AcceptEncoding.raw())?;
148+
buf.write_all(&[COLON, SP])?;
149+
buf.write_all(b"identity")?;
150+
buf.write_all(&[CR, LF])?;
151+
}
143152
}
144153

145154
buf.write_all(&[CR, LF])
@@ -159,6 +168,12 @@ impl ResponseHeaders {
159168
pub fn set_content_type(&mut self, content_type: MediaType) {
160169
self.content_type = content_type;
161170
}
171+
172+
/// Sets the encoding type to be written in the HTTP response.
173+
#[allow(unused)]
174+
pub fn set_encoding(&mut self) {
175+
self.accept_encoding = true;
176+
}
162177
}
163178

164179
/// Wrapper over an HTTP Response.
@@ -198,6 +213,11 @@ impl Response {
198213
self.headers.set_content_type(content_type);
199214
}
200215

216+
/// Updates the encoding type of `Response`.
217+
pub fn set_encoding(&mut self) {
218+
self.headers.set_encoding();
219+
}
220+
201221
/// Sets the HTTP response server.
202222
pub fn set_server(&mut self, server: &str) {
203223
self.headers.set_server(server);
@@ -274,8 +294,9 @@ mod tests {
274294
let body = "This is a test";
275295
response.set_body(Body::new(body));
276296
response.set_content_type(MediaType::PlainText);
297+
response.set_encoding();
277298

278-
assert!(response.status() == StatusCode::OK);
299+
assert_eq!(response.status(), StatusCode::OK);
279300
assert_eq!(response.body().unwrap(), Body::new(body));
280301
assert_eq!(response.http_version(), Version::Http10);
281302
assert_eq!(response.content_length(), 14);
@@ -285,12 +306,13 @@ mod tests {
285306
Server: Firecracker API\r\n\
286307
Connection: keep-alive\r\n\
287308
Content-Type: text/plain\r\n\
288-
Content-Length: 14\r\n\r\n\
309+
Content-Length: 14\r\n\
310+
Accept-Encoding: identity\r\n\r\n\
289311
This is a test";
290312

291-
let mut response_buf: [u8; 126] = [0; 126];
313+
let mut response_buf: [u8; 153] = [0; 153];
292314
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
293-
assert!(response_buf.as_ref() == expected_response);
315+
assert_eq!(response_buf.as_ref(), expected_response);
294316

295317
// Test response `Allow` header.
296318
let mut response = Response::new(Version::Http10, StatusCode::OK);
@@ -320,7 +342,7 @@ mod tests {
320342
response.set_content_type(MediaType::PlainText);
321343
response.set_server(server);
322344

323-
assert!(response.status() == StatusCode::OK);
345+
assert_eq!(response.status(), StatusCode::OK);
324346
assert_eq!(response.body().unwrap(), Body::new(body));
325347
assert_eq!(response.http_version(), Version::Http10);
326348
assert_eq!(response.content_length(), 14);

src/server.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,14 @@ mod tests {
773773
Content-Length: 136\r\n\r\n{ \"error\": \"Invalid header. \
774774
Reason: Invalid value. Key:Content-Length; Value: alpha\nAll previous unanswered requests will be dropped.\" }";
775775
assert_eq!(&buf[..], &error_message[..]);
776+
777+
socket
778+
.write_all(
779+
b"PATCH /machine-config HTTP/1.1\r\n\
780+
Content-Length: alpha\r\n\
781+
Content-Type: application/json\r\n\r\nwhatever body",
782+
)
783+
.unwrap();
776784
}
777785

778786
#[test]

0 commit comments

Comments
 (0)