Skip to content

Commit d37f967

Browse files
Iulian Barbuacatangiu
authored andcommitted
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 <[email protected]> Signed-off-by: YUAN LYU <[email protected]>
1 parent 530b36b commit d37f967

File tree

2 files changed

+69
-4
lines changed

2 files changed

+69
-4
lines changed

src/common/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ impl Body {
142142
}
143143

144144
/// Supported HTTP Methods.
145-
#[derive(Clone, Copy, Debug, PartialEq)]
145+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
146146
pub enum Method {
147147
/// GET Method.
148148
Get,

src/response.rs

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
use std::io::{Error as WriteError, Write};
55

6-
use crate::common::ascii::{COLON, CR, LF, SP};
7-
use crate::common::headers::{Header, MediaType};
8-
use crate::common::{Body, Version};
6+
use ascii::{COLON, CR, LF, SP};
7+
use common::{Body, Version};
8+
use headers::{Header, MediaType};
9+
use Method;
910

1011
/// Wrapper over a response status code.
1112
///
@@ -23,6 +24,8 @@ pub enum StatusCode {
2324
BadRequest,
2425
/// 404, Not Found
2526
NotFound,
27+
/// 405, Method Not Allowed
28+
MethodNotAllowed,
2629
/// 500, Internal Server Error
2730
InternalServerError,
2831
/// 501, Not Implemented
@@ -40,6 +43,7 @@ impl StatusCode {
4043
Self::NoContent => b"204",
4144
Self::BadRequest => b"400",
4245
Self::NotFound => b"404",
46+
Self::MethodNotAllowed => b"405",
4347
Self::InternalServerError => b"500",
4448
Self::NotImplemented => b"501",
4549
Self::ServiceUnavailable => b"503",
@@ -77,6 +81,7 @@ pub struct ResponseHeaders {
7781
content_length: i32,
7882
content_type: MediaType,
7983
server: String,
84+
allow: Vec<Method>,
8085
}
8186

8287
impl Default for ResponseHeaders {
@@ -85,11 +90,31 @@ impl Default for ResponseHeaders {
8590
content_length: Default::default(),
8691
content_type: Default::default(),
8792
server: String::from("Firecracker API"),
93+
allow: Vec::new(),
8894
}
8995
}
9096
}
9197

9298
impl ResponseHeaders {
99+
// The logic pertaining to `Allow` header writing.
100+
fn write_allow_header<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
101+
if self.allow.is_empty() {
102+
return Ok(());
103+
}
104+
105+
buf.write_all(b"Allow: ")?;
106+
107+
let delimitator = b", ";
108+
for (idx, method) in self.allow.iter().enumerate() {
109+
buf.write_all(method.raw())?;
110+
if idx < self.allow.len() - 1 {
111+
buf.write_all(delimitator)?;
112+
}
113+
}
114+
115+
buf.write_all(&[CR, LF])
116+
}
117+
93118
/// Writes the headers to `buf` using the HTTP specification.
94119
pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
95120
buf.write_all(Header::Server.raw())?;
@@ -100,6 +125,8 @@ impl ResponseHeaders {
100125
buf.write_all(b"Connection: keep-alive")?;
101126
buf.write_all(&[CR, LF])?;
102127

128+
self.write_allow_header(buf)?;
129+
103130
if self.content_length != 0 {
104131
buf.write_all(Header::ContentType.raw())?;
105132
buf.write_all(&[COLON, SP])?;
@@ -172,6 +199,16 @@ impl Response {
172199
self.headers.set_server(server);
173200
}
174201

202+
/// Sets the HTTP allowed methods.
203+
pub fn set_allow(&mut self, methods: Vec<Method>) {
204+
self.headers.allow = methods;
205+
}
206+
207+
/// Allows a specific HTTP method.
208+
pub fn allow_method(&mut self, method: Method) {
209+
self.headers.allow.push(method);
210+
}
211+
175212
fn write_body<T: Write>(&self, mut buf: T) -> Result<(), WriteError> {
176213
if let Some(ref body) = self.body {
177214
buf.write_all(body.raw())?;
@@ -216,6 +253,11 @@ impl Response {
216253
pub fn http_version(&self) -> Version {
217254
self.status_line.http_version
218255
}
256+
257+
/// Returns the allowed HTTP methods.
258+
pub fn allow(&self) -> Vec<Method> {
259+
self.headers.allow.clone()
260+
}
219261
}
220262

221263
#[cfg(test)]
@@ -246,6 +288,20 @@ mod tests {
246288
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
247289
assert!(response_buf.as_ref() == expected_response);
248290

291+
// Test response `Allow` header.
292+
let mut response = Response::new(Version::Http10, StatusCode::OK);
293+
let allowed_methods = vec![Method::Get, Method::Patch, Method::Put];
294+
response.set_allow(allowed_methods.clone());
295+
assert_eq!(response.allow(), allowed_methods);
296+
297+
let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
298+
Server: Firecracker API\r\n\
299+
Connection: keep-alive\r\n\
300+
Allow: GET, PATCH, PUT\r\n\r\n";
301+
let mut response_buf: [u8; 90] = [0; 90];
302+
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
303+
assert_eq!(response_buf.as_ref(), expected_response);
304+
249305
// Test write failed.
250306
let mut response_buf: [u8; 1] = [0; 1];
251307
assert!(response.write_all(&mut response_buf.as_mut()).is_err());
@@ -288,8 +344,17 @@ mod tests {
288344
assert_eq!(StatusCode::NoContent.raw(), b"204");
289345
assert_eq!(StatusCode::BadRequest.raw(), b"400");
290346
assert_eq!(StatusCode::NotFound.raw(), b"404");
347+
assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405");
291348
assert_eq!(StatusCode::InternalServerError.raw(), b"500");
292349
assert_eq!(StatusCode::NotImplemented.raw(), b"501");
293350
assert_eq!(StatusCode::ServiceUnavailable.raw(), b"503");
294351
}
352+
353+
#[test]
354+
fn test_allow_method() {
355+
let mut response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
356+
response.allow_method(Method::Get);
357+
response.allow_method(Method::Put);
358+
assert_eq!(response.allow(), vec![Method::Get, Method::Put]);
359+
}
295360
}

0 commit comments

Comments
 (0)