]> git.proxmox.com Git - proxmox-backup.git/blob - proxmox-rest-server/examples/minimal-rest-server.rs
b1ef933530479ce6cc323afe0dc4fdb968167a2b
[proxmox-backup.git] / proxmox-rest-server / examples / minimal-rest-server.rs
1 use std::sync::{Arc, Mutex};
2 use std::collections::HashMap;
3 use std::future::Future;
4 use std::pin::Pin;
5
6 use anyhow::{bail, format_err, Error};
7 use lazy_static::lazy_static;
8
9 use proxmox::api::{api, router::SubdirMap, Router, RpcEnvironmentType, UserInformation};
10 use proxmox::list_subdirs_api_method;
11 use proxmox_rest_server::{ApiAuth, ApiConfig, AuthError, RestServer, RestEnvironment};
12 // Create a Dummy User info and auth system
13 // Normally this would check and authenticate the user
14 struct DummyUserInfo;
15
16 impl UserInformation for DummyUserInfo {
17 fn is_superuser(&self, _userid: &str) -> bool {
18 true
19 }
20 fn is_group_member(&self, _userid: &str, group: &str) -> bool {
21 group == "Group"
22 }
23 fn lookup_privs(&self, _userid: &str, _path: &[&str]) -> u64 {
24 u64::MAX
25 }
26 }
27
28 struct DummyAuth;
29
30 impl ApiAuth for DummyAuth {
31 fn check_auth<'a>(
32 &'a self,
33 _headers: &'a http::HeaderMap,
34 _method: &'a hyper::Method,
35 ) -> Pin<Box<dyn Future<Output = Result<(String, Box<dyn UserInformation + Sync + Send>), AuthError>> + Send + 'a>> {
36 Box::pin(async move {
37 // get some global/cached userinfo
38 let userinfo: Box<dyn UserInformation + Sync + Send> = Box::new(DummyUserInfo);
39 // Do some user checks, e.g. cookie/csrf
40 Ok(("User".to_string(), userinfo))
41 })
42 }
43 }
44
45 // this should return the index page of the webserver
46 // iow. what the user browses to
47
48 fn get_index<'a>(
49 _env: RestEnvironment,
50 _parts: http::request::Parts,
51 ) -> Pin<Box<dyn Future<Output = http::Response<hyper::Body>> + Send + 'a>> {
52 Box::pin(async move {
53 // build an index page
54 http::Response::builder()
55 .body("hello world".into())
56 .unwrap()
57 })
58 }
59
60 // a few examples on how to do api calls with the Router
61
62 #[api]
63 /// A simple ping method. returns "pong"
64 fn ping() -> Result<String, Error> {
65 Ok("pong".to_string())
66 }
67
68 lazy_static! {
69 static ref ITEM_MAP: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
70 }
71
72 #[api]
73 /// Lists all current items
74 fn list_items() -> Result<Vec<String>, Error> {
75 Ok(ITEM_MAP.lock().unwrap().keys().map(|k| k.clone()).collect())
76 }
77
78 #[api(
79 input: {
80 properties: {
81 name: {
82 type: String,
83 description: "The name",
84 },
85 value: {
86 type: String,
87 description: "The value",
88 },
89 },
90 },
91 )]
92 /// creates a new item
93 fn create_item(name: String, value: String) -> Result<(), Error> {
94 let mut map = ITEM_MAP.lock().unwrap();
95 if map.contains_key(&name) {
96 bail!("{} already exists", name);
97 }
98
99 map.insert(name, value);
100
101 Ok(())
102 }
103
104 #[api(
105 input: {
106 properties: {
107 name: {
108 type: String,
109 description: "The name",
110 },
111 },
112 },
113 )]
114 /// returns the value of an item
115 fn get_item(name: String) -> Result<String, Error> {
116 ITEM_MAP.lock().unwrap().get(&name).map(|s| s.to_string()).ok_or_else(|| format_err!("no such item '{}'", name))
117 }
118
119 #[api(
120 input: {
121 properties: {
122 name: {
123 type: String,
124 description: "The name",
125 },
126 value: {
127 type: String,
128 description: "The value",
129 },
130 },
131 },
132 )]
133 /// updates an item
134 fn update_item(name: String, value: String) -> Result<(), Error> {
135 if let Some(val) = ITEM_MAP.lock().unwrap().get_mut(&name) {
136 *val = value;
137 } else {
138 bail!("no such item '{}'", name);
139 }
140 Ok(())
141 }
142
143 #[api(
144 input: {
145 properties: {
146 name: {
147 type: String,
148 description: "The name",
149 },
150 },
151 },
152 )]
153 /// deletes an item
154 fn delete_item(name: String) -> Result<(), Error> {
155 if ITEM_MAP.lock().unwrap().remove(&name).is_none() {
156 bail!("no such item '{}'", name);
157 }
158 Ok(())
159 }
160
161 const ITEM_ROUTER: Router = Router::new()
162 .get(&API_METHOD_GET_ITEM)
163 .put(&API_METHOD_UPDATE_ITEM)
164 .delete(&API_METHOD_DELETE_ITEM);
165
166 const SUBDIRS: SubdirMap = &[
167 (
168 "items",
169 &Router::new()
170 .get(&API_METHOD_LIST_ITEMS)
171 .post(&API_METHOD_CREATE_ITEM)
172 .match_all("name", &ITEM_ROUTER)
173 ),
174 (
175 "ping",
176 &Router::new()
177 .get(&API_METHOD_PING)
178 ),
179 ];
180
181 const ROUTER: Router = Router::new()
182 .get(&list_subdirs_api_method!(SUBDIRS))
183 .subdirs(SUBDIRS);
184
185 async fn run() -> Result<(), Error> {
186
187 // we first have to configure the api environment (basedir etc.)
188
189 let config = ApiConfig::new(
190 "/var/tmp/",
191 &ROUTER,
192 RpcEnvironmentType::PUBLIC,
193 Arc::new(DummyAuth {}),
194 &get_index,
195 )?;
196 let rest_server = RestServer::new(config);
197
198 // then we have to create a daemon that listens, accepts and serves
199 // the api to clients
200 proxmox_rest_server::daemon::create_daemon(
201 ([127, 0, 0, 1], 65000).into(),
202 move |listener| {
203 let incoming = hyper::server::conn::AddrIncoming::from_listener(listener)?;
204
205 Ok(async move {
206
207 hyper::Server::builder(incoming)
208 .serve(rest_server)
209 .await?;
210
211 Ok(())
212 })
213 },
214 ).await?;
215
216 Ok(())
217 }
218
219 fn main() -> Result<(), Error> {
220 let rt = tokio::runtime::Runtime::new()?;
221 rt.block_on(async { run().await })
222 }