mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-06 10:06:25 +00:00
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-api-macro",
|
||||||
"proxmox-sys",
|
"proxmox-sys",
|
||||||
"proxmox",
|
"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