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:
committed by
Adrian Catangiu
parent
31bc6268c7
commit
ac3bb940ab
@@ -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 {
|
||||
|
||||
@@ -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
142
src/router.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user