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:
@@ -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)"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user