feat: Allow to set custom headers in response
EC2 IMDS includes X-Aws-Ec2-Metadata-Token-Ttl-Seconds header in the response to PUT /latest/api/token. To enable Firecracker MMDS to behave compatibly with EC2 IMDS, allow to set custom headers in HTTP response. Signed-off-by: Takahiro Itazuri <itazur@amazon.com>
This commit is contained in:
committed by
Takahiro Itazuri
parent
11cc5da16a
commit
98d85677ba
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- Implemented `Eq` for `common::headers::Encoding`, `common::headers::MediaType`,
|
- Implemented `Eq` for `common::headers::Encoding`, `common::headers::MediaType`,
|
||||||
`common::headers::Headers`, `common::HttpHeaderError`, `common::Body`, `common::Version`,
|
`common::headers::Headers`, `common::HttpHeaderError`, `common::Body`, `common::Version`,
|
||||||
`common::RequestError`, `request::Uri`, `request::RequestLine`, `response::StatusCode`,
|
`common::RequestError`, `request::Uri`, `request::RequestLine`, `response::StatusCode`,
|
||||||
`response::ResponseHeaders`
|
`response::ResponseHeaders`
|
||||||
|
- Allowed to set custom headers in HTTP responses.
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub enum HttpHeaderError {
|
|||||||
InvalidUtf8String(Utf8Error),
|
InvalidUtf8String(Utf8Error),
|
||||||
///The value specified is not valid.
|
///The value specified is not valid.
|
||||||
InvalidValue(String, String),
|
InvalidValue(String, String),
|
||||||
|
/// Non-ASCII character found.
|
||||||
|
NonAsciiCharacter(String, String),
|
||||||
/// The content length specified is longer than the limit imposed by Micro Http.
|
/// The content length specified is longer than the limit imposed by Micro Http.
|
||||||
SizeLimitExceeded(String),
|
SizeLimitExceeded(String),
|
||||||
/// The requested feature is not currently supported.
|
/// The requested feature is not currently supported.
|
||||||
@@ -45,6 +47,13 @@ impl Display for HttpHeaderError {
|
|||||||
Self::InvalidValue(header_name, value) => {
|
Self::InvalidValue(header_name, value) => {
|
||||||
write!(f, "Invalid value. Key:{}; Value:{}", header_name, value)
|
write!(f, "Invalid value. Key:{}; Value:{}", header_name, value)
|
||||||
}
|
}
|
||||||
|
Self::NonAsciiCharacter(header_name, value) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Non-ASCII character found. Key: {}; Value: {}",
|
||||||
|
header_name, value
|
||||||
|
)
|
||||||
|
}
|
||||||
Self::SizeLimitExceeded(inner) => {
|
Self::SizeLimitExceeded(inner) => {
|
||||||
write!(f, "Invalid content length. Header: {}", inner)
|
write!(f, "Invalid content length. Header: {}", inner)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::{Error as WriteError, Write};
|
use std::io::{Error as WriteError, Write};
|
||||||
|
|
||||||
use crate::ascii::{COLON, CR, LF, SP};
|
use crate::ascii::{COLON, CR, LF, SP};
|
||||||
use crate::common::{Body, Version};
|
use crate::common::{Body, Version};
|
||||||
use crate::headers::{Header, MediaType};
|
use crate::headers::{Header, MediaType};
|
||||||
|
use crate::HttpHeaderError;
|
||||||
use crate::Method;
|
use crate::Method;
|
||||||
|
|
||||||
/// Wrapper over a response status code.
|
/// Wrapper over a response status code.
|
||||||
@@ -95,6 +97,7 @@ pub struct ResponseHeaders {
|
|||||||
server: String,
|
server: String,
|
||||||
allow: Vec<Method>,
|
allow: Vec<Method>,
|
||||||
accept_encoding: bool,
|
accept_encoding: bool,
|
||||||
|
custom_headers: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ResponseHeaders {
|
impl Default for ResponseHeaders {
|
||||||
@@ -106,6 +109,7 @@ impl Default for ResponseHeaders {
|
|||||||
server: String::from("Firecracker API"),
|
server: String::from("Firecracker API"),
|
||||||
allow: Vec::new(),
|
allow: Vec::new(),
|
||||||
accept_encoding: false,
|
accept_encoding: false,
|
||||||
|
custom_headers: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,6 +145,18 @@ impl ResponseHeaders {
|
|||||||
buf.write_all(&[CR, LF])
|
buf.write_all(&[CR, LF])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The logic pertaining to custom headers writing.
|
||||||
|
fn write_custom_headers<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
|
||||||
|
// Note that all the custom headers have already been validated as US-ASCII.
|
||||||
|
for (header, value) in &self.custom_headers {
|
||||||
|
buf.write_all(header.as_bytes())?;
|
||||||
|
buf.write_all(&[COLON, SP])?;
|
||||||
|
buf.write_all(value.as_bytes())?;
|
||||||
|
buf.write_all(&[CR, LF])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Writes the headers to `buf` using the HTTP specification.
|
/// Writes the headers to `buf` using the HTTP specification.
|
||||||
pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
|
pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
|
||||||
buf.write_all(Header::Server.raw())?;
|
buf.write_all(Header::Server.raw())?;
|
||||||
@@ -153,6 +169,7 @@ impl ResponseHeaders {
|
|||||||
|
|
||||||
self.write_allow_header(buf)?;
|
self.write_allow_header(buf)?;
|
||||||
self.write_deprecation_header(buf)?;
|
self.write_deprecation_header(buf)?;
|
||||||
|
self.write_custom_headers(buf)?;
|
||||||
|
|
||||||
if let Some(content_length) = self.content_length {
|
if let Some(content_length) = self.content_length {
|
||||||
buf.write_all(Header::ContentType.raw())?;
|
buf.write_all(Header::ContentType.raw())?;
|
||||||
@@ -203,6 +220,26 @@ impl ResponseHeaders {
|
|||||||
pub fn set_encoding(&mut self) {
|
pub fn set_encoding(&mut self) {
|
||||||
self.accept_encoding = true;
|
self.accept_encoding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets custom headers to be written in the HTTP response.
|
||||||
|
pub fn set_custom_headers(
|
||||||
|
&mut self,
|
||||||
|
custom_headers: &HashMap<String, String>,
|
||||||
|
) -> Result<(), HttpHeaderError> {
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7230
|
||||||
|
// HTTP headers MUST be US-ASCII.
|
||||||
|
if let Some((k, v)) = custom_headers
|
||||||
|
.iter()
|
||||||
|
.find(|(k, v)| !k.is_ascii() || !v.is_ascii())
|
||||||
|
{
|
||||||
|
return Err(HttpHeaderError::NonAsciiCharacter(
|
||||||
|
k.to_owned(),
|
||||||
|
v.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.custom_headers = custom_headers.to_owned();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper over an HTTP Response.
|
/// Wrapper over an HTTP Response.
|
||||||
@@ -294,6 +331,19 @@ impl Response {
|
|||||||
self.headers.set_server(server);
|
self.headers.set_server(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the custom headers.
|
||||||
|
pub fn set_custom_headers(
|
||||||
|
&mut self,
|
||||||
|
custom_headers: &HashMap<String, String>,
|
||||||
|
) -> Result<(), HttpHeaderError> {
|
||||||
|
self.headers.set_custom_headers(custom_headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the custom headers.
|
||||||
|
pub fn custom_headers(&self) -> &HashMap<String, String> {
|
||||||
|
&self.headers.custom_headers
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the HTTP allowed methods.
|
/// Sets the HTTP allowed methods.
|
||||||
pub fn set_allow(&mut self, methods: Vec<Method>) {
|
pub fn set_allow(&mut self, methods: Vec<Method>) {
|
||||||
self.headers.allow = methods;
|
self.headers.allow = methods;
|
||||||
@@ -554,4 +604,29 @@ mod tests {
|
|||||||
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
|
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
|
||||||
assert_eq!(response_buf.as_ref(), expected_response);
|
assert_eq!(response_buf.as_ref(), expected_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_headers() {
|
||||||
|
// Valid custom headers.
|
||||||
|
let mut response = Response::new(Version::Http10, StatusCode::OK);
|
||||||
|
let custom_headers = [("Foo".into(), "Bar".into())].into();
|
||||||
|
response.set_custom_headers(&custom_headers).unwrap();
|
||||||
|
let expected_response = b"HTTP/1.0 200 \r\n\
|
||||||
|
Server: Firecracker API\r\n\
|
||||||
|
Connection: keep-alive\r\n\
|
||||||
|
Foo: Bar\r\n\
|
||||||
|
Content-Type: application/json\r\n\
|
||||||
|
Content-Length: 0\r\n\r\n";
|
||||||
|
let mut response_buf: [u8; 127] = [0; 127];
|
||||||
|
response.write_all(&mut response_buf.as_mut()).unwrap();
|
||||||
|
assert_eq!(response_buf.as_ref(), expected_response);
|
||||||
|
|
||||||
|
// Should fail to set custom headers including non-ASCII character.
|
||||||
|
let mut response = Response::new(Version::Http10, StatusCode::OK);
|
||||||
|
let custom_headers = [("Greek capital delta".into(), "Δ".into())].into();
|
||||||
|
assert_eq!(
|
||||||
|
response.set_custom_headers(&custom_headers).unwrap_err(),
|
||||||
|
HttpHeaderError::NonAsciiCharacter("Greek capital delta".into(), "Δ".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user