initial commit

Pulled the code from firecracker-microvm/src/micro-http.

Signed-off-by: George Pisaltu <gpl@amazon.com>
Signed-off-by: Andreea Florescu <fandree@amazon.com>
This commit is contained in:
Andreea Florescu
2020-01-27 12:28:50 +02:00
commit 6708271862
9 changed files with 3295 additions and 0 deletions
+355
View File
@@ -0,0 +1,355 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::fmt::{Display, Error, Formatter};
pub mod headers;
pub mod ascii {
pub const CR: u8 = b'\r';
pub const COLON: u8 = b':';
pub const LF: u8 = b'\n';
pub const SP: u8 = b' ';
pub const CRLF_LEN: usize = 2;
}
/// Errors associated with parsing the HTTP Request from a u8 slice.
#[derive(Debug, PartialEq)]
pub enum RequestError {
/// The HTTP Method is not supported or it is invalid.
InvalidHttpMethod(&'static str),
/// Request URI is invalid.
InvalidUri(&'static str),
/// The HTTP Version in the Request is not supported or it is invalid.
InvalidHttpVersion(&'static str),
/// The header specified may be valid, but is not supported by this HTTP implementation.
UnsupportedHeader,
/// Header specified is invalid.
InvalidHeader,
/// The Request is invalid and cannot be served.
InvalidRequest,
}
impl Display for RequestError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
RequestError::InvalidHttpMethod(inner) => write!(f, "Invalid HTTP Method: {}", inner),
RequestError::InvalidUri(inner) => write!(f, "Invalid URI: {}", inner),
RequestError::InvalidHttpVersion(inner) => write!(f, "Invalid HTTP Version: {}", inner),
RequestError::UnsupportedHeader => write!(f, "Unsupported header."),
RequestError::InvalidHeader => write!(f, "Invalid header."),
RequestError::InvalidRequest => write!(f, "Invalid request."),
}
}
}
/// Errors associated with a HTTP Connection.
#[derive(Debug)]
pub enum ConnectionError {
/// The request parsing has failed.
ParseError(RequestError),
/// Could not perform a stream operation successfully.
StreamError(std::io::Error),
/// Attempted to read or write on a closed connection.
ConnectionClosed,
/// Attempted to write on a stream when there was nothing to write.
InvalidWrite,
}
impl Display for ConnectionError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
ConnectionError::ParseError(inner) => write!(f, "Parsing error: {}", inner),
ConnectionError::StreamError(inner) => write!(f, "Stream error: {}", inner),
ConnectionError::ConnectionClosed => write!(f, "Connection closed."),
ConnectionError::InvalidWrite => write!(f, "Invalid write attempt."),
}
}
}
/// Errors pertaining to `HttpServer`.
#[derive(Debug)]
pub enum ServerError {
/// Epoll operations failed.
IOError(std::io::Error),
/// Error from one of the connections.
ConnectionError(ConnectionError),
/// Server maximum capacity has been reached.
ServerFull,
}
impl Display for ServerError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
ServerError::IOError(inner) => write!(f, "IO error: {}", inner),
ServerError::ConnectionError(inner) => write!(f, "Connection error: {}", inner),
ServerError::ServerFull => write!(f, "Server is full."),
}
}
}
/// The Body associated with an HTTP Request or Response.
///
/// ## Examples
/// ```
/// extern crate micro_http;
/// use micro_http::Body;
/// let body = Body::new("This is a test body.".to_string());
/// assert_eq!(body.raw(), b"This is a test body.");
/// assert_eq!(body.len(), 20);
/// ```
#[derive(Clone, Debug, PartialEq)]
pub struct Body {
/// Body of the HTTP message as bytes.
pub body: Vec<u8>,
}
impl Body {
/// Creates a new `Body` from a `String` input.
pub fn new<T: Into<Vec<u8>>>(body: T) -> Self {
Body { body: body.into() }
}
/// Returns the body as an `u8 slice`.
pub fn raw(&self) -> &[u8] {
self.body.as_slice()
}
/// Returns the length of the `Body`.
pub fn len(&self) -> usize {
self.body.len()
}
/// Checks if the body is empty, ie with zero length
pub fn is_empty(&self) -> bool {
self.body.len() == 0
}
}
/// Supported HTTP Methods.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Method {
/// GET Method.
Get,
/// PUT Method.
Put,
/// PATCH Method.
Patch,
}
impl Method {
/// Returns a `Method` object if the parsing of `bytes` is successful.
///
/// The method is case sensitive. A call to try_from with the input b"get" will return
/// an error, but when using the input b"GET", it returns Method::Get.
///
/// # Errors
/// Returns `RequestError` if the method specified by `bytes` is unsupported.
pub fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
match bytes {
b"GET" => Ok(Method::Get),
b"PUT" => Ok(Method::Put),
b"PATCH" => Ok(Method::Patch),
_ => Err(RequestError::InvalidHttpMethod("Unsupported HTTP method.")),
}
}
/// Returns an `u8 slice` corresponding to the Method.
pub fn raw(self) -> &'static [u8] {
match self {
Method::Get => b"GET",
Method::Put => b"PUT",
Method::Patch => b"PATCH",
}
}
}
/// Supported HTTP Versions.
///
/// # Examples
/// ```
/// extern crate micro_http;
/// use micro_http::Version;
/// let version = Version::try_from(b"HTTP/1.1");
/// assert!(version.is_ok());
///
/// let version = Version::try_from(b"http/1.1");
/// assert!(version.is_err());
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Version {
/// HTTP/1.0
Http10,
/// HTTP/1.1
Http11,
}
impl Version {
/// HTTP Version as an `u8 slice`.
pub fn raw(self) -> &'static [u8] {
match self {
Version::Http10 => b"HTTP/1.0",
Version::Http11 => b"HTTP/1.1",
}
}
/// Creates a new HTTP Version from an `u8 slice`.
///
/// The supported versions are HTTP/1.0 and HTTP/1.1.
/// The version is case sensitive and the accepted input is upper case.
///
/// # Errors
/// Returns a `RequestError` when the version is not supported.
pub fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
match bytes {
b"HTTP/1.0" => Ok(Version::Http10),
b"HTTP/1.1" => Ok(Version::Http11),
_ => Err(RequestError::InvalidHttpVersion(
"Unsupported HTTP version.",
)),
}
}
/// Returns the default HTTP version = HTTP/1.1.
pub fn default() -> Self {
Version::Http11
}
}
#[cfg(test)]
mod tests {
use super::*;
impl PartialEq for ConnectionError {
fn eq(&self, other: &Self) -> bool {
use self::ConnectionError::*;
match (self, other) {
(ParseError(_), ParseError(_)) => true,
(ConnectionClosed, ConnectionClosed) => true,
(StreamError(_), StreamError(_)) => true,
(InvalidWrite, InvalidWrite) => true,
_ => false,
}
}
}
#[test]
fn test_version() {
// Tests for raw()
assert_eq!(Version::Http10.raw(), b"HTTP/1.0");
assert_eq!(Version::Http11.raw(), b"HTTP/1.1");
// Tests for try_from()
assert_eq!(Version::try_from(b"HTTP/1.0").unwrap(), Version::Http10);
assert_eq!(Version::try_from(b"HTTP/1.1").unwrap(), Version::Http11);
assert_eq!(
Version::try_from(b"HTTP/2.0").unwrap_err(),
RequestError::InvalidHttpVersion("Unsupported HTTP version.")
);
// Test for default()
assert_eq!(Version::default(), Version::Http11);
}
#[test]
fn test_method() {
// Test for raw
assert_eq!(Method::Get.raw(), b"GET");
assert_eq!(Method::Put.raw(), b"PUT");
assert_eq!(Method::Patch.raw(), b"PATCH");
// Tests for try_from
assert_eq!(Method::try_from(b"GET").unwrap(), Method::Get);
assert_eq!(Method::try_from(b"PUT").unwrap(), Method::Put);
assert_eq!(Method::try_from(b"PATCH").unwrap(), Method::Patch);
assert_eq!(
Method::try_from(b"POST").unwrap_err(),
RequestError::InvalidHttpMethod("Unsupported HTTP method.")
);
}
#[test]
fn test_body() {
let body = Body::new("".to_string());
// Test for is_empty
assert!(body.is_empty());
let body = Body::new("This is a body.".to_string());
// Test for len
assert_eq!(body.len(), 15);
// Test for raw
assert_eq!(body.raw(), b"This is a body.");
}
#[test]
fn test_display_request_error() {
assert_eq!(
format!("{}", RequestError::InvalidHttpMethod("test")),
"Invalid HTTP Method: test"
);
assert_eq!(
format!("{}", RequestError::InvalidUri("test")),
"Invalid URI: test"
);
assert_eq!(
format!("{}", RequestError::InvalidHttpVersion("test")),
"Invalid HTTP Version: test"
);
assert_eq!(
format!("{}", RequestError::InvalidHeader),
"Invalid header."
);
assert_eq!(
format!("{}", RequestError::UnsupportedHeader),
"Unsupported header."
);
assert_eq!(
format!("{}", RequestError::InvalidRequest),
"Invalid request."
);
}
#[test]
fn test_display_connection_error() {
assert_eq!(
format!(
"{}",
ConnectionError::ParseError(RequestError::InvalidRequest)
),
"Parsing error: Invalid request."
);
assert_eq!(
format!(
"{}",
ConnectionError::StreamError(std::io::Error::from_raw_os_error(11))
),
"Stream error: Resource temporarily unavailable (os error 11)"
);
assert_eq!(
format!("{}", ConnectionError::ConnectionClosed),
"Connection closed."
);
assert_eq!(
format!("{}", ConnectionError::InvalidWrite),
"Invalid write attempt."
);
}
#[test]
fn test_display_server_error() {
assert_eq!(
format!(
"{}",
ServerError::ConnectionError(ConnectionError::ConnectionClosed)
),
"Connection error: Connection closed."
);
assert_eq!(format!("{}", ServerError::ServerFull), "Server is full.");
assert_eq!(
format!(
"{}",
ServerError::IOError(std::io::Error::from_raw_os_error(11))
),
"IO error: Resource temporarily unavailable (os error 11)"
);
}
}