add an api-test module

Run with:
cargo run -p api-test -- SomeDirectory/

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-12 15:11:05 +02:00
parent 11b09e578c
commit fe46b11152
4 changed files with 190 additions and 0 deletions

View File

@ -5,4 +5,9 @@ members = [
"proxmox-api-macro",
"proxmox-sys",
"proxmox",
# This is an api server test and may be temporarily broken by changes to
# proxmox-api or proxmox-api-macro, but should ultimately be updated to work
# again as it's supposed to serve as an example code!
"api-test",
]

20
api-test/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "api-test"
edition = "2018"
version = "0.1.0"
authors = [
"Dietmar Maurer <dietmar@proxmox.com>",
"Wolfgang Bumiller <w.bumiller@proxmox.com>",
]
[dependencies]
bytes = "0.4"
endian_trait = { version = "0.6", features = [ "arrays" ] }
failure = "0.1"
futures-01 = { package = "futures", version = "0.1" }
futures-preview = { version = "0.3.0-alpha.16", features = [ "compat", "io-compat" ] }
http = "0.1"
hyper = "0.12"
proxmox = { path = "../proxmox" }
serde_json = "1.0"
tokio = "0.1"

95
api-test/src/api.rs Normal file
View File

@ -0,0 +1,95 @@
use std::io;
use std::path::Path;
use failure::{bail, Error};
use futures::compat::AsyncRead01CompatExt;
use futures::compat::Future01CompatExt;
use futures::io::AsyncReadExt;
use http::Response;
use hyper::Body;
use proxmox::api::{api, router};
#[api({
description: "Hello API call",
})]
async fn hello() -> Result<Response<Body>, Error> {
Ok(http::Response::builder()
.status(200)
.header("content-type", "text/html")
.body(Body::from("Hello"))?)
}
static mut WWW_DIR: Option<String> = None;
pub fn www_dir() -> &'static str {
unsafe {
WWW_DIR
.as_ref()
.expect("expected WWW_DIR to be initialized")
.as_str()
}
}
pub fn set_www_dir(dir: String) {
unsafe {
assert!(WWW_DIR.is_none(), "WWW_DIR must only be initialized once!");
WWW_DIR = Some(dir);
}
}
#[api({
description: "Get a file from the www/ subdirectory.",
parameters: {
path: "Path to the file to fetch",
},
})]
async fn get_www(path: String) -> Result<Response<Body>, Error> {
if path.contains("..") {
bail!("illegal path");
}
let mut file = match tokio::fs::File::open(format!("{}/{}", www_dir(), path))
.compat()
.await
{
Ok(file) => file,
Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
return Ok(http::Response::builder()
.status(404)
.body(Body::from(format!("No such file or directory: {}", path)))?);
}
Err(e) => return Err(e.into()),
}
.compat();
let mut data = Vec::new();
file.read_to_end(&mut data).await?;
let mut response = http::Response::builder();
response.status(200);
let content_type = match Path::new(&path).extension().and_then(|e| e.to_str()) {
Some("html") => Some("text/html"),
Some("css") => Some("text/css"),
Some("js") => Some("application/javascript"),
Some("txt") => Some("text/plain"),
// ...
_ => None,
};
if let Some(content_type) = content_type {
response.header("content-type", content_type);
}
Ok(response.body(Body::from(data))?)
}
router! {
pub static ROUTER: Router<Body> = {
GET: hello,
/www/{path}*: { GET: get_www },
/api/1: {
}
};
}

70
api-test/src/main.rs Normal file
View File

@ -0,0 +1,70 @@
#![feature(async_await)]
use failure::{format_err, Error};
use http::Request;
use http::Response;
use hyper::service::service_fn;
use hyper::{Body, Server};
use serde_json::Value;
mod api;
pub static FOO: u32 = 3;
async fn run_request(request: Request<Body>) -> Result<http::Response<Body>, hyper::Error> {
match route_request(request).await {
Ok(r) => Ok(r),
Err(err) => Ok(Response::builder()
.status(400)
.body(Body::from(format!("ERROR: {}", err)))
.expect("building an error response...")),
}
}
async fn route_request(request: Request<Body>) -> Result<http::Response<Body>, Error> {
let path = request.uri().path();
let (target, params) = api::ROUTER
.lookup(path)
.ok_or_else(|| format_err!("missing path: {}", path))?;
let handler = target
.get
.as_ref()
.ok_or_else(|| format_err!("no GET method for: {}", path))?
.handler();
Ok(handler(params.unwrap_or(Value::Null)).await?)
}
type BoxFut = Box<dyn futures_01::Future<Item = Response<Body>, Error = hyper::Error> + Send>;
fn main() {
// We expect a path, where to find our files we expose via the www/ dir:
let mut args = std::env::args();
// real code should have better error handling
let _program_name = args.next();
let www_dir = args.next().expect("expected a www/ subdirectory");
api::set_www_dir(www_dir.to_string());
// Construct our SocketAddr to listen on...
let addr = ([0, 0, 0, 0], 3000).into();
// And a MakeService to handle each connection...
let make_service = || {
service_fn(|req| {
let fut: BoxFut = Box::new(futures::compat::Compat::new(Box::pin(run_request(req))));
fut
})
};
// Then bind and serve...
let server = {
use futures_01::Future;
Server::bind(&addr)
.serve(make_service)
.map_err(|err| eprintln!("server error: {}", err))
};
tokio::run(server);
}