micro-http: added MethodNotAllow status code

When server returns `Method Not Allow` responses,
it can attach to the response a HTTP `Allow` header.
Added support for HTTP response `Allow` header as
well.

Signed-off-by: Iulian Barbu <iul@amazon.com>
Signed-off-by: YUAN LYU <lyuyuan92@gmail.com>
This commit is contained in:
Iulian Barbu
2020-04-29 14:30:00 +03:00
committed by Adrian Catangiu
parent 530b36bfd9
commit d37f9671af
2 changed files with 69 additions and 4 deletions

View File

@@ -142,7 +142,7 @@ impl Body {
}
/// Supported HTTP Methods.
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Method {
/// GET Method.
Get,

View File

@@ -3,9 +3,10 @@
use std::io::{Error as WriteError, Write};
use crate::common::ascii::{COLON, CR, LF, SP};
use crate::common::headers::{Header, MediaType};
use crate::common::{Body, Version};
use ascii::{COLON, CR, LF, SP};
use common::{Body, Version};
use headers::{Header, MediaType};
use Method;
/// Wrapper over a response status code.
///
@@ -23,6 +24,8 @@ pub enum StatusCode {
BadRequest,
/// 404, Not Found
NotFound,
/// 405, Method Not Allowed
MethodNotAllowed,
/// 500, Internal Server Error
InternalServerError,
/// 501, Not Implemented
@@ -40,6 +43,7 @@ impl StatusCode {
Self::NoContent => b"204",
Self::BadRequest => b"400",
Self::NotFound => b"404",
Self::MethodNotAllowed => b"405",
Self::InternalServerError => b"500",
Self::NotImplemented => b"501",
Self::ServiceUnavailable => b"503",
@@ -77,6 +81,7 @@ pub struct ResponseHeaders {
content_length: i32,
content_type: MediaType,
server: String,
allow: Vec<Method>,
}
impl Default for ResponseHeaders {
@@ -85,11 +90,31 @@ impl Default for ResponseHeaders {
content_length: Default::default(),
content_type: Default::default(),
server: String::from("Firecracker API"),
allow: Vec::new(),
}
}
}
impl ResponseHeaders {
// The logic pertaining to `Allow` header writing.
fn write_allow_header<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
if self.allow.is_empty() {
return Ok(());
}
buf.write_all(b"Allow: ")?;
let delimitator = b", ";
for (idx, method) in self.allow.iter().enumerate() {
buf.write_all(method.raw())?;
if idx < self.allow.len() - 1 {
buf.write_all(delimitator)?;
}
}
buf.write_all(&[CR, LF])
}
/// Writes the headers to `buf` using the HTTP specification.
pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
buf.write_all(Header::Server.raw())?;
@@ -100,6 +125,8 @@ impl ResponseHeaders {
buf.write_all(b"Connection: keep-alive")?;
buf.write_all(&[CR, LF])?;
self.write_allow_header(buf)?;
if self.content_length != 0 {
buf.write_all(Header::ContentType.raw())?;
buf.write_all(&[COLON, SP])?;
@@ -172,6 +199,16 @@ impl Response {
self.headers.set_server(server);
}
/// Sets the HTTP allowed methods.
pub fn set_allow(&mut self, methods: Vec<Method>) {
self.headers.allow = methods;
}
/// Allows a specific HTTP method.
pub fn allow_method(&mut self, method: Method) {
self.headers.allow.push(method);
}
fn write_body<T: Write>(&self, mut buf: T) -> Result<(), WriteError> {
if let Some(ref body) = self.body {
buf.write_all(body.raw())?;
@@ -216,6 +253,11 @@ impl Response {
pub fn http_version(&self) -> Version {
self.status_line.http_version
}
/// Returns the allowed HTTP methods.
pub fn allow(&self) -> Vec<Method> {
self.headers.allow.clone()
}
}
#[cfg(test)]
@@ -246,6 +288,20 @@ mod tests {
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
assert!(response_buf.as_ref() == expected_response);
// Test response `Allow` header.
let mut response = Response::new(Version::Http10, StatusCode::OK);
let allowed_methods = vec![Method::Get, Method::Patch, Method::Put];
response.set_allow(allowed_methods.clone());
assert_eq!(response.allow(), allowed_methods);
let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
Server: Firecracker API\r\n\
Connection: keep-alive\r\n\
Allow: GET, PATCH, PUT\r\n\r\n";
let mut response_buf: [u8; 90] = [0; 90];
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
assert_eq!(response_buf.as_ref(), expected_response);
// Test write failed.
let mut response_buf: [u8; 1] = [0; 1];
assert!(response.write_all(&mut response_buf.as_mut()).is_err());
@@ -288,8 +344,17 @@ mod tests {
assert_eq!(StatusCode::NoContent.raw(), b"204");
assert_eq!(StatusCode::BadRequest.raw(), b"400");
assert_eq!(StatusCode::NotFound.raw(), b"404");
assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405");
assert_eq!(StatusCode::InternalServerError.raw(), b"500");
assert_eq!(StatusCode::NotImplemented.raw(), b"501");
assert_eq!(StatusCode::ServiceUnavailable.raw(), b"503");
}
#[test]
fn test_allow_method() {
let mut response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
response.allow_method(Method::Get);
response.allow_method(Method::Put);
assert_eq!(response.allow(), vec![Method::Get, Method::Put]);
}
}