micro_http: Import Firecracker HTTP 1.x implementation

Based on Firecracker commit 58edf03b.

We're going to use the micro_http crate to serve the cloud-hypervisor
HTTP API.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Samuel Ortiz 2019-09-18 10:36:54 +02:00
parent 8916dad2da
commit e50f4418a2
7 changed files with 2185 additions and 0 deletions

6
micro_http/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "micro_http"
version = "0.1.0"
authors = ["Amazon Firecracker team <firecracker-devel@amazon.com>"]
[dependencies]

View File

@ -0,0 +1,392 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::result::Result;
use RequestError;
/// Wrapper over an HTTP Header type.
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum Header {
/// Header `Content-Length`.
ContentLength,
/// Header `Content-Type`.
ContentType,
/// Header `Expect`.
Expect,
/// Header `Transfer-Encoding`.
TransferEncoding,
}
impl Header {
pub fn raw(&self) -> &'static [u8] {
match self {
Header::ContentLength => b"Content-Length",
Header::ContentType => b"Content-Type",
Header::Expect => b"Expect",
Header::TransferEncoding => b"Transfer-Encoding",
}
}
fn try_from(string: &[u8]) -> Result<Self, RequestError> {
if let Ok(utf8_string) = String::from_utf8(string.to_vec()) {
match utf8_string.trim() {
"Content-Length" => Ok(Header::ContentLength),
"Content-Type" => Ok(Header::ContentType),
"Expect" => Ok(Header::Expect),
"Transfer-Encoding" => Ok(Header::TransferEncoding),
_ => Err(RequestError::InvalidHeader),
}
} else {
Err(RequestError::InvalidRequest)
}
}
}
/// Wrapper over the list of headers associated with a Request that we need
/// in order to parse the request correctly and be able to respond to it.
///
/// The only `Content-Type`s supported are `text/plain` and `application/json`, which are both
/// in plain text actually and don't influence our parsing process.
///
/// All the other possible header fields are not necessary in order to serve this connection
/// and, thus, are not of interest to us. However, we still look for header fields that might
/// invalidate our request as we don't support the full set of HTTP/1.1 specification.
/// Such header entries are "Transfer-Encoding: identity; q=0", which means a compression
/// algorithm is applied to the body of the request, or "Expect: 103-checkpoint".
#[derive(Debug)]
pub struct Headers {
/// The `Content-Length` header field tells us how many bytes we need to receive
/// from the source after the headers.
content_length: i32,
/// The `Expect` header field is set when the headers contain the entry "Expect: 100-continue".
/// This means that, per HTTP/1.1 specifications, we must send a response with the status code
/// 100 after we have received the headers in order to receive the body of the request. This
/// field should be known immediately after parsing the headers.
expect: bool,
/// `Chunked` is a possible value of the `Transfer-Encoding` header field and every HTTP/1.1
/// server must support it. It is useful only when receiving the body of the request and should
/// be known immediately after parsing the headers.
chunked: bool,
}
impl Headers {
/// By default Requests are created with no headers.
pub fn default() -> Headers {
Headers {
content_length: 0,
expect: false,
chunked: false,
}
}
/// Expects one header line and parses it, updating the header structure or returning an
/// error if the header is invalid.
///
/// # Errors
/// `UnsupportedHeader` is returned when the parsed header line is not of interest
/// to us or when it is unrecognizable.
/// `InvalidHeader` is returned when the parsed header is formatted incorrectly or suggests
/// that the client is using HTTP features that we do not support in this implementation,
/// which invalidates the request.
pub fn parse_header_line(&mut self, header_line: &[u8]) -> Result<(), RequestError> {
// Headers must be ASCII, so also UTF-8 valid.
match std::str::from_utf8(header_line) {
Ok(headers_str) => {
let entry = headers_str.split(": ").collect::<Vec<&str>>();
if entry.len() != 2 {
return Err(RequestError::InvalidHeader);
}
if let Ok(head) = Header::try_from(entry[0].as_bytes()) {
match head {
Header::ContentLength => {
let try_numeric: Result<i32, std::num::ParseIntError> =
std::str::FromStr::from_str(entry[1].trim());
if try_numeric.is_ok() {
self.content_length = try_numeric.unwrap();
Ok(())
} else {
Err(RequestError::InvalidHeader)
}
}
Header::ContentType => {
match MediaType::try_from(entry[1].trim().as_bytes()) {
Ok(_) => Ok(()),
Err(_) => Err(RequestError::InvalidHeader),
}
}
Header::TransferEncoding => match entry[1].trim() {
"chunked" => {
self.chunked = true;
Ok(())
}
"identity; q=0" => Err(RequestError::InvalidHeader),
_ => Err(RequestError::UnsupportedHeader),
},
Header::Expect => match entry[1].trim() {
"100-continue" => {
self.expect = true;
Ok(())
}
_ => Err(RequestError::InvalidHeader),
},
}
} else {
Err(RequestError::UnsupportedHeader)
}
}
_ => Err(RequestError::InvalidHeader),
}
}
/// Returns the content length of the body.
pub fn content_length(&self) -> i32 {
self.content_length
}
/// Returns `true` if the transfer encoding is chunked.
#[allow(unused)]
pub fn chunked(&self) -> bool {
self.chunked
}
/// Returns `true` if the client is expecting the code 100.
#[allow(unused)]
pub fn expect(&self) -> bool {
self.expect
}
#[cfg(test)]
pub fn new(content_length: i32, expect: bool, chunked: bool) -> Self {
Headers {
content_length,
expect,
chunked,
}
}
/// Parses a byte slice into a Headers structure for a HTTP request.
///
/// The byte slice is expected to have the following format: </br>
/// * Request Header Lines "<header_line> CRLF"- Optional </br>
/// There can be any number of request headers, including none, followed by
/// an extra sequence of Carriage Return and Line Feed.
/// All header fields are parsed. However, only the ones present in the
/// [`Headers`](struct.Headers.html) struct are relevant to us and stored
/// for future use.
///
/// # Errors
/// The function returns `InvalidHeader` when parsing the byte stream fails.
///
/// # Examples
///
/// ```
/// extern crate micro_http;
/// use micro_http::Headers;
///
/// let request_headers = Headers::try_from(b"Content-Length: 55\r\n\r\n");
/// ```
pub fn try_from(bytes: &[u8]) -> Result<Headers, RequestError> {
// Headers must be ASCII, so also UTF-8 valid.
if let Ok(text) = std::str::from_utf8(bytes) {
let mut headers = Headers::default();
let header_lines = text.split("\r\n");
for header_line in header_lines {
if header_line.is_empty() {
break;
}
match headers.parse_header_line(header_line.as_bytes()) {
Ok(_) | Err(RequestError::UnsupportedHeader) => continue,
Err(e) => return Err(e),
};
}
return Ok(headers);
}
Err(RequestError::InvalidRequest)
}
}
/// Wrapper over supported Media Types.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MediaType {
/// Media Type: "text/plain".
PlainText,
/// Media Type: "application/json".
ApplicationJson,
}
impl Default for MediaType {
fn default() -> Self {
MediaType::PlainText
}
}
impl MediaType {
fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
if bytes.is_empty() {
return Err(RequestError::InvalidRequest);
}
let utf8_slice =
String::from_utf8(bytes.to_vec()).map_err(|_| RequestError::InvalidRequest)?;
match utf8_slice.as_str() {
"text/plain" => Ok(MediaType::PlainText),
"application/json" => Ok(MediaType::ApplicationJson),
_ => Err(RequestError::InvalidRequest),
}
}
/// Returns a static string representation of the object.
pub fn as_str(self) -> &'static str {
match self {
MediaType::PlainText => "text/plain",
MediaType::ApplicationJson => "application/json",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default() {
let headers = Headers::default();
assert_eq!(headers.content_length(), 0);
assert_eq!(headers.chunked(), false);
assert_eq!(headers.expect(), false);
}
#[test]
fn test_try_from_media() {
assert_eq!(
MediaType::try_from(b"application/json").unwrap(),
MediaType::ApplicationJson
);
assert_eq!(
MediaType::try_from(b"text/plain").unwrap(),
MediaType::PlainText
);
assert_eq!(
MediaType::try_from(b"").unwrap_err(),
RequestError::InvalidRequest
);
assert_eq!(
MediaType::try_from(b"application/json-patch").unwrap_err(),
RequestError::InvalidRequest
);
}
#[test]
fn test_media_as_str() {
let media_type = MediaType::ApplicationJson;
assert_eq!(media_type.as_str(), "application/json");
let media_type = MediaType::PlainText;
assert_eq!(media_type.as_str(), "text/plain");
}
#[test]
fn test_try_from_headers() {
// Valid headers.
assert_eq!(
Headers::try_from(
b"Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\nContent-Length: 55\r\n\r\n"
)
.unwrap()
.content_length,
55
);
let bytes: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
// Invalid headers.
assert!(Headers::try_from(&bytes[..]).is_err());
}
#[test]
fn test_parse_header_line() {
let mut header = Headers::default();
// Invalid header syntax.
assert_eq!(
header.parse_header_line(b"Expect"),
Err(RequestError::InvalidHeader)
);
// Invalid content length.
assert_eq!(
header.parse_header_line(b"Content-Length: five"),
Err(RequestError::InvalidHeader)
);
// Invalid transfer encoding.
assert_eq!(
header.parse_header_line(b"Transfer-Encoding: gzip"),
Err(RequestError::UnsupportedHeader)
);
// Invalid expect.
assert_eq!(
header
.parse_header_line(b"Expect: 102-processing")
.unwrap_err(),
RequestError::InvalidHeader
);
// Invalid media type.
assert_eq!(
header
.parse_header_line(b"Content-Type: application/json-patch")
.unwrap_err(),
RequestError::InvalidHeader
);
// Invalid input format.
let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
assert_eq!(
header.parse_header_line(&input[..]).unwrap_err(),
RequestError::InvalidHeader
);
// Test valid transfer encoding.
assert!(header
.parse_header_line(b"Transfer-Encoding: chunked")
.is_ok());
assert!(header.chunked());
// Test valid expect.
assert!(header.parse_header_line(b"Expect: 100-continue").is_ok());
assert!(header.expect());
// Test valid media type.
assert!(header
.parse_header_line(b"Content-Type: application/json")
.is_ok());
}
#[test]
fn test_header_try_from() {
// Bad header.
assert_eq!(
Header::try_from(b"Encoding").unwrap_err(),
RequestError::InvalidHeader
);
// Invalid encoding.
let input: [u8; 10] = [130, 140, 150, 130, 140, 150, 130, 140, 150, 160];
assert_eq!(
Header::try_from(&input[..]).unwrap_err(),
RequestError::InvalidRequest
);
// Test valid headers.
let header = Header::try_from(b"Expect").unwrap();
assert_eq!(header.raw(), b"Expect");
let header = Header::try_from(b"Transfer-Encoding").unwrap();
assert_eq!(header.raw(), b"Transfer-Encoding");
}
}

View File

@ -0,0 +1,236 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
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,
}
/// 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,
}
/// 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.");
}
}

View File

@ -0,0 +1,760 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::collections::VecDeque;
use std::io::{Read, Write};
use common::ascii::{CR, CRLF_LEN, LF};
use common::Body;
pub use common::{ConnectionError, RequestError};
use headers::Headers;
use request::{find, Request, RequestLine};
use response::{Response, StatusCode};
const BUFFER_SIZE: usize = 1024;
/// Describes the state machine of an HTTP connection.
pub enum ConnectionState {
WaitingForRequestLine,
WaitingForHeaders,
WaitingForBody,
RequestReady,
}
/// A wrapper over a HTTP Connection.
pub struct HttpConnection<T> {
/// A partial request that is still being received.
pending_request: Option<Request>,
/// Stream implementing `Read` and `Write`, capable of sending and
/// receiving bytes.
stream: T,
/// The state of the connection regarding the current request that
/// is being processed.
state: ConnectionState,
/// Buffer where we store the bytes we read from the stream.
buffer: [u8; BUFFER_SIZE],
/// The index in the buffer from where we have to start reading in
/// the next `try_read` call.
read_cursor: usize,
/// Contains all bytes pertaining to the body of the request that
/// is currently being processed.
body_vec: Vec<u8>,
/// Represents how many bytes from the body of the request are still
/// to be read.
body_bytes_to_be_read: i32,
/// A queue of all requests that have been fully received and parsed.
parsed_requests: VecDeque<Request>,
/// A queue of requests that are waiting to be sent.
response_queue: VecDeque<Response>,
/// A buffer containing the bytes of a response that is currently
/// being sent.
response_buffer: Option<Vec<u8>>,
}
impl<T: Read + Write> HttpConnection<T> {
/// Creates an empty connection.
pub fn new(stream: T) -> Self {
HttpConnection {
pending_request: None,
stream,
state: ConnectionState::WaitingForRequestLine,
buffer: [0; BUFFER_SIZE],
read_cursor: 0,
body_vec: vec![],
body_bytes_to_be_read: 0,
parsed_requests: VecDeque::new(),
response_queue: VecDeque::new(),
response_buffer: None,
}
}
/// Tries to read new bytes from the stream and automatically update the request.
/// Meant to be used only with non-blocking streams and an `EPOLL` structure.
/// Should be called whenever an `EPOLLIN` event is signaled.
pub fn try_read(&mut self) -> Result<(), ConnectionError> {
// Read some bytes from the stream, which will be appended to what is already
// present in the buffer from a previous call of `try_read`. There are already
// `read_cursor` bytes present in the buffer.
let end_cursor = self.read_bytes()?;
let mut line_start_index = 0;
loop {
match self.state {
ConnectionState::WaitingForRequestLine => {
if !self.parse_request_line(&mut line_start_index, end_cursor)? {
return Ok(());
}
}
ConnectionState::WaitingForHeaders => {
if !self.parse_headers(&mut line_start_index, end_cursor)? {
return Ok(());
}
}
ConnectionState::WaitingForBody => {
if !self.parse_body(&mut line_start_index, end_cursor)? {
return Ok(());
}
}
ConnectionState::RequestReady => {
// This request is ready to be passed for handling.
// Update the state machine to expect a new request and push this request into
// the `parsed_requests` queue.
self.state = ConnectionState::WaitingForRequestLine;
self.body_bytes_to_be_read = 0;
self.parsed_requests
.push_back(self.pending_request.take().unwrap());
}
};
}
}
// Reads a maximum of 1024 bytes from the stream into `buffer`.
// The return value represents the end index of what we have just appended.
fn read_bytes(&mut self) -> Result<usize, ConnectionError> {
// Append new bytes to what we already have in the buffer.
let bytes_read = self
.stream
.read(&mut self.buffer[self.read_cursor..])
.map_err(ConnectionError::StreamError)?;
// If the read returned 0 then the client has closed the connection.
if bytes_read == 0 {
return Err(ConnectionError::ConnectionClosed);
}
Ok(bytes_read + self.read_cursor)
}
// Parses bytes in `buffer` for a valid request line.
// Returns `false` if there are no more bytes to be parsed in the buffer.
fn parse_request_line(
&mut self,
start: &mut usize,
end: usize,
) -> Result<bool, ConnectionError> {
match find(&self.buffer[*start..end], &[CR, LF]) {
Some(line_end_index) => {
let line = &self.buffer[*start..(*start + line_end_index)];
*start = *start + line_end_index + CRLF_LEN;
let request_line =
RequestLine::try_from(line).map_err(ConnectionError::ParseError)?;
// Form the request with a valid request line, which is the bare minimum
// for a valid request.
self.pending_request = Some(Request {
request_line,
headers: Headers::default(),
body: None,
});
self.state = ConnectionState::WaitingForHeaders;
Ok(true)
}
None => {
// The request line is longer than BUFFER_SIZE bytes, so the request is invalid.
if end == BUFFER_SIZE && *start == 0 {
return Err(ConnectionError::ParseError(RequestError::InvalidRequest));
} else {
// Move the incomplete request line to the beginning of the buffer and wait
// for the next `try_read` call to complete it.
// This can only happen if another request was sent before this one, as the
// limit for the length of a request line in this implementation is 1024 bytes.
self.shift_buffer_left(*start, end);
}
Ok(false)
}
}
}
// Parses bytes in `buffer` for header fields.
// Returns `false` if there are no more bytes to be parsed in the buffer.
fn parse_headers(
&mut self,
line_start_index: &mut usize,
end_cursor: usize,
) -> Result<bool, ConnectionError> {
match find(&self.buffer[*line_start_index..end_cursor], &[CR, LF]) {
// We have found the end of the headers.
// `line_start_index` points to the end of the most recently found CR LF
// sequence. That means that if we found the next CR LF sequence at this index,
// they are, in fact, a CR LF CR LF sequence, which marks the end of the header
// fields, per HTTP specification.
Some(0) => {
// If our current state is `WaitingForHeaders`, it means that we already have
// a valid request formed from a request line, so it's safe to unwrap.
let request = self.pending_request.as_mut().unwrap();
if request.headers.content_length() == 0 {
self.state = ConnectionState::RequestReady;
} else {
if request.headers.expect() {
// Send expect.
let expect_response =
Response::new(request.http_version(), StatusCode::Continue);
self.response_queue.push_back(expect_response);
}
self.body_bytes_to_be_read = request.headers.content_length();
request.body = Some(Body::new(vec![]));
self.state = ConnectionState::WaitingForBody;
}
// Update the index for the next header.
*line_start_index += CRLF_LEN;
Ok(true)
}
// We have found the end of a header line.
Some(relative_line_end_index) => {
let request = self.pending_request.as_mut().unwrap();
// The `line_end_index` relative to the whole buffer.
let line_end_index = relative_line_end_index + *line_start_index;
// Get the line slice and parse it.
let line = &self.buffer[*line_start_index..line_end_index];
match request.headers.parse_header_line(line) {
// If a header is unsupported we ignore it.
Ok(_) | Err(RequestError::UnsupportedHeader) => {}
// If parsing the header invalidates the request, we propagate
// the error.
Err(e) => return Err(ConnectionError::ParseError(e)),
};
// Update the `line_start_index` to where we finished parsing.
*line_start_index = line_end_index + CRLF_LEN;
Ok(true)
}
// If we have an incomplete header line.
None => {
// If we have parsed BUFFER_SIZE bytes and still haven't found the header
// line end sequence.
if *line_start_index == 0 && end_cursor == BUFFER_SIZE {
// Header line is longer than BUFFER_SIZE bytes, so it is invalid.
return Err(ConnectionError::ParseError(RequestError::InvalidHeader));
}
// Move the incomplete header line from the end of the buffer to
// the beginning, so that we can append the rest of the line and
// parse it in the next `try_read` call.
self.shift_buffer_left(*line_start_index, end_cursor);
Ok(false)
}
}
}
// Parses bytes in `buffer` to be put into the request body, if there should be one.
// Returns `false` if there are no more bytes to be parsed in the buffer.
fn parse_body(
&mut self,
line_start_index: &mut usize,
end_cursor: usize,
) -> Result<bool, ConnectionError> {
// If what we have just read is not enough to complete the request and
// there are more bytes pertaining to the body of the request.
if self.body_bytes_to_be_read > end_cursor as i32 - *line_start_index as i32 {
// Append everything that we read to our current incomplete body and update
// `body_bytes_to_be_read`.
self.body_vec
.extend_from_slice(&self.buffer[*line_start_index..end_cursor]);
self.body_bytes_to_be_read -= end_cursor as i32 - *line_start_index as i32;
// Clear the buffer and reset the starting index.
for i in 0..BUFFER_SIZE {
self.buffer[i] = 0;
}
self.read_cursor = 0;
return Ok(false);
}
// Append only the remaining necessary bytes to the body of the request.
self.body_vec.extend_from_slice(
&self.buffer
[*line_start_index..(*line_start_index + self.body_bytes_to_be_read as usize)],
);
*line_start_index += self.body_bytes_to_be_read as usize;
self.body_bytes_to_be_read = 0;
let request = self.pending_request.as_mut().unwrap();
// If there are no more bytes to be read for this request.
// Assign the body of the request.
let placeholder: Vec<_> = self
.body_vec
.drain(..request.headers.content_length() as usize)
.collect();
request.body = Some(Body::new(placeholder));
// If we read more bytes than we should have into the body of the request.
if !self.body_vec.is_empty() {
return Err(ConnectionError::ParseError(RequestError::InvalidRequest));
}
self.state = ConnectionState::RequestReady;
Ok(true)
}
/// Tries to write the first available response to the provided stream.
/// Meant to be used only with non-blocking streams and an `EPOLL` structure.
/// Should be called whenever an `EPOLLOUT` event is signaled.
pub fn try_write(&mut self) -> Result<(), ConnectionError> {
if self.response_buffer.is_none() {
if let Some(response) = self.response_queue.pop_front() {
let mut response_buffer_vec: Vec<u8> = Vec::new();
response
.write_all(&mut response_buffer_vec)
.map_err(ConnectionError::StreamError)?;
self.response_buffer = Some(response_buffer_vec);
} else {
return Err(ConnectionError::InvalidWrite);
}
}
let mut response_fully_written = false;
if let Some(response_buffer_vec) = self.response_buffer.as_mut() {
let bytes_to_be_written = response_buffer_vec.len();
match self.stream.write(response_buffer_vec.as_slice()) {
Ok(0) => {
return Err(ConnectionError::ConnectionClosed);
}
Ok(bytes_written) => {
if bytes_written != bytes_to_be_written {
response_buffer_vec.drain(..bytes_written);
} else {
response_fully_written = true;
}
}
Err(io_error) => {
return Err(ConnectionError::StreamError(io_error));
}
}
}
if response_fully_written {
self.response_buffer.take();
}
Ok(())
}
/// Send a response back to the source of a request.
pub fn enqueue_response(&mut self, response: Response) {
self.response_queue.push_back(response);
}
fn shift_buffer_left(&mut self, line_start_index: usize, end_cursor: usize) {
// We don't want to shift something that is already at the beginning.
if line_start_index != 0 {
// Move the bytes from `line_start_index` to the beginning of the buffer.
for cursor in 0..(end_cursor - line_start_index) {
self.buffer[cursor] = self.buffer[line_start_index + cursor];
}
// Clear the rest of the buffer.
for cursor in (end_cursor - line_start_index)..end_cursor {
self.buffer[cursor] = 0;
}
}
// Update `read_cursor`.
self.read_cursor = end_cursor - line_start_index;
}
/// Returns the first parsed request in the queue.
pub fn pop_parsed_request(&mut self) -> Option<Request> {
self.parsed_requests.pop_front()
}
}
#[cfg(test)]
mod tests {
use super::*;
use common::{Method, Version};
use std::os::unix::net::UnixStream;
#[test]
fn test_try_read_expect() {
// Test request with `Expect` header.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Content-Length: 26\r\n \
Transfer-Encoding: chunked\r\n\r\n",
)
.unwrap();
assert!(conn.try_read().is_ok());
sender.write_all(b"this is not\n\r\na json \nbody").unwrap();
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(26, true, true),
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_long_headers() {
// Long request headers.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: chunked\r\n",
)
.unwrap();
for i in 0..90 {
sender.write_all(b"Custom-Header-Testing: 1").unwrap();
sender.write_all(i.to_string().as_bytes()).unwrap();
sender.write_all(b"\r\n").unwrap();
}
sender
.write_all(b"Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody")
.unwrap();
assert!(conn.try_read().is_ok());
assert!(conn.try_read().is_ok());
assert!(conn.try_read().is_ok());
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(26, true, true),
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_split_ending() {
// Long request with '\r\n' on BUFFER_SIZEth and 1025th positions in the request.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: chunked\r\n",
)
.unwrap();
for i in 0..32 {
sender.write_all(b"Custom-Header-Testing: 1").unwrap();
sender.write_all(i.to_string().as_bytes()).unwrap();
sender.write_all(b"\r\n").unwrap();
}
sender
.write_all(b"Head: aaaaa\r\nContent-Length: 26\r\n\r\nthis is not\n\r\na json \nbody")
.unwrap();
assert!(conn.try_read().is_ok());
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(26, true, true),
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_invalid_request() {
// Invalid request.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: chunked\r\n",
)
.unwrap();
for i in 0..40 {
sender.write_all(b"Custom-Header-Testing: 1").unwrap();
sender.write_all(i.to_string().as_bytes()).unwrap();
sender.write_all(b"\r\n").unwrap();
}
sender
.write_all(b"Content-Length: alpha\r\n\r\nthis is not\n\r\na json \nbody")
.unwrap();
assert!(conn.try_read().is_ok());
let request_error = conn.try_read().unwrap_err();
assert_eq!(
request_error,
ConnectionError::ParseError(RequestError::InvalidHeader)
);
}
#[test]
fn test_try_read_long_request_body() {
// Long request body.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: chunked\r\n \
Content-Length: 1400\r\n\r\n",
)
.unwrap();
let mut request_body: Vec<u8> = Vec::with_capacity(1400);
for _ in 0..100 {
request_body.write_all(b"This is a test").unwrap();
}
sender.write_all(request_body.as_slice()).unwrap();
assert!(conn.try_read().is_ok());
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(1400, true, true),
body: Some(Body::new(request_body)),
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_large_req_line() {
// Request line longer than BUFFER_SIZE bytes.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender.write_all(b"PATCH http://localhost/home").unwrap();
let mut request_body: Vec<u8> = Vec::with_capacity(1400);
for _ in 0..200 {
request_body.write_all(b"/home").unwrap();
}
sender.write_all(request_body.as_slice()).unwrap();
assert_eq!(
conn.try_read().unwrap_err(),
ConnectionError::ParseError(RequestError::InvalidRequest)
);
}
#[test]
fn test_try_read_large_header_line() {
// Header line longer than BUFFER_SIZE bytes.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(b"PATCH http://localhost/home HTTP/1.1\r\nhead: ")
.unwrap();
let mut request_body: Vec<u8> = Vec::with_capacity(1030);
for _ in 0..86 {
request_body.write_all(b"abcdefghijkl").unwrap();
}
request_body.write_all(b"\r\n\r\n").unwrap();
sender.write_all(request_body.as_slice()).unwrap();
assert!(conn.try_read().is_ok());
assert_eq!(
conn.try_read().unwrap_err(),
ConnectionError::ParseError(RequestError::InvalidHeader)
);
}
#[test]
fn test_try_read_no_body_request() {
// Request without body.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: chunked\r\n\r\n",
)
.unwrap();
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(0, true, true),
body: None,
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_segmented_req_line() {
// Segmented request line.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender.write_all(b"PATCH http://local").unwrap();
assert!(conn.try_read().is_ok());
sender.write_all(b"host/home HTTP/1.1\r\n\r\n").unwrap();
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(0, false, false),
body: None,
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_long_req_line_b2b() {
// Long request line after another request.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
// Req line 23 + 10*x + 13 = 36 + 10* x 984 free in first try read
sender
.write_all(b"PATCH http://localhost/home HTTP/1.1\r\n\r\nPATCH http://localhost/")
.unwrap();
let mut request_line: Vec<u8> = Vec::with_capacity(980);
for _ in 0..98 {
request_line.write_all(b"localhost/").unwrap();
}
request_line.write_all(b" HTTP/1.1\r\n\r\n").unwrap();
sender.write_all(request_line.as_slice()).unwrap();
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let expected_request = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(0, false, false),
body: None,
};
assert_eq!(request, expected_request);
conn.try_read().unwrap();
let request = conn.pop_parsed_request().unwrap();
let mut expected_request_as_bytes = Vec::new();
expected_request_as_bytes
.write_all(b"http://localhost/")
.unwrap();
expected_request_as_bytes.append(request_line.as_mut());
let expected_request = Request {
request_line: RequestLine::new(
Method::Patch,
std::str::from_utf8(&expected_request_as_bytes[..997]).unwrap(),
Version::Http11,
),
headers: Headers::new(0, false, false),
body: None,
};
assert_eq!(request, expected_request);
}
#[test]
fn test_try_read_double_request() {
// Double request in a single read.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Transfer-Encoding: chunked\r\n \
Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody",
)
.unwrap();
sender
.write_all(
b"PUT http://farhost/away HTTP/1.1\r\nContent-Length: 23\r\n\r\nthis is another request",
)
.unwrap();
let expected_request_first = Request {
request_line: RequestLine::new(Method::Patch, "http://localhost/home", Version::Http11),
headers: Headers::new(26, false, true),
body: Some(Body::new(b"this is not\n\r\na json \nbody".to_vec())),
};
conn.try_read().unwrap();
let request_first = conn.pop_parsed_request().unwrap();
let request_second = conn.pop_parsed_request().unwrap();
let expected_request_second = Request {
request_line: RequestLine::new(Method::Put, "http://farhost/away", Version::Http11),
headers: Headers::new(23, false, false),
body: Some(Body::new(b"this is another request".to_vec())),
};
assert_eq!(request_first, expected_request_first);
assert_eq!(request_second, expected_request_second);
}
#[test]
fn test_try_read_connection_closed() {
// Connection abruptly closed.
let (mut sender, receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(receiver);
sender
.write_all(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Transfer-Encoding: chunked\r\n \
Content-Len",
)
.unwrap();
conn.try_read().unwrap();
sender.shutdown(std::net::Shutdown::Both).unwrap();
assert_eq!(
conn.try_read().unwrap_err(),
ConnectionError::ConnectionClosed
);
}
#[test]
fn test_enqueue_response() {
// Response without body.
let (sender, mut receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(sender);
let response = Response::new(Version::Http11, StatusCode::OK);
let mut expected_response: Vec<u8> = vec![];
response.write_all(&mut expected_response).unwrap();
conn.enqueue_response(response);
assert!(conn.try_write().is_ok());
let mut response_buffer = vec![0u8; expected_response.len()];
receiver.read_exact(&mut response_buffer).unwrap();
assert_eq!(response_buffer, expected_response);
// Response with body.
let (sender, mut receiver) = UnixStream::pair().unwrap();
receiver.set_nonblocking(true).expect("Can't modify socket");
let mut conn = HttpConnection::new(sender);
let mut response = Response::new(Version::Http11, StatusCode::OK);
let mut body: Vec<u8> = vec![];
body.write_all(br#"{ "json": "body", "hello": "world" }"#)
.unwrap();
response.set_body(Body::new(body));
let mut expected_response: Vec<u8> = vec![];
response.write_all(&mut expected_response).unwrap();
conn.enqueue_response(response);
assert!(conn.try_write().is_ok());
let mut response_buffer = vec![0u8; expected_response.len()];
receiver.read_exact(&mut response_buffer).unwrap();
assert_eq!(response_buffer, expected_response);
}
}

85
micro_http/src/lib.rs Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#![deny(missing_docs)]
//! Minimal implementation of the [HTTP/1.0](https://tools.ietf.org/html/rfc1945)
//! and [HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt) protocols.
//!
//! HTTP/1.1 has a mandatory header **Host**, but as this crate is only used
//! for parsing MMDS requests, this header (if present) is ignored.
//!
//! This HTTP implementation is stateless thus it does not support chunking or
//! compression.
//!
//! ## Supported Headers
//! The **micro_http** crate has support for parsing the following **Request**
//! headers:
//! - Content-Length
//! - Expect
//! - Transfer-Encoding
//!
//! The **Response** does not have a public interface for adding headers, but whenever
//! a write to the **Body** is made, the headers **ContentLength** and **MediaType**
//! are automatically updated.
//!
//! ### Media Types
//! The supported media types are:
//! - text/plain
//! - application/json
//!
//! ## Supported Methods
//! The supported HTTP Methods are:
//! - GET
//! - PUT
//! - PATCH
//!
//! ## Supported Status Codes
//! The supported status codes are:
//!
//! - Continue - 100
//! - OK - 200
//! - No Content - 204
//! - Bad Request - 400
//! - Not Found - 404
//! - Internal Server Error - 500
//! - Not Implemented - 501
//!
//! ## Example for parsing an HTTP Request from a slice
//! ```
//! extern crate micro_http;
//! use micro_http::{Request, Version};
//!
//! let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n\r\n").unwrap();
//! assert_eq!(http_request.http_version(), Version::Http10);
//! assert_eq!(http_request.uri().get_abs_path(), "/home");
//! ```
//!
//! ## Example for creating an HTTP Response
//! ```
//! extern crate micro_http;
//! use micro_http::{Body, Response, StatusCode, Version};
//!
//! let mut response = Response::new(Version::Http10, StatusCode::OK);
//! let body = String::from("This is a test");
//! response.set_body(Body::new(body.clone()));
//!
//! assert!(response.status() == StatusCode::OK);
//! assert_eq!(response.body().unwrap(), Body::new(body));
//! assert_eq!(response.http_version(), Version::Http10);
//!
//! let mut response_buf: [u8; 126] = [0; 126];
//! assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
//! ```
mod common;
mod connection;
mod request;
mod response;
use common::ascii;
use common::headers;
pub use connection::HttpConnection;
pub use request::{Request, RequestError};
pub use response::{Response, StatusCode};
pub use common::headers::Headers;
pub use common::{Body, Version};

474
micro_http/src/request.rs Normal file
View File

@ -0,0 +1,474 @@
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::str::from_utf8;
use common::ascii::{CR, CRLF_LEN, LF, SP};
pub use common::RequestError;
use common::{Body, Method, Version};
use headers::Headers;
/// Finds the first occurence of `sequence` in the `bytes` slice.
///
/// Returns the starting position of the `sequence` in `bytes` or `None` if the
/// `sequence` is not found.
pub fn find(bytes: &[u8], sequence: &[u8]) -> Option<usize> {
bytes
.windows(sequence.len())
.position(|window| window == sequence)
}
/// Wrapper over HTTP URIs.
///
/// The `Uri` can not be used directly and it is only accessible from an HTTP Request.
#[derive(Clone, Debug, PartialEq)]
pub struct Uri {
string: String,
}
impl Uri {
fn new(slice: &str) -> Self {
Uri {
string: String::from(slice),
}
}
fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
if bytes.is_empty() {
return Err(RequestError::InvalidUri("Empty URI not allowed."));
}
let utf8_slice =
from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?;
Ok(Uri::new(utf8_slice))
}
/// Returns the absolute path of the `Uri`.
///
/// URIs can be represented in absolute form or relative form. The absolute form includes
/// the HTTP scheme, followed by the absolute path as follows:
/// "http:" "//" host [ ":" port ] [ abs_path ]
/// The relative URIs can be one of net_path | abs_path | rel_path.
/// This method only handles absolute URIs and relative URIs specified by abs_path.
/// The abs_path is expected to start with '/'.
///
/// # Errors
/// Returns an empty byte array when the host or the path are empty/invalid.
///
pub fn get_abs_path(&self) -> &str {
const HTTP_SCHEME_PREFIX: &str = "http://";
if self.string.starts_with(HTTP_SCHEME_PREFIX) {
let without_scheme = &self.string[HTTP_SCHEME_PREFIX.len()..];
if without_scheme.is_empty() {
return "";
}
// The host in this case includes the port and contains the bytes after http:// up to
// the next '/'.
match without_scheme.bytes().position(|byte| byte == b'/') {
Some(len) => &without_scheme[len..],
None => "",
}
} else {
if self.string.starts_with('/') {
return self.string.as_str();
}
""
}
}
}
/// Wrapper over an HTTP Request Line.
#[derive(Debug, PartialEq)]
pub struct RequestLine {
method: Method,
uri: Uri,
http_version: Version,
}
impl RequestLine {
fn parse_request_line(request_line: &[u8]) -> (&[u8], &[u8], &[u8]) {
if let Some(method_end) = find(request_line, &[SP]) {
let method = &request_line[..method_end];
let uri_and_version = &request_line[(method_end + 1)..];
if let Some(uri_end) = find(uri_and_version, &[SP]) {
let uri = &uri_and_version[..uri_end];
let version = &uri_and_version[(uri_end + 1)..];
return (method, uri, version);
}
return (method, uri_and_version, b"");
}
(b"", b"", b"")
}
/// Tries to parse a byte stream in a request line. Fails if the request line is malformed.
pub fn try_from(request_line: &[u8]) -> Result<Self, RequestError> {
let (method, uri, version) = RequestLine::parse_request_line(request_line);
Ok(RequestLine {
method: Method::try_from(method)?,
uri: Uri::try_from(uri)?,
http_version: Version::try_from(version)?,
})
}
// Returns the minimum length of a valid request. The request must contain
// the method (GET), the URI (minmum 1 character), the HTTP version(HTTP/DIGIT.DIGIT) and
// 2 separators (SP).
fn min_len() -> usize {
Method::Get.raw().len() + 1 + Version::Http10.raw().len() + 2
}
#[cfg(test)]
pub fn new(method: Method, uri: &str, http_version: Version) -> Self {
RequestLine {
method,
uri: Uri::new(uri),
http_version,
}
}
}
/// Wrapper over an HTTP Request.
#[allow(unused)]
#[derive(Debug)]
pub struct Request {
/// The request line of the request.
pub request_line: RequestLine,
/// The headers of the request.
pub headers: Headers,
/// The body of the request.
pub body: Option<Body>,
}
impl Request {
/// Parses a byte slice into a HTTP Request.
///
/// The byte slice is expected to have the following format: </br>
/// * Request Line: "GET SP Request-uri SP HTTP/1.0 CRLF" - Mandatory </br>
/// * Request Headers "<headers> CRLF"- Optional </br>
/// * Entity Body - Optional </br>
/// The request headers and the entity body is not parsed and None is returned because
/// these are not used by the MMDS server.
/// The only supported method is GET and the HTTP protocol is expected to be HTTP/1.0
/// or HTTP/1.1.
///
/// # Errors
/// The function returns InvalidRequest when parsing the byte stream fails.
///
/// # Examples
///
/// ```
/// extern crate micro_http;
/// use micro_http::Request;
///
/// let http_request = Request::try_from(b"GET http://localhost/home HTTP/1.0\r\n");
/// ```
pub fn try_from(byte_stream: &[u8]) -> Result<Self, RequestError> {
// The first line of the request is the Request Line. The line ending is CR LF.
let request_line_end = match find(byte_stream, &[CR, LF]) {
Some(len) => len,
// If no CR LF is found in the stream, the request format is invalid.
None => return Err(RequestError::InvalidRequest),
};
let request_line_bytes = &byte_stream[..request_line_end];
if request_line_bytes.len() < RequestLine::min_len() {
return Err(RequestError::InvalidRequest);
}
let request_line = RequestLine::try_from(request_line_bytes)?;
// Find the next CR LF CR LF sequence in our buffer starting at the end on the Request
// Line, including the trailing CR LF previously found.
match find(&byte_stream[request_line_end..], &[CR, LF, CR, LF]) {
// If we have found a CR LF CR LF at the end of the Request Line, the request
// is complete.
Some(0) => Ok(Request {
request_line,
headers: Headers::default(),
body: None,
}),
Some(headers_end) => {
// Parse the request headers.
// Start by removing the leading CR LF from them.
let headers_and_body = &byte_stream[(request_line_end + CRLF_LEN)..];
let headers_end = headers_end - CRLF_LEN;
let headers = Headers::try_from(&headers_and_body[..headers_end])?;
// Parse the body of the request.
// Firstly check if we have a body.
let body = match headers.content_length() {
0 => {
// No request body.
None
}
content_length => {
// Headers suggest we have a body, but the buffer is shorter than the specified
// content length.
if headers_and_body.len() - (headers_end + 2 * CRLF_LEN)
< content_length as usize
{
return Err(RequestError::InvalidRequest);
}
let body_as_bytes = &headers_and_body[(headers_end + 2 * CRLF_LEN)..];
// If the actual length of the body is different than the `Content-Length` value
// in the headers then this request is invalid.
if body_as_bytes.len() == content_length as usize {
Some(Body::new(body_as_bytes))
} else {
return Err(RequestError::InvalidRequest);
}
}
};
Ok(Request {
request_line,
headers,
body,
})
}
// If we can't find a CR LF CR LF even though the request should have headers
// the request format is invalid.
None => Err(RequestError::InvalidRequest),
}
}
/// Returns the `Uri` from the parsed `Request`.
///
/// The return value can be used to get the absolute path of the URI.
pub fn uri(&self) -> &Uri {
&self.request_line.uri
}
/// Returns the HTTP `Version` of the `Request`.
pub fn http_version(&self) -> Version {
self.request_line.http_version
}
/// Returns the HTTP `Method` of the `Request`.
pub fn method(&self) -> Method {
self.request_line.method
}
}
#[cfg(test)]
mod tests {
use super::*;
impl PartialEq for Request {
fn eq(&self, other: &Request) -> bool {
// Ignore the other fields of Request for now because they are not used.
self.request_line == other.request_line
&& self.headers.content_length() == other.headers.content_length()
&& self.headers.expect() == other.headers.expect()
&& self.headers.chunked() == other.headers.chunked()
}
}
#[test]
fn test_uri() {
let uri = Uri::new("http://localhost/home");
assert_eq!(uri.get_abs_path(), "/home");
let uri = Uri::new("/home");
assert_eq!(uri.get_abs_path(), "/home");
let uri = Uri::new("home");
assert_eq!(uri.get_abs_path(), "");
let uri = Uri::new("http://");
assert_eq!(uri.get_abs_path(), "");
let uri = Uri::new("http://192.168.0.0");
assert_eq!(uri.get_abs_path(), "");
}
#[test]
fn test_find() {
let bytes: &[u8; 13] = b"abcacrgbabsjl";
let i = find(&bytes[..], b"ac");
assert_eq!(i.unwrap(), 3);
let i = find(&bytes[..], b"rgb");
assert_eq!(i.unwrap(), 5);
let i = find(&bytes[..], b"ab");
assert_eq!(i.unwrap(), 0);
let i = find(&bytes[..], b"l");
assert_eq!(i.unwrap(), 12);
let i = find(&bytes[..], b"jle");
assert!(i.is_none());
let i = find(&bytes[..], b"asdkjhasjhdjhgsadg");
assert!(i.is_none());
let i = find(&bytes[..], b"abcacrgbabsjl");
assert_eq!(i.unwrap(), 0);
}
#[test]
// Allow assertions on constants so we can have asserts on the values returned
// when result is Ok.
#[allow(clippy::assertions_on_constants)]
fn test_into_request_line() {
let expected_request_line = RequestLine {
http_version: Version::Http10,
method: Method::Get,
uri: Uri::new("http://localhost/home"),
};
let request_line = b"GET http://localhost/home HTTP/1.0";
match RequestLine::try_from(request_line) {
Ok(request) => assert_eq!(request, expected_request_line),
Err(_) => assert!(false),
};
let expected_request_line = RequestLine {
http_version: Version::Http11,
method: Method::Get,
uri: Uri::new("http://localhost/home"),
};
// Happy case with request line ending in CRLF.
let request_line = b"GET http://localhost/home HTTP/1.1";
match RequestLine::try_from(request_line) {
Ok(request) => assert_eq!(request, expected_request_line),
Err(_) => assert!(false),
};
// Happy case with request line ending in LF instead of CRLF.
let request_line = b"GET http://localhost/home HTTP/1.1";
match RequestLine::try_from(request_line) {
Ok(request) => assert_eq!(request, expected_request_line),
Err(_) => assert!(false),
};
// Test for invalid method.
let request_line = b"POST http://localhost/home HTTP/1.0";
assert_eq!(
RequestLine::try_from(request_line).unwrap_err(),
RequestError::InvalidHttpMethod("Unsupported HTTP method.")
);
// Test for invalid uri.
let request_line = b"GET HTTP/1.0";
assert_eq!(
RequestLine::try_from(request_line).unwrap_err(),
RequestError::InvalidUri("Empty URI not allowed.")
);
// Test for invalid HTTP version.
let request_line = b"GET http://localhost/home HTTP/2.0";
assert_eq!(
RequestLine::try_from(request_line).unwrap_err(),
RequestError::InvalidHttpVersion("Unsupported HTTP version.")
);
// Test for invalid format with no method, uri or version.
let request_line = b"nothing";
assert_eq!(
RequestLine::try_from(request_line).unwrap_err(),
RequestError::InvalidHttpMethod("Unsupported HTTP method.")
);
// Test for invalid format with no version.
let request_line = b"GET /";
assert_eq!(
RequestLine::try_from(request_line).unwrap_err(),
RequestError::InvalidHttpVersion("Unsupported HTTP version.")
);
}
#[test]
fn test_into_request() {
let expected_request = Request {
request_line: RequestLine {
http_version: Version::Http10,
method: Method::Get,
uri: Uri::new("http://localhost/home"),
},
body: None,
headers: Headers::default(),
};
let request_bytes = b"GET http://localhost/home HTTP/1.0\r\n \
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT\r\n\r\n";
let request = Request::try_from(request_bytes).unwrap();
assert_eq!(request, expected_request);
assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
assert_eq!(request.http_version(), Version::Http10);
assert!(request.body.is_none());
// Test for invalid Request (length is less than minimum).
let request_bytes = b"GET";
assert_eq!(
Request::try_from(request_bytes).unwrap_err(),
RequestError::InvalidRequest
);
// Test for a request with the headers we are looking for.
let request = Request::try_from(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: chunked\r\n \
Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody",
)
.unwrap();
assert_eq!(request.uri(), &Uri::new("http://localhost/home"));
assert_eq!(request.http_version(), Version::Http11);
assert_eq!(request.method(), Method::Patch);
assert_eq!(request.headers.chunked(), true);
assert_eq!(request.headers.expect(), true);
assert_eq!(request.headers.content_length(), 26);
assert_eq!(
request.body.unwrap().body,
String::from("this is not\n\r\na json \nbody")
.as_bytes()
.to_vec()
);
// Test for an invalid request format.
Request::try_from(b"PATCH http://localhost/home HTTP/1.1\r\n").unwrap_err();
// Test for an invalid encoding.
let request = Request::try_from(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Transfer-Encoding: identity; q=0\r\n \
Content-Length: 26\r\n\r\nthis is not\n\r\na json \nbody",
)
.unwrap_err();
assert_eq!(request, RequestError::InvalidHeader);
// Test for an invalid content length.
let request = Request::try_from(
b"PATCH http://localhost/home HTTP/1.1\r\n \
Expect: 100-continue\r\n \
Content-Length: 5000\r\n\r\nthis is a short body",
)
.unwrap_err();
assert_eq!(request, RequestError::InvalidRequest);
// Test for a request without a body and an optional header.
let request = Request::try_from(
b"GET http://localhost/ HTTP/1.0\r\n \
Accept-Encoding: gzip\r\n\r\n",
)
.unwrap();
assert_eq!(request.uri(), &Uri::new("http://localhost/"));
assert_eq!(request.http_version(), Version::Http10);
assert_eq!(request.method(), Method::Get);
assert_eq!(request.headers.chunked(), false);
assert_eq!(request.headers.expect(), false);
assert_eq!(request.headers.content_length(), 0);
assert!(request.body.is_none());
}
}

232
micro_http/src/response.rs Normal file
View File

@ -0,0 +1,232 @@
// 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 ascii::{COLON, CR, LF, SP};
use common::{Body, Version};
use headers::{Header, MediaType};
/// Wrapper over a response status code.
///
/// The status code is defined as specified in the
/// [RFC](https://tools.ietf.org/html/rfc7231#section-6).
#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq)]
pub enum StatusCode {
/// 100, Continue
Continue,
/// 200, OK
OK,
/// 204, No Content
NoContent,
/// 400, Bad Request
BadRequest,
/// 404, Not Found
NotFound,
/// 500, Internal Server Error
InternalServerError,
/// 501, Not Implemented
NotImplemented,
}
impl StatusCode {
fn raw(self) -> &'static [u8; 3] {
match self {
StatusCode::Continue => b"100",
StatusCode::OK => b"200",
StatusCode::NoContent => b"204",
StatusCode::BadRequest => b"400",
StatusCode::NotFound => b"404",
StatusCode::InternalServerError => b"500",
StatusCode::NotImplemented => b"501",
}
}
}
struct StatusLine {
http_version: Version,
status_code: StatusCode,
}
impl StatusLine {
fn new(http_version: Version, status_code: StatusCode) -> Self {
StatusLine {
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(Default)]
pub struct ResponseHeaders {
content_length: i32,
content_type: MediaType,
}
impl ResponseHeaders {
/// Writes the headers to `buf` using the HTTP specification.
pub fn write_all<T: Write>(&self, buf: &mut T) -> Result<(), WriteError> {
buf.write_all(b"Server: Firecracker API")?;
buf.write_all(&[CR, LF])?;
buf.write_all(b"Connection: keep-alive")?;
buf.write_all(&[CR, LF])?;
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])?;
if self.content_length != 0 {
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])?;
}
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 content type to be written in the HTTP response.
#[allow(unused)]
pub fn set_content_type(&mut self, content_type: MediaType) {
self.content_type = content_type;
}
}
/// 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`. The body can be updated with a call to `set_body`.
pub struct Response {
status_line: StatusLine,
headers: ResponseHeaders,
body: Option<Body>,
}
impl Response {
/// Creates a new HTTP `Response` with an empty body.
pub fn new(http_version: Version, status_code: StatusCode) -> Response {
Response {
status_line: StatusLine::new(http_version, status_code),
headers: ResponseHeaders::default(),
body: 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.
/// - `MediaType`: this is set to "text/plain".
pub fn set_body(&mut self, body: Body) {
self.headers.set_content_length(body.len() as i32);
self.body = Some(body);
}
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 HTTP Version of the response.
pub fn content_length(&self) -> i32 {
self.headers.content_length
}
/// Returns the HTTP Version 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
}
}
#[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));
assert!(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\r\n\
This is a test";
let mut response_buf: [u8; 126] = [0; 126];
assert!(response.write_all(&mut response_buf.as_mut()).is_ok());
assert!(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_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::NotFound.raw(), b"404");
assert_eq!(StatusCode::InternalServerError.raw(), b"500");
assert_eq!(StatusCode::NotImplemented.raw(), b"501");
}
}