From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 01BE96D539 for ; Tue, 28 Sep 2021 11:11:55 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id F0703B59C for ; Tue, 28 Sep 2021 11:11:54 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 77C2AB588 for ; Tue, 28 Sep 2021 11:11:53 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 5098C42495 for ; Tue, 28 Sep 2021 11:11:53 +0200 (CEST) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Tue, 28 Sep 2021 11:11:52 +0200 Message-Id: <20210928091152.2151682-4-d.csapak@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210928091152.2151682-1-d.csapak@proxmox.com> References: <20210928091152.2151682-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.248 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 28 Sep 2021 09:11:55 -0000 show how to generally start a daemon that serves a rest api + index page api calls are: / GET listing /ping GET returns "pong" /items GET lists existing items POST lets user create new items /items/{id} GET returns the content of a single item PUT updates an item DELETE deletes an item Contains a small dummy user/authinfo Signed-off-by: Dominik Csapak --- changes from v1: * use hyper::server::conn::AddrIncoming::from_listener proxmox-rest-server/Cargo.toml | 5 + proxmox-rest-server/examples/rest_server.rs | 219 ++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 proxmox-rest-server/examples/rest_server.rs diff --git a/proxmox-rest-server/Cargo.toml b/proxmox-rest-server/Cargo.toml index b0e53d19..a6d25b8b 100644 --- a/proxmox-rest-server/Cargo.toml +++ b/proxmox-rest-server/Cargo.toml @@ -5,6 +5,11 @@ authors = ["Proxmox Support Team "] edition = "2018" description = "REST server implementation" +# for example +[dev-dependencies] +proxmox = { version = "0.13.4", features = ["router","api-macro"] } +pbs-runtime = { path = "../pbs-runtime" } + [dependencies] anyhow = "1.0" futures = "0.3" diff --git a/proxmox-rest-server/examples/rest_server.rs b/proxmox-rest-server/examples/rest_server.rs new file mode 100644 index 00000000..89efbcb2 --- /dev/null +++ b/proxmox-rest-server/examples/rest_server.rs @@ -0,0 +1,219 @@ +use std::sync::{Arc, Mutex}; +use std::collections::HashMap; + +use anyhow::{bail, format_err, Error}; +use futures::{FutureExt, TryFutureExt}; +use lazy_static::lazy_static; + +use proxmox::api::{api, router::SubdirMap, Router, RpcEnvironmentType, UserInformation}; +use proxmox::list_subdirs_api_method; +use proxmox_rest_server::{ApiAuth, ApiConfig, AuthError, RestServer}; + +// Create a Dummy User info and auth system +// Normally this would check and authenticate the user +struct DummyUserInfo; + +impl UserInformation for DummyUserInfo { + fn is_superuser(&self, _userid: &str) -> bool { + true + } + fn is_group_member(&self, _userid: &str, group: &str) -> bool { + group == "Group" + } + fn lookup_privs(&self, _userid: &str, _path: &[&str]) -> u64 { + u64::MAX + } +} + +struct DummyAuth; + +impl ApiAuth for DummyAuth { + fn check_auth( + &self, + _headers: &http::HeaderMap, + _method: &hyper::Method, + ) -> Result<(String, Box), AuthError> { + // get some global/cached userinfo + let userinfo = DummyUserInfo; + // Do some user checks, e.g. cookie/csrf + Ok(("User".to_string(), Box::new(userinfo))) + } +} + +// this should return the index page of the webserver +// iow. what the user browses to + +fn get_index( + _auth_id: Option, + _language: Option, + _api: &ApiConfig, + _parts: http::request::Parts, +) -> http::Response { + // build an index page + http::Response::builder() + .body("hello world".into()) + .unwrap() +} + +// a few examples on how to do api calls with the Router + +#[api] +/// A simple ping method. returns "pong" +fn ping() -> Result { + Ok("pong".to_string()) +} + +lazy_static! { + static ref ITEM_MAP: Mutex> = Mutex::new(HashMap::new()); +} + +#[api] +/// Lists all current items +fn list_items() -> Result, Error> { + Ok(ITEM_MAP.lock().unwrap().keys().map(|k| k.clone()).collect()) +} + +#[api( + input: { + properties: { + name: { + type: String, + description: "The name", + }, + value: { + type: String, + description: "The value", + }, + }, + }, +)] +/// creates a new item +fn create_item(name: String, value: String) -> Result<(), Error> { + let mut map = ITEM_MAP.lock().unwrap(); + if map.contains_key(&name) { + bail!("{} already exists", name); + } + + map.insert(name, value); + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + type: String, + description: "The name", + }, + }, + }, +)] +/// returns the value of an item +fn get_item(name: String) -> Result { + ITEM_MAP.lock().unwrap().get(&name).map(|s| s.to_string()).ok_or_else(|| format_err!("no such item '{}'", name)) +} + +#[api( + input: { + properties: { + name: { + type: String, + description: "The name", + }, + value: { + type: String, + description: "The value", + }, + }, + }, +)] +/// updates an item +fn update_item(name: String, value: String) -> Result<(), Error> { + if let Some(val) = ITEM_MAP.lock().unwrap().get_mut(&name) { + *val = value; + } else { + bail!("no such item '{}'", name); + } + Ok(()) +} + +#[api( + input: { + properties: { + name: { + type: String, + description: "The name", + }, + }, + }, +)] +/// deletes an item +fn delete_item(name: String) -> Result<(), Error> { + if ITEM_MAP.lock().unwrap().remove(&name).is_none() { + bail!("no such item '{}'", name); + } + Ok(()) +} + +const ITEM_ROUTER: Router = Router::new() + .get(&API_METHOD_GET_ITEM) + .put(&API_METHOD_UPDATE_ITEM) + .delete(&API_METHOD_DELETE_ITEM); + +const SUBDIRS: SubdirMap = &[ + ( + "items", + &Router::new() + .get(&API_METHOD_LIST_ITEMS) + .post(&API_METHOD_CREATE_ITEM) + .match_all("name", &ITEM_ROUTER) + ), + ( + "ping", + &Router::new() + .get(&API_METHOD_PING) + ), +]; + +const ROUTER: Router = Router::new() + .get(&list_subdirs_api_method!(SUBDIRS)) + .subdirs(SUBDIRS); + +async fn run() -> Result<(), Error> { + + // we first have to configure the api environment (basedir etc.) + + let config = ApiConfig::new( + "/var/tmp/", + &ROUTER, + RpcEnvironmentType::PUBLIC, + Arc::new(DummyAuth {}), + get_index, + )?; + let rest_server = RestServer::new(config); + + // then we have to create a daemon that listens, accepts and serves + // the api to clients + proxmox_rest_server::daemon::create_daemon( + ([127, 0, 0, 1], 65000).into(), + move |listener, ready| { + let incoming = hyper::server::conn::AddrIncoming::from_listener(listener)?; + + Ok(ready + .and_then(|_| hyper::Server::builder(incoming) + .serve(rest_server) + .map_err(Error::from) + ) + .map_err(|err| eprintln!("ERR: {}", err)) + .map(|test| println!("OK: {}", test.is_ok()))) + }, + "example_server", + ).await?; + + Ok(()) +} + +fn main() -> Result<(), Error> { + pbs_runtime::main(run()) +} -- 2.30.2