all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal