]>
Commit | Line | Data |
---|---|---|
763220ce | 1 | extern crate apitest; |
d8d978eb | 2 | |
6d77fb40 | 3 | use failure::*; |
a9696f7b | 4 | use std::sync::Arc; |
1a53be14 | 5 | //use std::collections::HashMap; |
035cce94 | 6 | //use std::io; |
a974251e DM |
7 | //use std::fs; |
8 | use std::path::{PathBuf}; | |
d11f14f7 | 9 | |
c819ec8d | 10 | //use std::collections::HashMap; |
504b3597 | 11 | use lazy_static::lazy_static; |
28e47cea | 12 | |
504b3597 | 13 | //use apitest::json_schema::*; |
886e5ce8 | 14 | use apitest::api_info::*; |
6d77fb40 | 15 | use apitest::json_schema::*; |
1a53be14 | 16 | use apitest::api_server::*; |
886e5ce8 | 17 | |
504b3597 | 18 | //use serde_derive::{Serialize, Deserialize}; |
805aec15 DM |
19 | use serde_json::{json, Value}; |
20 | ||
a974251e | 21 | use futures::future::{self, Either}; |
579fbe7d DM |
22 | //use tokio::prelude::*; |
23 | //use tokio::timer::Delay; | |
24 | use tokio::fs::File; | |
25 | use tokio_codec; | |
26 | //use bytes::{BytesMut, BufMut}; | |
22f0adf2 | 27 | |
c819ec8d | 28 | //use hyper::body::Payload; |
b82472c0 | 29 | use hyper::http::request::Parts; |
1a53be14 | 30 | use hyper::{Body, Request, Response, Server, StatusCode}; |
b82472c0 | 31 | use hyper::rt::{Future, Stream}; |
324a5bd0 | 32 | use hyper::service::service_fn; |
3cdec2a0 | 33 | use hyper::header; |
b82472c0 | 34 | |
579fbe7d | 35 | //use std::time::{Duration, Instant}; |
b82472c0 | 36 | |
260c1ee8 | 37 | type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>; |
d8d978eb | 38 | |
260c1ee8 | 39 | macro_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 |
47 | macro_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 | 54 | fn 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 |
99 | fn 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 |
143 | fn 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 | ||
155 | fn 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 |
174 | fn 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 | 189 | fn 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 |
244 | fn 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 | } |