1 use std
::collections
::HashMap
;
2 use std
::path
::PathBuf
;
3 use std
::time
::SystemTime
;
5 use std
::sync
::{Arc, Mutex, RwLock}
;
7 use anyhow
::{bail, Error, format_err}
;
8 use hyper
::{Method, Body, Response}
;
9 use hyper
::http
::request
::Parts
;
11 use handlebars
::Handlebars
;
14 use proxmox
::api
::{ApiMethod, Router, RpcEnvironmentType}
;
15 use proxmox
::tools
::fs
::{create_path, CreateOptions}
;
17 use crate::{ApiAuth, FileLogger, FileLogOptions, CommandoSocket}
;
19 pub type GetIndexFn
= fn(Option
<String
>, Option
<String
>, &ApiConfig
, Parts
) -> Response
<Body
>;
21 /// REST server configuration
22 pub struct ApiConfig
{
24 router
: &'
static Router
,
25 aliases
: HashMap
<String
, PathBuf
>,
26 env_type
: RpcEnvironmentType
,
27 templates
: RwLock
<Handlebars
<'
static>>,
28 template_files
: RwLock
<HashMap
<String
, (SystemTime
, PathBuf
)>>,
29 request_log
: Option
<Arc
<Mutex
<FileLogger
>>>,
30 auth_log
: Option
<Arc
<Mutex
<FileLogger
>>>,
31 pub(crate) api_auth
: Arc
<dyn ApiAuth
+ Send
+ Sync
>,
32 get_index_fn
: GetIndexFn
,
36 /// Creates a new instance
38 /// `basedir` - File lookups are relative to this directory.
40 /// `router` - The REST API definition.
42 /// `env_type` - The environment type.
44 /// `api_auth` - The Authentification handler
46 /// `get_index_fn` - callback to generate the root page
47 /// (index). Please note that this fuctions gets a reference to
48 /// the [ApiConfig], so it can use [Handlebars] templates
49 /// ([render_template](Self::render_template) to generate pages.
50 pub fn new
<B
: Into
<PathBuf
>>(
52 router
: &'
static Router
,
53 env_type
: RpcEnvironmentType
,
54 api_auth
: Arc
<dyn ApiAuth
+ Send
+ Sync
>,
55 get_index_fn
: GetIndexFn
,
56 ) -> Result
<Self, Error
> {
58 basedir
: basedir
.into(),
60 aliases
: HashMap
::new(),
62 templates
: RwLock
::new(Handlebars
::new()),
63 template_files
: RwLock
::new(HashMap
::new()),
71 pub(crate) fn get_index(
73 auth_id
: Option
<String
>,
74 language
: Option
<String
>,
77 (self.get_index_fn
)(auth_id
, language
, self, parts
)
80 pub(crate) fn find_method(
84 uri_param
: &mut HashMap
<String
, String
>,
85 ) -> Option
<&'
static ApiMethod
> {
87 self.router
.find_method(components
, method
, uri_param
)
90 pub(crate) fn find_alias(&self, components
: &[&str]) -> PathBuf
{
92 let mut prefix
= String
::new();
93 let mut filename
= self.basedir
.clone();
94 let comp_len
= components
.len();
96 prefix
.push_str(components
[0]);
97 if let Some(subdir
) = self.aliases
.get(&prefix
) {
98 filename
.push(subdir
);
99 components
.iter().skip(1).for_each(|comp
| filename
.push(comp
));
101 components
.iter().for_each(|comp
| filename
.push(comp
));
107 /// Register a path alias
109 /// This can be used to redirect file lookups to a specific
113 /// use proxmox_rest_server::ApiConfig;
114 /// // let mut config = ApiConfig::new(...);
115 /// # fn fake(config: &mut ApiConfig) {
116 /// config.add_alias("extjs", "/usr/share/javascript/extjs");
119 pub fn add_alias
<S
, P
>(&mut self, alias
: S
, path
: P
)
120 where S
: Into
<String
>,
123 self.aliases
.insert(alias
.into(), path
.into());
126 pub(crate) fn env_type(&self) -> RpcEnvironmentType
{
130 /// Register a [Handlebars] template file
132 /// Those templates cane be use with [render_template](Self::render_template) to generate pages.
133 pub fn register_template
<P
>(&self, name
: &str, path
: P
) -> Result
<(), Error
>
137 if self.template_files
.read().unwrap().contains_key(name
) {
138 bail
!("template already registered");
141 let path
: PathBuf
= path
.into();
142 let metadata
= metadata(&path
)?
;
143 let mtime
= metadata
.modified()?
;
145 self.templates
.write().unwrap().register_template_file(name
, &path
)?
;
146 self.template_files
.write().unwrap().insert(name
.to_string(), (mtime
, path
));
151 /// Checks if the template was modified since the last rendering
152 /// if yes, it loads a the new version of the template
153 pub fn render_template
<T
>(&self, name
: &str, data
: &T
) -> Result
<String
, Error
>
160 let template_files
= self.template_files
.read().unwrap();
161 let (old_mtime
, old_path
) = template_files
.get(name
).ok_or_else(|| format_err
!("template not found"))?
;
163 mtime
= metadata(old_path
)?
.modified()?
;
164 if mtime
<= *old_mtime
{
165 return self.templates
.read().unwrap().render(name
, data
).map_err(|err
| format_err
!("{}", err
));
167 path
= old_path
.to_path_buf();
171 let mut template_files
= self.template_files
.write().unwrap();
172 let mut templates
= self.templates
.write().unwrap();
174 templates
.register_template_file(name
, &path
)?
;
175 template_files
.insert(name
.to_string(), (mtime
, path
));
177 templates
.render(name
, data
).map_err(|err
| format_err
!("{}", err
))
181 /// Enable the access log feature
183 /// When enabled, all requests are logged to the specified file.
184 /// This function also registers a `api-access-log-reopen`
185 /// command one the [CommandoSocket].
186 pub fn enable_access_log
<P
>(
189 dir_opts
: Option
<CreateOptions
>,
190 file_opts
: Option
<CreateOptions
>,
191 commando_sock
: &mut CommandoSocket
,
192 ) -> Result
<(), Error
>
196 let path
: PathBuf
= path
.into();
197 if let Some(base
) = path
.parent() {
199 create_path(base
, None
, dir_opts
).map_err(|err
| format_err
!("{}", err
))?
;
203 let logger_options
= FileLogOptions
{
205 file_opts
: file_opts
.unwrap_or(CreateOptions
::default()),
208 let request_log
= Arc
::new(Mutex
::new(FileLogger
::new(&path
, logger_options
)?
));
209 self.request_log
= Some(Arc
::clone(&request_log
));
211 commando_sock
.register_command("api-access-log-reopen".into(), move |_args
| {
212 println
!("re-opening access-log file");
213 request_log
.lock().unwrap().reopen()?
;
214 Ok(serde_json
::Value
::Null
)
220 /// Enable the authentification log feature
222 /// When enabled, all authentification requests are logged to the
223 /// specified file. This function also registers a
224 /// `api-auth-log-reopen` command one the [CommandoSocket].
225 pub fn enable_auth_log
<P
>(
228 dir_opts
: Option
<CreateOptions
>,
229 file_opts
: Option
<CreateOptions
>,
230 commando_sock
: &mut CommandoSocket
,
231 ) -> Result
<(), Error
>
235 let path
: PathBuf
= path
.into();
236 if let Some(base
) = path
.parent() {
238 create_path(base
, None
, dir_opts
).map_err(|err
| format_err
!("{}", err
))?
;
242 let logger_options
= FileLogOptions
{
245 file_opts
: file_opts
.unwrap_or(CreateOptions
::default()),
248 let auth_log
= Arc
::new(Mutex
::new(FileLogger
::new(&path
, logger_options
)?
));
249 self.auth_log
= Some(Arc
::clone(&auth_log
));
251 commando_sock
.register_command("api-auth-log-reopen".into(), move |_args
| {
252 println
!("re-opening auth-log file");
253 auth_log
.lock().unwrap().reopen()?
;
254 Ok(serde_json
::Value
::Null
)
260 pub(crate) fn get_access_log(&self) -> Option
<&Arc
<Mutex
<FileLogger
>>> {
261 self.request_log
.as_ref()
264 pub(crate) fn get_auth_log(&self) -> Option
<&Arc
<Mutex
<FileLogger
>>> {
265 self.auth_log
.as_ref()