public inbox for pbs-devel@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal