* Re: [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api
@ 2021-09-29 5:19 Dietmar Maurer
0 siblings, 0 replies; 3+ messages in thread
From: Dietmar Maurer @ 2021-09-29 5:19 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak
Is it possible to avoid the dev-dependency to pbs-runtime?
(We want to split out proxmox-rest-server, so we cannot use 'pbs-runtime')
> 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 <support@proxmox.com>"]
> edition = "2018"
> description = "REST server implementation"
>
> +# for example
> +[dev-dependencies]
> +proxmox = { version = "0.13.4", features = ["router","api-macro"] }
> +pbs-runtime = { path = "../pbs-runtime" }
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api
2021-09-28 9:11 ` [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api Dominik Csapak
@ 2021-09-29 6:41 ` Thomas Lamprecht
0 siblings, 0 replies; 3+ messages in thread
From: Thomas Lamprecht @ 2021-09-29 6:41 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak
On 28.09.21 11:11, Dominik Csapak wrote:
> show how to generally start a daemon that serves a rest api + index page
>
> api calls are:
well they are prefixed with `/api2` that's really something that we need to
make configurable, for any future project I definitively do not want to use
the rather confusing /api2 from the start, that can be rather easily adapted
in the client's central HTTP request handling code.
> / 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 <d.csapak@proxmox.com>
> ---
> 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 <support@proxmox.com>"]
> edition = "2018"
> description = "REST server implementation"
>
> +# for example
> +[dev-dependencies]
> +proxmox = { version = "0.13.4", features = ["router","api-macro"] }
0.13.4 is already outdated, do we even need tho have that version target so strict as
else I'd go with "0.13" for now..
> +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<dyn UserInformation + Sync + Send>), 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
above fits in one line even with 80 cc, and in rust we normally go always for 100cc.
> +fn get_index(
> + _auth_id: Option<String>,
> + _language: Option<String>,
> + _api: &ApiConfig,
> + _parts: http::request::Parts,
> +) -> http::Response<hyper::Body> {
> + // build an index page
> + http::Response::builder()
> + .body("hello world".into())
maybe a mini html thingy with the API description from above + links to the GET calls would
be nice to have?
> +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())))
closing parenthesis jungle ^^
Can you at least place the closing one from Ok() one a new line or does rustfmt does not like that?
> + },
> + "example_server",
> + ).await?;
> +
> + Ok(())
> +}
> +
> +fn main() -> Result<(), Error> {
> + pbs_runtime::main(run())
here I agree with Dietmar, avoiding that dependency would be nice for the example.
> +}
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api
2021-09-28 9:11 [pbs-devel] [PATCH proxmox-backup v2 0/3] add rest_server example Dominik Csapak
@ 2021-09-28 9:11 ` Dominik Csapak
2021-09-29 6:41 ` Thomas Lamprecht
0 siblings, 1 reply; 3+ messages in thread
From: Dominik Csapak @ 2021-09-28 9:11 UTC (permalink / raw)
To: pbs-devel
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 <d.csapak@proxmox.com>
---
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 <support@proxmox.com>"]
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<dyn UserInformation + Sync + Send>), 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<String>,
+ _language: Option<String>,
+ _api: &ApiConfig,
+ _parts: http::request::Parts,
+) -> http::Response<hyper::Body> {
+ // 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<String, Error> {
+ Ok("pong".to_string())
+}
+
+lazy_static! {
+ static ref ITEM_MAP: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
+}
+
+#[api]
+/// Lists all current items
+fn list_items() -> Result<Vec<String>, 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<String, Error> {
+ 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
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2021-09-29 6:42 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-29 5:19 [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api Dietmar Maurer
-- strict thread matches above, loose matches on Subject: below --
2021-09-28 9:11 [pbs-devel] [PATCH proxmox-backup v2 0/3] add rest_server example Dominik Csapak
2021-09-28 9:11 ` [pbs-devel] [PATCH proxmox-backup v2 3/3] examples: add example for a simple rest server with a small api Dominik Csapak
2021-09-29 6:41 ` Thomas Lamprecht
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox