]>
Commit | Line | Data |
---|---|---|
763220ce | 1 | extern crate apitest; |
d8d978eb | 2 | |
6d77fb40 | 3 | use failure::*; |
ef1f0e65 | 4 | use std::collections::HashMap; |
d11f14f7 | 5 | |
c819ec8d | 6 | //use std::collections::HashMap; |
504b3597 | 7 | use lazy_static::lazy_static; |
28e47cea | 8 | |
504b3597 | 9 | //use apitest::json_schema::*; |
886e5ce8 | 10 | use apitest::api_info::*; |
6d77fb40 | 11 | use apitest::json_schema::*; |
886e5ce8 | 12 | |
504b3597 | 13 | //use serde_derive::{Serialize, Deserialize}; |
805aec15 DM |
14 | use serde_json::{json, Value}; |
15 | ||
16 | use tokio::prelude::*; | |
17 | use tokio::timer::Delay; | |
22f0adf2 | 18 | |
c819ec8d | 19 | //use hyper::body::Payload; |
b82472c0 | 20 | use hyper::http::request::Parts; |
28e47cea | 21 | use hyper::{Method, Body, Request, Response, Server, StatusCode}; |
b82472c0 DM |
22 | use hyper::rt::{Future, Stream}; |
23 | use hyper::service::service_fn; | |
3cdec2a0 | 24 | use hyper::header; |
b82472c0 | 25 | |
805aec15 DM |
26 | use futures::future::*; |
27 | ||
28 | use std::time::{Duration, Instant}; | |
b82472c0 | 29 | |
260c1ee8 | 30 | type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>; |
d8d978eb | 31 | |
260c1ee8 | 32 | macro_rules! error_response { |
28e47cea DM |
33 | ($status:ident, $msg:expr) => {{ |
34 | let mut resp = Response::new(Body::from($msg)); | |
35 | *resp.status_mut() = StatusCode::$status; | |
260c1ee8 | 36 | resp |
b82472c0 DM |
37 | }} |
38 | } | |
260c1ee8 | 39 | |
b82472c0 DM |
40 | macro_rules! http_error_future { |
41 | ($status:ident, $msg:expr) => {{ | |
260c1ee8 | 42 | let resp = error_response!($status, $msg); |
805aec15 | 43 | return Box::new(ok(resp)); |
28e47cea DM |
44 | }} |
45 | } | |
46 | ||
805aec15 | 47 | fn get_request_parameters_async<'a>( |
c819ec8d DM |
48 | info: &'a ApiMethod, |
49 | parts: Parts, | |
50 | req_body: Body, | |
805aec15 | 51 | ) -> Box<Future<Item = Value, Error = failure::Error> + Send + 'a> |
b82472c0 | 52 | { |
cec9f02e | 53 | let resp = req_body |
260c1ee8 | 54 | .map_err(|err| format_err!("Promlems reading request body: {}", err)) |
cec9f02e DM |
55 | .fold(Vec::new(), |mut acc, chunk| { |
56 | if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size? | |
57 | acc.extend_from_slice(&*chunk); | |
58 | ok(acc) | |
59 | } else { | |
60 | err(format_err!("Request body too large")) | |
61 | } | |
62 | }) | |
260c1ee8 | 63 | .and_then(move |body| { |
b82472c0 | 64 | |
260c1ee8 | 65 | let bytes = String::from_utf8(body.to_vec())?; // why copy?? |
b82472c0 | 66 | |
260c1ee8 | 67 | println!("GOT BODY {:?}", bytes); |
b82472c0 | 68 | |
260c1ee8 | 69 | let mut test_required = true; |
b82472c0 | 70 | |
260c1ee8 | 71 | let mut params = json!({}); |
c819ec8d | 72 | |
260c1ee8 DM |
73 | if bytes.len() > 0 { |
74 | params = parse_query_string(&bytes, &info.parameters, true)?; | |
75 | test_required = false; | |
76 | } | |
c819ec8d | 77 | |
260c1ee8 DM |
78 | if let Some(query_str) = parts.uri.query() { |
79 | let query_params = parse_query_string(query_str, &info.parameters, test_required)?; | |
c819ec8d | 80 | |
260c1ee8 DM |
81 | for (k, v) in query_params.as_object().unwrap() { |
82 | params[k] = v.clone(); // fixme: why clone()?? | |
83 | } | |
b82472c0 | 84 | } |
b82472c0 | 85 | |
805aec15 DM |
86 | println!("GOT PARAMS {}", params); |
87 | Ok(params) | |
88 | }); | |
89 | ||
90 | Box::new(resp) | |
91 | } | |
92 | ||
93 | fn handle_async_api_request<'a>( | |
94 | info: &'a ApiMethod, | |
95 | parts: Parts, | |
96 | req_body: Body, | |
97 | ) -> Box<Future<Item = Response<Body>, Error = failure::Error> + Send + 'a> | |
98 | { | |
99 | let params = get_request_parameters_async(info, parts, req_body); | |
100 | ||
101 | let resp = params | |
102 | .and_then(move |params| { | |
103 | ||
104 | println!("GOT PARAMS {}", params); | |
105 | ||
106 | /* | |
107 | let when = Instant::now() + Duration::from_millis(3000); | |
108 | let task = Delay::new(when).then(|_| { | |
109 | println!("A LAZY TASK"); | |
110 | ok(()) | |
111 | }); | |
112 | ||
113 | tokio::spawn(task); | |
114 | */ | |
115 | ||
116 | (info.async_handler)(params, info) | |
117 | }); | |
118 | ||
119 | Box::new(resp) | |
120 | } | |
121 | ||
122 | fn handle_sync_api_request<'a>( | |
123 | info: &'a ApiMethod, | |
124 | parts: Parts, | |
125 | req_body: Body, | |
126 | ) -> Box<Future<Item = Response<Body>, Error = failure::Error> + Send + 'a> | |
127 | { | |
128 | let params = get_request_parameters_async(info, parts, req_body); | |
129 | ||
130 | let resp = params | |
131 | .and_then(move |params| { | |
132 | ||
260c1ee8 DM |
133 | println!("GOT PARAMS {}", params); |
134 | ||
805aec15 DM |
135 | /* |
136 | let when = Instant::now() + Duration::from_millis(3000); | |
137 | let task = Delay::new(when).then(|_| { | |
138 | println!("A LAZY TASK"); | |
139 | ok(()) | |
140 | }); | |
141 | ||
142 | tokio::spawn(task); | |
143 | */ | |
144 | ||
260c1ee8 | 145 | let res = (info.handler)(params, info)?; |
c819ec8d | 146 | |
260c1ee8 | 147 | Ok(res) |
c819ec8d | 148 | |
805aec15 DM |
149 | }).then(|result| { |
150 | match result { | |
151 | Ok(ref value) => { | |
152 | let json_str = value.to_string(); | |
c819ec8d | 153 | |
805aec15 DM |
154 | Ok(Response::builder() |
155 | .status(StatusCode::OK) | |
3cdec2a0 | 156 | .header(header::CONTENT_TYPE, "application/json") |
805aec15 DM |
157 | .body(Body::from(json_str))?) |
158 | } | |
159 | Err(err) => Ok(error_response!(BAD_REQUEST, err.to_string())) | |
260c1ee8 | 160 | } |
805aec15 | 161 | }); |
b82472c0 DM |
162 | |
163 | Box::new(resp) | |
164 | } | |
165 | ||
166 | fn handle_request(req: Request<Body>) -> BoxFut { | |
167 | ||
168 | let (parts, body) = req.into_parts(); | |
28e47cea | 169 | |
b82472c0 DM |
170 | let method = parts.method.clone(); |
171 | let path = parts.uri.path(); | |
886e5ce8 | 172 | |
28e47cea DM |
173 | let components: Vec<&str> = path.split('/').filter(|x| !x.is_empty()).collect(); |
174 | let comp_len = components.len(); | |
886e5ce8 DM |
175 | |
176 | println!("REQUEST {} {}", method, path); | |
28e47cea DM |
177 | println!("COMPO {:?}", components); |
178 | ||
179 | if comp_len >= 1 && components[0] == "api3" { | |
180 | println!("GOT API REQUEST"); | |
181 | if comp_len >= 2 { | |
182 | let format = components[1]; | |
183 | if format != "json" { | |
6639c14b | 184 | http_error_future!(BAD_REQUEST, format!("Unsupported output format '{}'.", format)) |
28e47cea DM |
185 | } |
186 | ||
0dde2f04 | 187 | if let Some(info) = ROUTER.find_method(&components[2..]) { |
28e47cea DM |
188 | println!("FOUND INFO"); |
189 | let api_method_opt = match method { | |
b82472c0 DM |
190 | Method::GET => &info.get, |
191 | Method::PUT => &info.put, | |
192 | Method::POST => &info.post, | |
193 | Method::DELETE => &info.delete, | |
0dde2f04 | 194 | _ => &None, |
28e47cea | 195 | }; |
bfcba4fd | 196 | let api_method = match api_method_opt { |
28e47cea | 197 | Some(m) => m, |
6639c14b | 198 | _ => http_error_future!(NOT_FOUND, format!("No such method '{}'.", method)), |
28e47cea DM |
199 | }; |
200 | ||
4beaacb6 | 201 | // fixme: handle auth |
28e47cea | 202 | |
805aec15 DM |
203 | //return handle_sync_api_request(api_method, parts, body); |
204 | return handle_async_api_request(api_method, parts, body); | |
b82472c0 | 205 | |
28e47cea | 206 | } else { |
6639c14b | 207 | http_error_future!(NOT_FOUND, "Path not found."); |
28e47cea DM |
208 | } |
209 | } | |
210 | } | |
886e5ce8 | 211 | |
805aec15 | 212 | Box::new(ok(Response::new(Body::from("RETURN WEB GUI\n")))) |
886e5ce8 DM |
213 | } |
214 | ||
ef1f0e65 DM |
215 | // add default dirs which includes jquery and bootstrap |
216 | // my $base = '/usr/share/libpve-http-server-perl'; | |
217 | // add_dirs($self->{dirs}, '/css/' => "$base/css/"); | |
218 | // add_dirs($self->{dirs}, '/js/' => "$base/js/"); | |
219 | // add_dirs($self->{dirs}, '/fonts/' => "$base/fonts/"); | |
220 | ||
221 | use std::io; | |
222 | use std::fs::{self, DirEntry}; | |
223 | use std::path::{Path, PathBuf}; | |
224 | ||
225 | fn add_dirs(cache: &mut HashMap<String, PathBuf>, alias: String, path: &Path) -> Result<(), Error> { | |
226 | ||
227 | if path.is_dir() { | |
228 | for direntry in fs::read_dir(path)? { | |
229 | let entry = direntry?; | |
230 | let entry_path = entry.path(); | |
231 | let file_type = entry.file_type()?; | |
232 | if let Some(file_name) = entry_path.file_name() { | |
233 | let newalias = alias.clone() + &String::from(file_name.to_string_lossy()); // fixme | |
234 | if file_type.is_dir() { | |
235 | add_dirs(cache, newalias, entry_path.as_path())?; | |
236 | } else if file_type.is_file() { | |
237 | cache.insert(newalias, entry_path); | |
238 | } | |
239 | } | |
240 | } | |
241 | } | |
242 | Ok(()) | |
243 | } | |
244 | ||
245 | fn initialize_directory_cache() -> HashMap<String, PathBuf> { | |
246 | ||
247 | let mut basedirs = HashMap::new(); | |
248 | ||
249 | basedirs.insert("novnc", Path::new("/usr/share/novnc-pve")); | |
250 | basedirs.insert("extjs", Path::new("/usr/share/javascript/extjs")); | |
251 | basedirs.insert("fontawesome", Path::new("/usr/share/fonts-font-awesome")); | |
252 | basedirs.insert("xtermjs", Path::new("/usr/share/pve-xtermjs")); | |
253 | basedirs.insert("widgettoolkit", Path::new("/usr/share/javascript/proxmox-widget-toolkit")); | |
254 | ||
255 | let mut cache = HashMap::new(); | |
256 | ||
257 | add_dirs(&mut cache, "/pve2/ext6/".into(), basedirs["extjs"]); | |
258 | ||
259 | cache | |
260 | } | |
261 | ||
262 | lazy_static!{ | |
263 | static ref CACHED_DIRS: HashMap<String, PathBuf> = initialize_directory_cache(); | |
264 | } | |
265 | ||
504b3597 | 266 | lazy_static!{ |
0dde2f04 | 267 | static ref ROUTER: MethodInfo = apitest::api3::router(); |
504b3597 DM |
268 | } |
269 | ||
d8d978eb DM |
270 | fn main() { |
271 | println!("Fast Static Type Definitions 1"); | |
272 | ||
ef1f0e65 DM |
273 | let mut count = 0; |
274 | for (k, v) in CACHED_DIRS.iter() { | |
275 | println!("DIRCACHE: {:?} => {:?}", k, v); | |
276 | count += 1; | |
277 | } | |
278 | println!("Dircache contains {} entries.", count); | |
279 | ||
886e5ce8 DM |
280 | let addr = ([127, 0, 0, 1], 8007).into(); |
281 | ||
282 | let new_svc = || { | |
260c1ee8 DM |
283 | service_fn(|req| { |
284 | // clumsy way to convert failure::Error to Response | |
285 | handle_request(req).then(|result| -> Result<Response<Body>, String> { | |
286 | match result { | |
287 | Ok(res) => Ok(res), | |
6639c14b | 288 | Err(err) => Ok(error_response!(BAD_REQUEST, err.to_string())), |
260c1ee8 DM |
289 | } |
290 | }) | |
291 | }) | |
886e5ce8 DM |
292 | }; |
293 | ||
294 | let server = Server::bind(&addr) | |
295 | .serve(new_svc) | |
296 | .map_err(|e| eprintln!("server error: {}", e)); | |
297 | ||
298 | // Run this server for... forever! | |
299 | hyper::rt::run(server); | |
d8d978eb | 300 | } |