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:
parent
11b09e578c
commit
fe46b11152
@ -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
20
api-test/Cargo.toml
Normal 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
95
api-test/src/api.rs
Normal 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
70
api-test/src/main.rs
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user