Import http routes from Cloud Hypervisor project

Import vmm/src/api/http.rs from Cloud Hyerpvisor project, commit
345c922cb9a88183e2da9d29230ecb945b0a6452 with following changes:
1) use generic type for handler argument.
2) remove server thread relative code.
3) add unit test cases.
4) refine for better code reuse.

Signed-off-by: Liu Jiang <gerry@linux.alibaba.com>
This commit is contained in:
Liu Jiang
2020-03-23 21:26:13 +08:00
committed by Adrian Catangiu
parent 31bc6268c7
commit ac3bb940ab
3 changed files with 159 additions and 0 deletions

View File

@@ -67,6 +67,21 @@ impl Display for ConnectionError {
}
}
/// Errors pertaining to `HttpRoute`.
#[derive(Debug)]
pub enum RouteError {
/// Handler for http routing path already exists.
HandlerExist(String),
}
impl Display for RouteError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
RouteError::HandlerExist(p) => write!(f, "handler for {} already exists", p),
}
}
}
/// Errors pertaining to `HttpServer`.
#[derive(Debug)]
pub enum ServerError {

View File

@@ -113,6 +113,7 @@ mod common;
mod connection;
mod request;
mod response;
mod router;
mod server;
pub use self::common::headers::{Headers, MediaType};
@@ -120,4 +121,5 @@ pub use self::common::{Body, Method, Version};
pub use self::connection::{ConnectionError, HttpConnection};
pub use self::request::{Request, RequestError};
pub use self::response::{Response, StatusCode};
pub use self::router::{EndpointHandler, HttpRoutes, RouteError, TryClone};
pub use self::server::{HttpServer, ServerError, ServerRequest, ServerResponse};

142
src/router.rs Normal file
View File

@@ -0,0 +1,142 @@
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use crate::{MediaType, Request, Response, StatusCode, Version};
pub use crate::common::RouteError;
/// Trait to clone the argument for EndpointHandler::handle_request.
pub trait TryClone
where
Self: std::marker::Sized,
{
/// Clone an object from a reference.
fn try_clone(&self) -> Option<Self>;
}
/// An HTTP endpoint handler interface
pub trait EndpointHandler<T>: Sync + Send {
/// Handles an HTTP request.
fn handle_request(&self, req: &Request, arg: T) -> Response;
}
/// An HTTP routes structure.
pub struct HttpRoutes<T: TryClone> {
server_id: String,
prefix: String,
media_type: MediaType,
/// routes is a hash table mapping endpoint URIs to their endpoint handlers.
routes: HashMap<String, Box<dyn EndpointHandler<T> + Sync + Send>>,
}
impl<T: Send + TryClone> HttpRoutes<T> {
/// Create a http request router.
pub fn new(server_id: String, prefix: String) -> Self {
HttpRoutes {
server_id,
prefix,
media_type: MediaType::ApplicationJson,
routes: HashMap::new(),
}
}
/// Register a request handler for a path.
pub fn add_route(
&mut self,
path: String,
handler: Box<dyn EndpointHandler<T> + Sync + Send>,
) -> Result<(), RouteError> {
let full = format!("{}{}", self.prefix, path);
if self.routes.contains_key(&full) {
Err(RouteError::HandlerExist(full))
} else {
self.routes.insert(full, handler);
Ok(())
}
}
/// Handle an incoming http request and generate corresponding response.
pub fn handle_http_request(&self, request: &Request, argument: &T) -> Response {
let path = request.uri().get_abs_path().to_string();
let mut response = match self.routes.get(&path) {
Some(route) => match argument.try_clone() {
Some(arg) => route.handle_request(&request, arg),
None => Response::new(Version::Http11, StatusCode::InternalServerError),
},
None => Response::new(Version::Http11, StatusCode::NotFound),
};
response.set_server(&self.server_id);
response.set_content_type(self.media_type);
response
}
}
#[cfg(test)]
mod tests {
use super::*;
struct HandlerArg(bool);
impl TryClone for HandlerArg {
fn try_clone(&self) -> Option<Self> {
match self.0 {
true => Some(HandlerArg(true)),
false => None,
}
}
}
struct MockHandler {}
impl EndpointHandler<HandlerArg> for MockHandler {
fn handle_request(&self, _req: &Request, _arg: HandlerArg) -> Response {
Response::new(Version::Http11, StatusCode::OK)
}
}
#[test]
fn test_create_router() {
let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
let handler = MockHandler {};
let res = router.add_route("/func1".to_string(), Box::new(handler));
assert!(res.is_ok());
let key = format!("{}{}", "/api/v1", "/func1");
assert!(router.routes.contains_key(&key));
let handler = MockHandler {};
match router.add_route("/func1".to_string(), Box::new(handler)) {
Err(RouteError::HandlerExist(_)) => {}
_ => panic!("add_route() should return error for path with existing handler"),
}
let handler = MockHandler {};
let res = router.add_route("/func2".to_string(), Box::new(handler));
assert!(res.is_ok());
}
#[test]
fn test_handle_http_request() {
let mut router = HttpRoutes::new("Mock_Server".to_string(), "/api/v1".to_string());
let handler = MockHandler {};
router
.add_route("/func1".to_string(), Box::new(handler))
.unwrap();
let request =
Request::try_from(b"GET http://localhost/api/v1/func2 HTTP/1.1\r\n\r\n").unwrap();
let arg = HandlerArg(true);
let reply = router.handle_http_request(&request, &arg);
assert_eq!(reply.status(), StatusCode::NotFound);
let request =
Request::try_from(b"GET http://localhost/api/v1/func1 HTTP/1.1\r\n\r\n").unwrap();
let arg = HandlerArg(false);
let reply = router.handle_http_request(&request, &arg);
assert_eq!(reply.status(), StatusCode::InternalServerError);
}
}