Files
micro-http/src/response.rs
Adrian Catangiu c2240b319d use raw_fd since owned object would close fd on drop
Signed-off-by: Adrian Catangiu <acatan@amazon.com>
2021-09-28 14:15:41 +03:00

418 lines
13 KiB
Rust

// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::io::{Error as WriteError, Write};
use std::os::unix::io::RawFd;
use crate::ascii::{COLON, CR, LF, SP};
use crate::common::{Body, Version};
use crate::headers::{Header, MediaType};
use crate::Method;
/// Wrapper over a response status code.
///
/// The status code is defined as specified in the
/// [RFC](https://tools.ietf.org/html/rfc7231#section-6).
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StatusCode {
/// 100, Continue
Continue,
/// 200, OK
OK,
/// 204, No Content
NoContent,
/// 400, Bad Request
BadRequest,
/// 401, Unauthorized
Unauthorized,
/// 404, Not Found
NotFound,
/// 405, Method Not Allowed
MethodNotAllowed,
/// 413, Payload Too Large
PayloadTooLarge,
/// 500, Internal Server Error
InternalServerError,
/// 501, Not Implemented
NotImplemented,
/// 503, Service Unavailable
ServiceUnavailable,
}
impl StatusCode {
/// Returns the status code as bytes.
pub fn raw(self) -> &'static [u8; 3] {
match self {
Self::Continue => b"100",
Self::OK => b"200",
Self::NoContent => b"204",
Self::BadRequest => b"400",
Self::Unauthorized => b"401",
Self::NotFound => b"404",
Self::MethodNotAllowed => b"405",
Self::PayloadTooLarge => b"413",
Self::InternalServerError => b"500",
Self::NotImplemented => b"501",
Self::ServiceUnavailable => b"503",
}
}
}
#[derive(Debug, PartialEq)]
struct StatusLine {
http_version: Version,
status_code: StatusCode,
}
impl StatusLine {
fn new(http_version: Version, status_code: StatusCode) -> Self {
Self {
http_version,
status_code,
}
}
fn write_all<T: Write>(&self, mut buf: T) -> Result<(), WriteError> {
buf.write_all(self.http_version.raw())?;
buf.write_all(&[SP])?;
buf.write_all(self.status_code.raw())?;
buf.write_all(&[SP, CR, LF])?;
Ok(())
}
}
/// Wrapper over the list of headers associated with a HTTP Response.
/// When creating a ResponseHeaders object, the content type is initialized to `text/plain`.
/// The content type can be updated with a call to `set_content_type`.
#[derive(Debug, PartialEq)]
pub struct ResponseHeaders {
content_length: i32,
content_type: MediaType,
server: String,
allow: Vec<Method>,
accept_encoding: bool,
}
impl Default for ResponseHeaders {
fn default() -> Self {
Self {
content_length: Default::default(),
content_type: Default::default(),
server: String::from("Firecracker API"),
allow: Vec::new(),
accept_encoding: false,
}
}
}
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())?;
// We check above that `self.allow` is not empty.
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())?;
buf.write_all(&[COLON, SP])?;
buf.write_all(self.server.as_bytes())?;
buf.write_all(&[CR, LF])?;
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])?;
buf.write_all(self.content_type.as_str().as_bytes())?;
buf.write_all(&[CR, LF])?;
buf.write_all(Header::ContentLength.raw())?;
buf.write_all(&[COLON, SP])?;
buf.write_all(self.content_length.to_string().as_bytes())?;
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])
}
// Sets the content length to be written in the HTTP response.
fn set_content_length(&mut self, content_length: i32) {
self.content_length = content_length;
}
/// Sets the HTTP response header server.
pub fn set_server(&mut self, server: &str) {
self.server = String::from(server);
}
/// Sets the content type to be written in the HTTP response.
pub fn set_content_type(&mut self, content_type: MediaType) {
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.
///
/// The Response is created using a `Version` and a `StatusCode`. When creating a Response object,
/// the body is initialized to `None` and the header is initialized with the `default` value. The body
/// can be updated with a call to `set_body`. The header can be updated with `set_content_type` and
/// `set_server`.
#[derive(Debug)]
pub struct Response {
status_line: StatusLine,
headers: ResponseHeaders,
body: Option<Body>,
/// The optional file descriptor associated with the response.
pub file: Option<RawFd>,
}
impl PartialEq for Response {
fn eq(&self, other: &Self) -> bool {
self.status_line == other.status_line
&& self.headers == other.headers
&& self.body == other.body
}
}
impl Response {
/// Creates a new HTTP `Response` with an empty body.
pub fn new(http_version: Version, status_code: StatusCode) -> Self {
Self {
status_line: StatusLine::new(http_version, status_code),
headers: ResponseHeaders::default(),
body: Default::default(),
file: None,
}
}
/// Updates the body of the `Response`.
///
/// This function has side effects because it also updates the headers:
/// - `ContentLength`: this is set to the length of the specified body.
pub fn set_body(&mut self, body: Body) {
self.headers.set_content_length(body.len() as i32);
self.body = Some(body);
}
/// Updates the content type of the `Response`.
pub fn set_content_type(&mut self, content_type: MediaType) {
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.
pub fn set_server(&mut self, server: &str) {
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())?;
}
Ok(())
}
/// Writes the content of the `Response` to the specified `buf`.
///
/// # Errors
/// Returns an error when the buffer is not large enough.
pub fn write_all<T: Write>(&self, mut buf: &mut T) -> Result<(), WriteError> {
self.status_line.write_all(&mut buf)?;
self.headers.write_all(&mut buf)?;
self.write_body(&mut buf)?;
Ok(())
}
/// Returns the Status Code of the Response.
pub fn status(&self) -> StatusCode {
self.status_line.status_code
}
/// Returns the Body of the response. If the response does not have a body,
/// it returns None.
pub fn body(&self) -> Option<Body> {
self.body.clone()
}
/// Returns the Content Length of the response.
pub fn content_length(&self) -> i32 {
self.headers.content_length
}
/// Returns the Content Type of the response.
pub fn content_type(&self) -> MediaType {
self.headers.content_type
}
/// Returns the HTTP Version of the 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)]
mod tests {
use super::*;
#[test]
fn test_write_response() {
let mut response = Response::new(Version::Http10, StatusCode::OK);
let body = "This is a test";
response.set_body(Body::new(body));
response.set_content_type(MediaType::PlainText);
response.set_encoding();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().unwrap(), Body::new(body));
assert_eq!(response.http_version(), Version::Http10);
assert_eq!(response.content_length(), 14);
assert_eq!(response.content_type(), MediaType::PlainText);
let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\
Server: Firecracker API\r\n\
Connection: keep-alive\r\n\
Content-Type: text/plain\r\n\
Content-Length: 14\r\n\
Accept-Encoding: identity\r\n\r\n\
This is a test";
let mut response_buf: [u8; 153] = [0; 153];
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
assert_eq!(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());
}
#[test]
fn test_set_server() {
let mut response = Response::new(Version::Http10, StatusCode::OK);
let body = "This is a test";
let server = "rust-vmm API";
response.set_body(Body::new(body));
response.set_content_type(MediaType::PlainText);
response.set_server(server);
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.body().unwrap(), Body::new(body));
assert_eq!(response.http_version(), Version::Http10);
assert_eq!(response.content_length(), 14);
assert_eq!(response.content_type(), MediaType::PlainText);
let expected_response = format!(
"HTTP/1.0 200 \r\n\
Server: {}\r\n\
Connection: keep-alive\r\n\
Content-Type: text/plain\r\n\
Content-Length: 14\r\n\r\n\
This is a test",
server
);
let mut response_buf: [u8; 123] = [0; 123];
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
assert!(response_buf.as_ref() == expected_response.as_bytes());
}
#[test]
fn test_status_code() {
assert_eq!(StatusCode::Continue.raw(), b"100");
assert_eq!(StatusCode::OK.raw(), b"200");
assert_eq!(StatusCode::NoContent.raw(), b"204");
assert_eq!(StatusCode::BadRequest.raw(), b"400");
assert_eq!(StatusCode::Unauthorized.raw(), b"401");
assert_eq!(StatusCode::NotFound.raw(), b"404");
assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405");
assert_eq!(StatusCode::PayloadTooLarge.raw(), b"413");
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]);
}
#[test]
fn test_equal() {
let response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
let another_response = Response::new(Version::Http10, StatusCode::MethodNotAllowed);
assert_eq!(response, another_response);
let response = Response::new(Version::Http10, StatusCode::OK);
let another_response = Response::new(Version::Http10, StatusCode::BadRequest);
assert_ne!(response, another_response);
}
}