micro_http: some doc and code corrections

Signed-off-by: karthik nedunchezhiyan <karthik1705.n@gmail.com>
Signed-off-by: YUAN LYU <lyuyuan92@gmail.com>
This commit is contained in:
karthik nedunchezhiyan
2020-01-26 19:16:05 +05:30
committed by Adrian Catangiu
parent 4ee7142098
commit 569230220f
6 changed files with 186 additions and 110 deletions

View File

@@ -21,25 +21,32 @@ pub enum Header {
}
impl Header {
/// Returns a byte slice representation of the object.
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",
Header::Server => b"Server",
Self::ContentLength => b"Content-Length",
Self::ContentType => b"Content-Type",
Self::Expect => b"Expect",
Self::TransferEncoding => b"Transfer-Encoding",
Self::Server => b"Server",
}
}
/// Parses a byte slice into a Header structure. Header must be ASCII, so also
/// UTF-8 valid.
///
/// # Errors
/// `InvalidRequest` is returned if slice contains invalid utf8 characters.
/// `InvalidHeader` is returned if unsupported header found.
fn try_from(string: &[u8]) -> Result<Self, RequestError> {
if let Ok(mut utf8_string) = String::from_utf8(string.to_vec()) {
utf8_string.make_ascii_lowercase();
match utf8_string.trim() {
"content-length" => Ok(Header::ContentLength),
"content-type" => Ok(Header::ContentType),
"expect" => Ok(Header::Expect),
"transfer-encoding" => Ok(Header::TransferEncoding),
"server" => Ok(Header::Server),
"content-length" => Ok(Self::ContentLength),
"content-type" => Ok(Self::ContentType),
"expect" => Ok(Self::Expect),
"transfer-encoding" => Ok(Self::TransferEncoding),
"server" => Ok(Self::Server),
_ => Err(RequestError::InvalidHeader),
}
} else {
@@ -75,16 +82,18 @@ pub struct Headers {
chunked: bool,
}
impl Headers {
impl Default for Headers {
/// By default Requests are created with no headers.
pub fn default() -> Headers {
Headers {
content_length: 0,
expect: false,
chunked: false,
fn default() -> Self {
Self {
content_length: Default::default(),
expect: Default::default(),
chunked: Default::default(),
}
}
}
impl Headers {
/// Expects one header line and parses it, updating the header structure or returning an
/// error if the header is invalid.
///
@@ -94,6 +103,17 @@ impl Headers {
/// `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.
///
/// # Examples
///
/// ```
/// extern crate micro_http;
/// use micro_http::Headers;
///
/// let mut request_header = Headers::default();
/// assert!(request_header.parse_header_line(b"Content-Length: 24").is_ok());
/// assert!(request_header.parse_header_line(b"Content-Length: 24: 2").is_err());
/// ```
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) {
@@ -104,17 +124,13 @@ impl Headers {
}
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());
match try_numeric {
Ok(content_length) if content_length >= 0 => {
self.content_length = content_length;
Ok(())
}
_ => Err(RequestError::InvalidHeader),
Header::ContentLength => match entry[1].trim().parse::<i32>() {
Ok(content_length) => {
self.content_length = content_length;
Ok(())
}
}
Err(_) => Err(RequestError::InvalidHeader),
},
Header::ContentType => {
match MediaType::try_from(entry[1].trim().as_bytes()) {
Ok(_) => Ok(()),
@@ -165,7 +181,7 @@ impl Headers {
#[cfg(test)]
pub fn new(content_length: i32, expect: bool, chunked: bool) -> Self {
Headers {
Self {
content_length,
expect,
chunked,
@@ -196,7 +212,7 @@ impl Headers {
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 mut headers = Self::default();
let header_lines = text.split("\r\n");
for header_line in header_lines {
@@ -224,30 +240,57 @@ pub enum MediaType {
}
impl Default for MediaType {
/// Default value for MediaType is application/json
fn default() -> Self {
MediaType::ApplicationJson
Self::ApplicationJson
}
}
impl MediaType {
fn try_from(bytes: &[u8]) -> Result<Self, RequestError> {
/// Parses a byte slice into a MediaType structure for a HTTP request. MediaType
/// must be ASCII, so also UTF-8 valid.
///
/// # Errors
/// The function returns `InvalidRequest` when parsing the byte stream fails or
/// unsupported MediaType found.
///
/// # Examples
///
/// ```
/// extern crate micro_http;
/// use micro_http::MediaType;
///
/// assert!(MediaType::try_from(b"application/json").is_ok());
/// assert!(MediaType::try_from(b"application/json2").is_err());
/// ```
pub 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().trim() {
"text/plain" => Ok(MediaType::PlainText),
"application/json" => Ok(MediaType::ApplicationJson),
"text/plain" => Ok(Self::PlainText),
"application/json" => Ok(Self::ApplicationJson),
_ => Err(RequestError::InvalidRequest),
}
}
/// Returns a static string representation of the object.
///
/// # Examples
///
/// ```
/// extern crate micro_http;
/// use micro_http::MediaType;
///
/// let media_type = MediaType::ApplicationJson;
/// assert_eq!(media_type.as_str(), "application/json");
/// ```
pub fn as_str(self) -> &'static str {
match self {
MediaType::PlainText => "text/plain",
MediaType::ApplicationJson => "application/json",
Self::PlainText => "text/plain",
Self::ApplicationJson => "application/json",
}
}
}

View File

@@ -33,12 +33,12 @@ pub enum RequestError {
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."),
Self::InvalidHttpMethod(inner) => write!(f, "Invalid HTTP Method: {}", inner),
Self::InvalidUri(inner) => write!(f, "Invalid URI: {}", inner),
Self::InvalidHttpVersion(inner) => write!(f, "Invalid HTTP Version: {}", inner),
Self::UnsupportedHeader => write!(f, "Unsupported header."),
Self::InvalidHeader => write!(f, "Invalid header."),
Self::InvalidRequest => write!(f, "Invalid request."),
}
}
}
@@ -59,10 +59,10 @@ pub enum ConnectionError {
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."),
Self::ParseError(inner) => write!(f, "Parsing error: {}", inner),
Self::StreamError(inner) => write!(f, "Stream error: {}", inner),
Self::ConnectionClosed => write!(f, "Connection closed."),
Self::InvalidWrite => write!(f, "Invalid write attempt."),
}
}
}
@@ -96,9 +96,9 @@ pub enum ServerError {
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."),
Self::IOError(inner) => write!(f, "IO error: {}", inner),
Self::ConnectionError(inner) => write!(f, "Connection error: {}", inner),
Self::ServerFull => write!(f, "Server is full."),
}
}
}
@@ -122,7 +122,7 @@ pub struct Body {
impl Body {
/// Creates a new `Body` from a `String` input.
pub fn new<T: Into<Vec<u8>>>(body: T) -> Self {
Body { body: body.into() }
Self { body: body.into() }
}
/// Returns the body as an `u8 slice`.
@@ -159,12 +159,12 @@ impl Method {
/// an error, but when using the input b"GET", it returns Method::Get.
///
/// # Errors
/// Returns `RequestError` if the method specified by `bytes` is unsupported.
/// `InvalidHttpMethod` is returned if the specified HTTP method 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),
b"GET" => Ok(Self::Get),
b"PUT" => Ok(Self::Put),
b"PATCH" => Ok(Self::Patch),
_ => Err(RequestError::InvalidHttpMethod("Unsupported HTTP method.")),
}
}
@@ -172,9 +172,9 @@ impl 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",
Self::Get => b"GET",
Self::Put => b"PUT",
Self::Patch => b"PATCH",
}
}
@@ -208,12 +208,19 @@ pub enum Version {
Http11,
}
impl Default for Version {
/// Returns the default HTTP version = HTTP/1.1.
fn default() -> Self {
Self::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",
Self::Http10 => b"HTTP/1.0",
Self::Http11 => b"HTTP/1.1",
}
}
@@ -223,21 +230,16 @@ impl Version {
/// The version is case sensitive and the accepted input is upper case.
///
/// # Errors
/// Returns a `RequestError` when the version is not supported.
/// Returns a `InvalidHttpVersion` when the HTTP 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),
b"HTTP/1.0" => Ok(Self::Http10),
b"HTTP/1.1" => Ok(Self::Http11),
_ => Err(RequestError::InvalidHttpVersion(
"Unsupported HTTP version.",
)),
}
}
/// Returns the default HTTP version = HTTP/1.1.
pub fn default() -> Self {
Version::Http11
}
}
#[cfg(test)]

View File

@@ -55,7 +55,7 @@ pub struct HttpConnection<T> {
impl<T: Read + Write> HttpConnection<T> {
/// Creates an empty connection.
pub fn new(stream: T) -> Self {
HttpConnection {
Self {
pending_request: None,
stream,
state: ConnectionState::WaitingForRequestLine,
@@ -114,8 +114,12 @@ impl<T: Read + Write> HttpConnection<T> {
}
}
// Reads a maximum of `BUFFER_SIZE` bytes from the stream into `buffer`.
// The return value represents the end index of what we have just appended.
/// Reads a maximum of 1024 bytes from the stream into `buffer`.
/// The return value represents the end index of what we have just appended.
///
/// # Errors
/// `StreamError` is returned if any error occurred while reading the stream.
/// `ConnectionClosed` is returned if the client closed the connection.
fn read_bytes(&mut self) -> Result<usize, ConnectionError> {
loop {
// Append new bytes to what we already have in the buffer.
@@ -129,8 +133,11 @@ impl<T: Read + Write> HttpConnection<T> {
}
}
// Parses bytes in `buffer` for a valid request line.
// Returns `false` if there are no more bytes to be parsed in the buffer.
/// Parses bytes in `buffer` for a valid request line.
/// Returns `false` if there are no more bytes to be parsed in the buffer.
///
/// # Errors
/// `ParseError` is returned if unable to parse request line or line longer than BUFFER_SIZE.
fn parse_request_line(
&mut self,
start: &mut usize,
@@ -141,13 +148,12 @@ impl<T: Read + Write> HttpConnection<T> {
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,
request_line: RequestLine::try_from(line)
.map_err(ConnectionError::ParseError)?,
headers: Headers::default(),
body: None,
});
@@ -170,19 +176,23 @@ impl<T: Read + Write> HttpConnection<T> {
}
}
// Parses bytes in `buffer` for header fields.
// Returns `false` if there are no more bytes to be parsed in the buffer.
/// Parses bytes in `buffer` for header fields.
/// Returns `false` if there are no more bytes to be parsed in the buffer.
///
/// # Errors
/// `ParseError` is returned if unable to parse header or line longer than BUFFER_SIZE.
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.
// We have found the end of the header.
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.
@@ -243,8 +253,11 @@ impl<T: Read + Write> HttpConnection<T> {
}
}
// 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.
/// 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.
///
/// # Errors
/// `ParseError` is returned when the body is larger than the specified content-length.
fn parse_body(
&mut self,
line_start_index: &mut usize,
@@ -296,7 +309,10 @@ impl<T: Read + Write> HttpConnection<T> {
/// 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.
/// Should be called whenever an `EPOLLOUT` event is signaled. If no bytes
/// were written to the stream or error occurred while trying to write to stream,
/// we will discard all responses from response_queue because there is no way
/// to deliver it to client.
///
/// # Errors
/// `StreamError` is returned when an IO operation fails.

View File

@@ -29,7 +29,7 @@ pub struct Uri {
impl Uri {
fn new(slice: &str) -> Self {
Uri {
Self {
string: String::from(slice),
}
}
@@ -40,7 +40,7 @@ impl Uri {
}
let utf8_slice =
from_utf8(bytes).map_err(|_| RequestError::InvalidUri("Cannot parse URI as UTF-8."))?;
Ok(Uri::new(utf8_slice))
Ok(Self::new(utf8_slice))
}
/// Returns the absolute path of the `Uri`.
@@ -108,10 +108,15 @@ impl RequestLine {
}
/// Tries to parse a byte stream in a request line. Fails if the request line is malformed.
///
/// # Errors
/// `InvalidHttpMethod` is returned if the specified HTTP method is unsupported.
/// `InvalidHttpVersion` is returned if the specified HTTP version is unsupported.
/// `InvalidUri` is returned if the specified Uri is not valid.
pub fn try_from(request_line: &[u8]) -> Result<Self, RequestError> {
let (method, uri, version) = RequestLine::parse_request_line(request_line);
let (method, uri, version) = Self::parse_request_line(request_line);
Ok(RequestLine {
Ok(Self {
method: Method::try_from(method)?,
uri: Uri::try_from(uri)?,
http_version: Version::try_from(version)?,
@@ -127,7 +132,7 @@ impl RequestLine {
#[cfg(test)]
pub fn new(method: Method, uri: &str, http_version: Version) -> Self {
RequestLine {
Self {
method,
uri: Uri::new(uri),
http_version,
@@ -187,7 +192,7 @@ impl Request {
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 {
Some(0) => Ok(Self {
request_line,
headers: Headers::default(),
body: None,
@@ -225,7 +230,7 @@ impl Request {
}
};
Ok(Request {
Ok(Self {
request_line,
headers,
body,
@@ -260,7 +265,7 @@ mod tests {
use super::*;
impl PartialEq for Request {
fn eq(&self, other: &Request) -> bool {
fn eq(&self, other: &Self) -> 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()

View File

@@ -35,14 +35,14 @@ impl StatusCode {
/// Returns the status code as bytes.
pub 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",
StatusCode::ServiceUnavailable => b"503",
Self::Continue => b"100",
Self::OK => b"200",
Self::NoContent => b"204",
Self::BadRequest => b"400",
Self::NotFound => b"404",
Self::InternalServerError => b"500",
Self::NotImplemented => b"501",
Self::ServiceUnavailable => b"503",
}
}
}
@@ -54,7 +54,7 @@ struct StatusLine {
impl StatusLine {
fn new(http_version: Version, status_code: StatusCode) -> Self {
StatusLine {
Self {
http_version,
status_code,
}
@@ -81,10 +81,10 @@ pub struct ResponseHeaders {
impl Default for ResponseHeaders {
fn default() -> Self {
ResponseHeaders {
Self {
content_length: Default::default(),
content_type: Default::default(),
server: "Firecracker API".to_string(),
server: String::from("Firecracker API"),
}
}
}
@@ -134,7 +134,9 @@ impl ResponseHeaders {
/// 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`.
/// 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`.
pub struct Response {
status_line: StatusLine,
headers: ResponseHeaders,
@@ -143,11 +145,11 @@ pub struct Response {
impl Response {
/// Creates a new HTTP `Response` with an empty body.
pub fn new(http_version: Version, status_code: StatusCode) -> Response {
Response {
pub fn new(http_version: Version, status_code: StatusCode) -> Self {
Self {
status_line: StatusLine::new(http_version, status_code),
headers: ResponseHeaders::default(),
body: None,
body: Default::default(),
}
}

View File

@@ -38,7 +38,7 @@ impl ServerRequest {
/// Creates a new `ServerRequest` object from an existing `Request`,
/// adding an identification token.
pub fn new(request: Request, id: u64) -> Self {
ServerRequest { request, id }
Self { request, id }
}
/// Returns a reference to the inner request.
@@ -68,8 +68,8 @@ pub struct ServerResponse {
}
impl ServerResponse {
fn new(response: Response, id: u64) -> ServerResponse {
ServerResponse { response, id }
fn new(response: Response, id: u64) -> Self {
Self { response, id }
}
}
@@ -97,7 +97,7 @@ struct ClientConnection<T> {
impl<T: Read + Write> ClientConnection<T> {
fn new(connection: HttpConnection<T>) -> Self {
ClientConnection {
Self {
connection,
state: ClientConnectionState::AwaitingIncoming,
in_flight_response_count: 0,
@@ -201,13 +201,14 @@ impl<T: Read + Write> ClientConnection<T> {
/// HTTP Server implementation using Unix Domain Sockets and `EPOLL` to
/// handle multiple connections on the same thread.
///
/// The function that does the data exchange is `handle_notifications`.
/// The function that handles incoming connections, parses incoming
/// requests and sends responses for awaiting requests is `requests`.
/// It can be called in a loop, which will render the thread that the
/// server runs on incapable of performing other operations, or it can
/// be used in another `EPOLL` structure, as it provides its `epoll_fd`,
/// the file descriptor of the epoll structure used within the server,
/// and it can be added to another one using the `EPOLLIN` flag. Whenever
/// there is a notification on that fd, `handle_notifications` should be
/// there is a notification on that fd, `requests` should be
/// called once.
///
/// # Example
@@ -263,7 +264,7 @@ impl HttpServer {
pub fn new<P: AsRef<Path>>(path_to_socket: P) -> Result<Self> {
let socket = UnixListener::bind(path_to_socket).map_err(ServerError::IOError)?;
let epoll_fd = epoll::create(true).map_err(ServerError::IOError)?;
Ok(HttpServer {
Ok(Self {
socket,
epoll_fd,
connections: HashMap::new(),
@@ -477,7 +478,8 @@ impl HttpServer {
/// Accepts a new incoming connection and adds it to the `epoll` notification structure.
///
/// # Errors
/// `IOError` is returned when an `epoll::ctl` operation fails.
/// `IOError` is returned when socket or epoll operations fail.
/// `ServerFull` is returned if server full capacity has been reached.
fn handle_new_connection(&mut self) -> Result<()> {
if self.connections.len() == MAX_CONNECTIONS {
// If we want a replacement policy for connections
@@ -509,6 +511,9 @@ impl HttpServer {
/// Changes the event type for a connection to either listen for incoming bytes
/// or for when the stream is ready for writing.
///
/// # Errors
/// `IOError` is returned when an `EPOLL_CTL_MOD` control operation fails.
fn epoll_mod(epoll_fd: RawFd, stream_fd: RawFd, evset: epoll::Events) -> Result<()> {
let event = epoll::Event::new(evset, stream_fd as u64);
epoll::ctl(
@@ -521,6 +526,9 @@ impl HttpServer {
}
/// Adds a stream to the `epoll` notification structure with the `EPOLLIN` event set.
///
/// # Errors
/// `IOError` is returned when an `EPOLL_CTL_ADD` control operation fails.
fn epoll_add(epoll_fd: RawFd, stream_fd: RawFd) -> Result<()> {
epoll::ctl(
epoll_fd,