]>
Commit | Line | Data |
---|---|---|
20def38e DC |
1 | use std::sync::{Arc, Mutex}; |
2 | use std::collections::HashMap; | |
038f3850 DM |
3 | use std::future::Future; |
4 | use std::pin::Pin; | |
20def38e DC |
5 | |
6 | use anyhow::{bail, format_err, Error}; | |
20def38e DC |
7 | use lazy_static::lazy_static; |
8 | ||
9 | use proxmox::api::{api, router::SubdirMap, Router, RpcEnvironmentType, UserInformation}; | |
10 | use proxmox::list_subdirs_api_method; | |
48176b0a | 11 | use proxmox_rest_server::{ApiAuth, ApiConfig, AuthError, RestServer, RestEnvironment}; |
20def38e DC |
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 { | |
038f3850 DM |
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 | }) | |
20def38e DC |
42 | } |
43 | } | |
44 | ||
45 | // this should return the index page of the webserver | |
46 | // iow. what the user browses to | |
47 | ||
6680878b | 48 | fn get_index<'a>( |
48176b0a | 49 | _env: RestEnvironment, |
20def38e | 50 | _parts: http::request::Parts, |
6680878b DM |
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 | }) | |
20def38e DC |
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 {}), | |
6680878b | 194 | &get_index, |
20def38e DC |
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(), | |
d2654200 | 202 | move |listener| { |
20def38e DC |
203 | let incoming = hyper::server::conn::AddrIncoming::from_listener(listener)?; |
204 | ||
d2654200 DM |
205 | Ok(async move { |
206 | ||
207 | hyper::Server::builder(incoming) | |
20def38e | 208 | .serve(rest_server) |
d2654200 DM |
209 | .await?; |
210 | ||
211 | Ok(()) | |
212 | }) | |
20def38e | 213 | }, |
20def38e DC |
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 | } |