]> git.proxmox.com Git - proxmox-backup.git/blame - src/main.rs
remove more test code, cleanups
[proxmox-backup.git] / src / main.rs
CommitLineData
763220ce 1extern crate apitest;
d8d978eb 2
6d77fb40 3use failure::*;
a9696f7b 4use std::sync::Arc;
1a53be14 5//use std::collections::HashMap;
035cce94 6//use std::io;
a974251e
DM
7//use std::fs;
8use std::path::{PathBuf};
d11f14f7 9
c819ec8d 10//use std::collections::HashMap;
504b3597 11use lazy_static::lazy_static;
28e47cea 12
504b3597 13//use apitest::json_schema::*;
886e5ce8 14use apitest::api_info::*;
6d77fb40 15use apitest::json_schema::*;
1a53be14 16use apitest::api_server::*;
886e5ce8 17
504b3597 18//use serde_derive::{Serialize, Deserialize};
805aec15
DM
19use serde_json::{json, Value};
20
a974251e 21use futures::future::{self, Either};
579fbe7d
DM
22//use tokio::prelude::*;
23//use tokio::timer::Delay;
24use tokio::fs::File;
25use tokio_codec;
26//use bytes::{BytesMut, BufMut};
22f0adf2 27
c819ec8d 28//use hyper::body::Payload;
b82472c0 29use hyper::http::request::Parts;
1a53be14 30use hyper::{Body, Request, Response, Server, StatusCode};
b82472c0 31use hyper::rt::{Future, Stream};
324a5bd0 32use hyper::service::service_fn;
3cdec2a0 33use hyper::header;
b82472c0 34
579fbe7d 35//use std::time::{Duration, Instant};
b82472c0 36
260c1ee8 37type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>;
d8d978eb 38
260c1ee8 39macro_rules! error_response {
28e47cea
DM
40 ($status:ident, $msg:expr) => {{
41 let mut resp = Response::new(Body::from($msg));
42 *resp.status_mut() = StatusCode::$status;
260c1ee8 43 resp
b82472c0
DM
44 }}
45}
260c1ee8 46
b82472c0
DM
47macro_rules! http_error_future {
48 ($status:ident, $msg:expr) => {{
260c1ee8 49 let resp = error_response!($status, $msg);
a974251e 50 return Box::new(future::ok(resp));
28e47cea
DM
51 }}
52}
53
805aec15 54fn get_request_parameters_async<'a>(
c819ec8d
DM
55 info: &'a ApiMethod,
56 parts: Parts,
57 req_body: Body,
805aec15 58) -> Box<Future<Item = Value, Error = failure::Error> + Send + 'a>
b82472c0 59{
cec9f02e 60 let resp = req_body
a974251e 61 .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("Promlems reading request body: {}", err))))
cec9f02e
DM
62 .fold(Vec::new(), |mut acc, chunk| {
63 if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size?
64 acc.extend_from_slice(&*chunk);
2c10fd5c 65 Ok(acc)
cec9f02e 66 }
2c10fd5c 67 else { Err(Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("Request body too large")))) }
cec9f02e 68 })
260c1ee8 69 .and_then(move |body| {
b82472c0 70
260c1ee8 71 let bytes = String::from_utf8(body.to_vec())?; // why copy??
b82472c0 72
260c1ee8 73 println!("GOT BODY {:?}", bytes);
b82472c0 74
260c1ee8 75 let mut test_required = true;
b82472c0 76
260c1ee8 77 let mut params = json!({});
c819ec8d 78
260c1ee8
DM
79 if bytes.len() > 0 {
80 params = parse_query_string(&bytes, &info.parameters, true)?;
81 test_required = false;
82 }
c819ec8d 83
260c1ee8
DM
84 if let Some(query_str) = parts.uri.query() {
85 let query_params = parse_query_string(query_str, &info.parameters, test_required)?;
c819ec8d 86
260c1ee8
DM
87 for (k, v) in query_params.as_object().unwrap() {
88 params[k] = v.clone(); // fixme: why clone()??
89 }
b82472c0 90 }
b82472c0 91
805aec15
DM
92 println!("GOT PARAMS {}", params);
93 Ok(params)
94 });
95
96 Box::new(resp)
97}
98
805aec15
DM
99fn handle_sync_api_request<'a>(
100 info: &'a ApiMethod,
101 parts: Parts,
102 req_body: Body,
103) -> Box<Future<Item = Response<Body>, Error = failure::Error> + Send + 'a>
104{
105 let params = get_request_parameters_async(info, parts, req_body);
106
107 let resp = params
108 .and_then(move |params| {
109
260c1ee8
DM
110 println!("GOT PARAMS {}", params);
111
805aec15
DM
112 /*
113 let when = Instant::now() + Duration::from_millis(3000);
114 let task = Delay::new(when).then(|_| {
115 println!("A LAZY TASK");
116 ok(())
117 });
118
119 tokio::spawn(task);
120 */
121
260c1ee8 122 let res = (info.handler)(params, info)?;
c819ec8d 123
260c1ee8 124 Ok(res)
c819ec8d 125
805aec15
DM
126 }).then(|result| {
127 match result {
128 Ok(ref value) => {
129 let json_str = value.to_string();
c819ec8d 130
805aec15
DM
131 Ok(Response::builder()
132 .status(StatusCode::OK)
3cdec2a0 133 .header(header::CONTENT_TYPE, "application/json")
805aec15
DM
134 .body(Body::from(json_str))?)
135 }
136 Err(err) => Ok(error_response!(BAD_REQUEST, err.to_string()))
260c1ee8 137 }
805aec15 138 });
b82472c0
DM
139
140 Box::new(resp)
141}
142
a0efdca1
DM
143fn simple_static_file_download(filename: PathBuf) -> BoxFut {
144
145 Box::new(File::open(filename)
a974251e 146 .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File open failed: {}", err))))
a0efdca1
DM
147 .and_then(|file| {
148 let buf: Vec<u8> = Vec::new();
149 tokio::io::read_to_end(file, buf)
a974251e 150 .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File read failed: {}", err))))
a0efdca1
DM
151 .and_then(|data| Ok(Response::new(data.1.into())))
152 }))
153}
154
155fn chuncked_static_file_download(filename: PathBuf) -> BoxFut {
156
157 Box::new(File::open(filename)
a974251e 158 .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File open failed: {}", err))))
a0efdca1
DM
159 .and_then(|file| {
160 let payload = tokio_codec::FramedRead::new(file, tokio_codec::BytesCodec::new()).
161 map(|bytes| {
162 //sigh - howto avoid copy here? or the whole map() ??
163 hyper::Chunk::from(bytes.to_vec())
164 });
165 let body = Body::wrap_stream(payload);
166 // fixme: set content type and other headers
167 Ok(Response::builder()
168 .status(StatusCode::OK)
169 .body(body)
170 .unwrap())
171 }))
172}
173
579fbe7d
DM
174fn handle_static_file_download(filename: PathBuf) -> BoxFut {
175
78d0783b 176 let response = tokio::fs::metadata(filename.clone())
a974251e 177 .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File access problems: {}", err))))
78d0783b 178 .and_then(|metadata| {
a0efdca1
DM
179 if metadata.len() < 1024*32 {
180 Either::A(simple_static_file_download(filename))
78d0783b 181 } else {
a0efdca1
DM
182 Either::B(chuncked_static_file_download(filename))
183 }
579fbe7d
DM
184 });
185
186 return Box::new(response);
187}
188
5599e263 189fn handle_request(api: Arc<ApiServer>, req: Request<Body>) -> BoxFut {
b82472c0
DM
190
191 let (parts, body) = req.into_parts();
28e47cea 192
b82472c0
DM
193 let method = parts.method.clone();
194 let path = parts.uri.path();
886e5ce8 195
579fbe7d 196 // normalize path
198fab6f
DM
197 // do not allow ".", "..", or hidden files ".XXXX"
198 // also remove empty path components
199
200 let items = path.split('/');
201 let mut path = String::new();
202 let mut components = vec![];
203
204 for name in items {
205 if name.is_empty() { continue; }
206 if name.starts_with(".") {
207 http_error_future!(BAD_REQUEST, "Path contains illegal components.\n");
208 }
209 path.push('/');
210 path.push_str(name);
211 components.push(name);
212 }
213
28e47cea 214 let comp_len = components.len();
886e5ce8
DM
215
216 println!("REQUEST {} {}", method, path);
28e47cea
DM
217 println!("COMPO {:?}", components);
218
219 if comp_len >= 1 && components[0] == "api3" {
220 println!("GOT API REQUEST");
221 if comp_len >= 2 {
222 let format = components[1];
223 if format != "json" {
6639c14b 224 http_error_future!(BAD_REQUEST, format!("Unsupported output format '{}'.", format))
28e47cea
DM
225 }
226
1a53be14 227 if let Some(api_method) = api.find_method(&components[2..], method) {
4beaacb6 228 // fixme: handle auth
98d82428 229 return handle_sync_api_request(api_method, parts, body);
28e47cea
DM
230 }
231 }
035cce94
DM
232 } else {
233 // not Auth for accessing files!
234
1a53be14 235 let filename = api.find_alias(&components);
035cce94 236 return handle_static_file_download(filename);
28e47cea 237 }
886e5ce8 238
579fbe7d
DM
239
240 http_error_future!(NOT_FOUND, "Path not found.")
241 //Box::new(ok(Response::new(Body::from("RETURN WEB GUI\n"))))
886e5ce8
DM
242}
243
d8d978eb
DM
244fn main() {
245 println!("Fast Static Type Definitions 1");
246
886e5ce8
DM
247 let addr = ([127, 0, 0, 1], 8007).into();
248
a9696f7b 249 lazy_static!{
1a53be14 250 static ref ROUTER: MethodInfo = apitest::api3::router();
a9696f7b 251 }
324a5bd0 252
1a53be14
DM
253 let mut api_server = ApiServer::new("/var/www", &ROUTER);
254
255 // add default dirs which includes jquery and bootstrap
256 // my $base = '/usr/share/libpve-http-server-perl';
257 // add_dirs($self->{dirs}, '/css/' => "$base/css/");
258 // add_dirs($self->{dirs}, '/js/' => "$base/js/");
259 // add_dirs($self->{dirs}, '/fonts/' => "$base/fonts/");
260 api_server.add_alias("novnc", "/usr/share/novnc-pve");
261 api_server.add_alias("extjs", "/usr/share/javascript/extjs");
262 api_server.add_alias("fontawesome", "/usr/share/fonts-font-awesome");
263 api_server.add_alias("xtermjs", "/usr/share/pve-xtermjs");
264 api_server.add_alias("widgettoolkit", "/usr/share/javascript/proxmox-widget-toolkit");
265
1a53be14 266 let api_server = Arc::new(api_server);
5599e263 267
a9696f7b 268 let new_svc = move || {
324a5bd0 269
5599e263 270 let api_server = api_server.clone();
324a5bd0 271
a9696f7b 272 service_fn(move |req| {
5599e263 273 handle_request(api_server.clone(), req).then(|result| {
324a5bd0
DM
274 match result {
275 Ok(res) => Ok::<_,String>(res),
276 Err(err) => {
277 if let Some(apierr) = err.downcast_ref::<ApiError>() {
278 let mut resp = Response::new(Body::from(apierr.message.clone()));
279 *resp.status_mut() = apierr.code;
280 Ok(resp)
281 } else {
282 Ok(error_response!(BAD_REQUEST, err.to_string()))
283 }
284 }
285 }
286 })
287 })
886e5ce8
DM
288 };
289
290 let server = Server::bind(&addr)
291 .serve(new_svc)
292 .map_err(|e| eprintln!("server error: {}", e));
293
294 // Run this server for... forever!
295 hyper::rt::run(server);
d8d978eb 296}