chilli/
app.rs

1//! This module implements the central application object.
2extern crate num_cpus;
3
4use std::convert::Into;
5use std::sync::RwLock;
6use std::fmt;
7use std::collections::HashMap;
8use std::error::Error;
9use std::fs::File;
10use std::path::PathBuf;
11use std::net::ToSocketAddrs;
12
13use serde_json::{Value};
14use serde::Serialize;
15use handlebars::Handlebars;
16use hyper;
17use hyper::method::Method;
18use hyper::status::StatusCode;
19use hyper::server::Request as HTTPRequest;
20use hyper::server::Response as HTTPResponse;
21
22use types::{
23    PencilError,
24        PenHTTPError,
25        PenUserError,
26
27    UserError,
28    PencilResult,
29    ViewFunc,
30    HTTPErrorHandler,
31    UserErrorHandler,
32    BeforeRequestFunc,
33    AfterRequestFunc,
34    TeardownRequestFunc,
35};
36use wrappers::{
37    Request,
38    Response,
39};
40use helpers::{PathBound, send_from_directory_range, redirect};
41use config::Config;
42use logging;
43use serving::run_server;
44use routing::{Map, Rule, Matcher};
45use testing::PencilClient;
46use http_errors::{HTTPError, NotFound, InternalServerError};
47use templating::{render_template, render_template_string, load_template};
48use module::Module;
49use typemap::{ShareMap, Key};
50use hyper::header::{IfModifiedSince, LastModified, HttpDate, CacheControl, CacheDirective};
51use time;
52
53/// The pencil type.  It acts as the central application object.  Once it is created it
54/// will act as a central registry for the view functions, the URL rules and much more.
55pub struct Pencil {
56    /// The path where your application locates.
57    pub root_path: String,
58    /// The name of the application.  By default it's guessed from the root path.
59    pub name: String,
60    /// The folder with static files that should be served at `static_url_path`.
61    /// Defaults to the `"static"` folder in the root path of the application.
62    pub static_folder: String,
63    /// The url path for the static files on the web, defaults to be `"/static"`.
64    pub static_url_path: String,
65    /// The folder that contains the templates that should be used for the application.
66    /// Defaults to `''templates''` folder in the root path of the application.
67    pub template_folder: String,
68    /// The configuration for this application.
69    pub config: Config,
70    /// For storing arbitrary types as "static" data.
71    pub extensions: ShareMap,
72    /// The Handlebars registry used to load templates and register helpers.
73    pub handlebars_registry: RwLock<Box<Handlebars>>,
74    /// The url map for this pencil application.
75    pub url_map: Map,
76    /// All the attached modules in a hashmap by name.
77    pub modules: HashMap<String, Module>,
78    /// A dictionary of all view functions registered.  The key will be endpoint.
79    view_functions: HashMap<String, ViewFunc>,
80    before_request_funcs: Vec<Box<BeforeRequestFunc>>,
81    after_request_funcs: Vec<Box<AfterRequestFunc>>,
82    teardown_request_funcs: Vec<Box<TeardownRequestFunc>>,
83    http_error_handlers: HashMap<u16, Box<HTTPErrorHandler>>,
84    user_error_handlers: HashMap<String, Box<UserErrorHandler>>,
85}
86
87fn default_config() -> Config {
88    let mut config = Config::new();
89    config.set("DEBUG", Value::Bool(false));
90    config.set("TESTING", Value::Bool(false));
91    config
92}
93
94impl Pencil {
95    /// Create a new pencil object.  It is passed the root path of your application.
96    /// The root path is used to resolve resources from inside it, for more information
97    /// about resource loading, see method `open_resource`.
98    ///
99    /// Usually you create a pencil object in your main function like this:
100    ///
101    /// ```rust,no_run
102    /// use chilli::Pencil;
103    ///
104    /// fn main() {
105    ///     let mut app = Pencil::new("/web/myapp");
106    /// }
107    /// ```
108    pub fn new(root_path: &str) -> Pencil {
109        Pencil {
110            root_path: root_path.to_string(),
111            name: root_path.to_string(),
112            static_folder: String::from("static"),
113            static_url_path: String::from("/static"),
114            template_folder: String::from("templates"),
115            config: default_config(),
116            extensions: ShareMap::custom(),
117            handlebars_registry: RwLock::new(Box::new(Handlebars::new())),
118            url_map: Map::new(),
119            modules: HashMap::new(),
120            view_functions: HashMap::new(),
121            before_request_funcs: vec![],
122            after_request_funcs: vec![],
123            teardown_request_funcs: vec![],
124            http_error_handlers: HashMap::new(),
125            user_error_handlers: HashMap::new(),
126        }
127    }
128
129    /// The debug flag.  This field is configured from the config
130    /// with the `DEBUG` configuration key.  Defaults to `False`.
131    pub fn is_debug(&self) -> bool {
132        self.config.get_boolean("DEBUG", false)
133    }
134
135    /// The testing flag.  This field is configured from the config
136    /// with the `TESTING` configuration key.  Defaults to `False`.
137    pub fn is_testing(&self) -> bool {
138        self.config.get_boolean("TESTING", false)
139    }
140
141    /// Set the debug flag.  This field is configured from the config
142    /// with the `DEBUG` configuration key.  Set this to `True` to
143    /// enable debugging of the application.
144    pub fn set_debug(&mut self, flag: bool) {
145        self.config.set("DEBUG", Value::Bool(flag));
146    }
147
148    /// Set the testing flag.  This field is configured from the config
149    /// with the `TESTING` configuration key.  Set this to `True` to
150    /// enable the test mode of the application.
151    pub fn set_testing(&mut self, flag: bool) {
152        self.config.set("TESTING", Value::Bool(flag));
153    }
154
155    /// Set global log level based on the application's debug flag.
156    /// This is only useful for `env_logger` crate users.
157    /// On debug mode, this turns on all debug logging.
158    pub fn set_log_level(&self) {
159        logging::set_log_level(self);
160    }
161
162    /// This is used to register a view function for a given URL rule.
163    /// Basically this example:
164    ///
165    /// ```rust,ignore
166    /// app.route("/home", &[Get], "home", home);
167    /// app.route("/user/<user_id:int>", &[Get], "user", user);
168    /// ```
169    ///
170    /// A rule that listens for `GET` will implicitly listen for `HEAD`.
171    ///
172    pub fn route<M: Into<Matcher>, N: AsRef<[Method]>>(&mut self, rule: M, methods: N, endpoint: &str, view_func: ViewFunc) {
173        self.add_url_rule(rule.into(), methods.as_ref(), endpoint, view_func);
174    }
175
176    /// This is a shortcut for `route`, register a view function for
177    /// a given URL rule with just `GET` method (implicitly `HEAD`).
178    pub fn get<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
179        self.route(rule, &[Method::Get], endpoint, view_func);
180    }
181
182    /// This is a shortcut for `route`, register a view function for
183    /// a given URL rule with just `POST` method.
184    pub fn post<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
185        self.route(rule, &[Method::Post], endpoint, view_func);
186    }
187
188    /// This is a shortcut for `route`, register a view function for
189    /// a given URL rule with just `DELETE` method.
190    pub fn delete<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
191        self.route(rule, &[Method::Delete], endpoint, view_func);
192    }
193
194    /// This is a shortcut for `route`, register a view function for
195    /// a given URL rule with just `PATCH` method.
196    pub fn patch<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
197        self.route(rule, &[Method::Patch], endpoint, view_func);
198    }
199
200    /// This is a shortcut for `route`, register a view function for
201    /// a given URL rule with just `PUT` method.
202    pub fn put<M: Into<Matcher>>(&mut self, rule: M, endpoint: &str, view_func: ViewFunc) {
203        self.route(rule, &[Method::Put], endpoint, view_func);
204    }
205
206    /// Connects a URL rule.
207    pub fn add_url_rule(&mut self, matcher: Matcher, methods: &[Method], endpoint: &str, view_func: ViewFunc) {
208        let url_rule = Rule::new(matcher, methods, endpoint);
209        self.url_map.add(url_rule);
210        self.view_functions.insert(endpoint.to_string(), view_func);
211    }
212
213    /// Register a module on the application.
214    pub fn register_module(&mut self, module: Module) {
215        module.register(self);
216    }
217
218    /// Enables static file handling.
219    pub fn enable_static_file_handling(&mut self) {
220        let mut rule = self.static_url_path.clone();
221        rule = rule + "/<filename:path>";
222        let rule_str: &str = &rule;
223        self.route(rule_str, &[Method::Get], "static", send_app_static_file);
224    }
225
226    /// Enables static file handling with caching. (304 Not Modified + Max-Age) The static files are considered
227    /// "not modified" since the server was started.
228    pub fn enable_static_cached_file_handling(&mut self, max_age: ::std::time::Duration) {
229        let mut rule = self.static_url_path.clone();
230        rule = rule + "/<filename:path>";
231        let rule_str: &str = &rule;
232        let mut tm = time::now_utc();
233        tm.tm_nsec = 0;
234        self.extensions.insert::<TimeAtServerStartKey>(tm);
235        self.extensions.insert::<MaxAgeKey>(max_age);
236        self.route(rule_str, &[Method::Get], "static", send_app_static_file_with_cache);
237    }
238
239    /// Registers a function to run before each request.
240    pub fn before_request<F: Fn(&mut Request) -> Option<PencilResult> + Send + Sync + 'static>(&mut self, f: F) {
241        self.before_request_funcs.push(Box::new(f));
242    }
243
244    /// Registers a function to run after each request.  Your function
245    /// must take a response object and modify it.
246    pub fn after_request<F: Fn(&Request, &mut Response) + Send + Sync + 'static>(&mut self, f: F) {
247        self.after_request_funcs.push(Box::new(f));
248    }
249
250    /// Registers a function to run at the end of each request,
251    /// regardless of whether there was an error or not.
252    pub fn teardown_request<F: Fn(Option<&PencilError>) + Send + Sync + 'static>(&mut self, f: F) {
253        self.teardown_request_funcs.push(Box::new(f));
254    }
255
256    /// Registers a function as one http error handler.
257    /// Same to `httperrorhandler`.
258    pub fn register_http_error_handler<F: Fn(HTTPError) -> PencilResult + Send + Sync + 'static>(&mut self, status_code: u16, f: F) {
259        self.http_error_handlers.insert(status_code, Box::new(f));
260    }
261
262    /// Registers a function as one user error handler.
263    /// Same to `usererrorhandler`.
264    pub fn register_user_error_handler<F: Fn(UserError) -> PencilResult + Send + Sync + 'static>(&mut self, error_desc: &str, f: F) {
265        self.user_error_handlers.insert(error_desc.to_string(), Box::new(f));
266    }
267
268    /// Registers a function as one http error handler.  Example:
269    ///
270    /// ```rust,no_run
271    /// use chilli::{Pencil, PencilResult, Response};
272    /// use chilli::HTTPError;
273    ///
274    ///
275    /// fn page_not_found(error: HTTPError) -> PencilResult {
276    ///     let mut response = Response::from("The page does not exist");
277    ///     response.status_code = 404;
278    ///     return Ok(response);
279    /// }
280    ///
281    ///
282    /// fn main() {
283    ///     let mut app = Pencil::new("/web/demo");
284    ///     app.httperrorhandler(404, page_not_found);
285    /// }
286    /// ```
287    pub fn httperrorhandler<F: Fn(HTTPError) -> PencilResult + Send + Sync + 'static>(&mut self, status_code: u16, f: F) {
288        self.register_http_error_handler(status_code, f);
289    }
290
291    /// Registers a function as one user error handler.  There are two ways to handle
292    /// user errors currently, you can do it in your own view like this:
293    ///
294    /// ```rust,no_run
295    /// use chilli::Request;
296    /// use chilli::{PencilResult, Response};
297    ///
298    ///
299    /// #[derive(Clone, Copy)]
300    /// struct MyErr(isize);
301    ///
302    ///
303    /// fn some_operation() -> Result<isize, MyErr> {
304    ///     return Err(MyErr(10));
305    /// }
306    ///
307    ///
308    /// fn my_err_handler(_: MyErr) -> PencilResult {
309    ///     Ok(Response::from("My err occurred!"))
310    /// }
311    ///
312    ///
313    /// fn hello(_: &mut Request) -> PencilResult {
314    ///     match some_operation() {
315    ///         Ok(_) => Ok(Response::from("Hello!")),
316    ///         Err(e) => my_err_handler(e),
317    ///     }
318    /// }
319    /// ```
320    ///
321    /// The problem with this is that you have to do it in all of your views, it brings
322    /// a lot of redundance, so pencil provides another solution, currently I still
323    /// haven't got any better idea on how to store user error handlers, this feature is
324    /// really just experimental, if you have any good idea, please wake me up.  Here is
325    /// one simple example:
326    ///
327    /// ```rust,no_run
328    /// use std::convert;
329    ///
330    /// use chilli::Request;
331    /// use chilli::{Pencil, PencilResult, Response};
332    /// use chilli::{PencilError, PenUserError, UserError};
333    ///
334    ///
335    /// #[derive(Clone, Copy)]
336    /// pub struct MyErr(isize);
337    ///
338    /// impl convert::From<MyErr> for PencilError {
339    ///     fn from(err: MyErr) -> PencilError {
340    ///         let user_error = UserError::new("MyErr");
341    ///         return PenUserError(user_error);
342    ///     }
343    /// }
344    ///
345    ///
346    /// fn my_err_handler(_: UserError) -> PencilResult {
347    ///     Ok(Response::from("My err occurred!"))
348    /// }
349    ///
350    ///
351    /// fn some_operation() -> Result<String, MyErr> {
352    ///     return Err(MyErr(10));
353    /// }
354    ///
355    ///
356    /// fn hello(_: &mut Request) -> PencilResult {
357    ///     let rv = try!(some_operation());
358    ///     return Ok(rv.into());
359    /// }
360    ///
361    ///
362    /// fn main() {
363    ///     let mut app = Pencil::new("/web/demo");
364    ///     // Use error description as key to store handlers, really ugly...
365    ///     app.usererrorhandler("MyErr", my_err_handler);
366    /// }
367    /// ```
368    pub fn usererrorhandler<F: Fn(UserError) -> PencilResult + Send + Sync + 'static>(&mut self, error_desc: &str, f: F) {
369        self.register_user_error_handler(error_desc, f);
370    }
371
372    /// Creates a test client for this application, you can use it
373    /// like this:
374    ///
375    /// ```ignore
376    /// let client = app.test_client();
377    /// let response = client.get('/');
378    /// assert!(response.code, 200);
379    /// ```
380    #[allow(dead_code)]
381    fn test_client(&self) -> PencilClient {
382        PencilClient::new(self)
383    }
384
385    /// Called before the actual request dispatching, you can return value
386    /// from here and stop the further request handling.
387    fn preprocess_request(&self, request: &mut Request) -> Option<PencilResult> {
388        if let Some(module) = self.get_module(request.module_name()) {
389            for func in &module.before_request_funcs {
390                if let Some(result) = func(request) {
391                    return Some(result);
392                }
393            }
394        }
395        for func in &self.before_request_funcs {
396            if let Some(result) = func(request) {
397                return Some(result);
398            }
399        }
400        None
401    }
402
403    /// Does the request dispatching.  Matches the URL and returns the return
404    /// value of the view.
405    fn dispatch_request(&self, request: &mut Request) -> PencilResult {
406        if let Some(ref routing_error) = request.routing_error {
407            return Err(PenHTTPError(routing_error.clone()));
408        }
409        if let Some((ref redirect_url, redirect_code)) = request.routing_redirect {
410            return redirect(redirect_url, redirect_code);
411        }
412        if let Some(default_options_response) = self.make_default_options_response(request) {
413            return Ok(default_options_response);
414        }
415        match self.view_functions.get(&request.endpoint().unwrap()) {
416            Some(&view_func) => {
417                view_func(request)
418            },
419            None => {
420                Err(PenHTTPError(NotFound))
421            }
422        }
423    }
424
425    /// This method is called to create the default `OPTIONS` response.
426    fn make_default_options_response(&self, request: &Request) -> Option<Response> {
427        if let Some(ref rule) = request.url_rule {
428            // if we provide automatic options for this URL and the request
429            // came with the OPTIONS method, reply automatically
430            if rule.provide_automatic_options && request.method() == Method::Options {
431                let url_adapter = request.url_adapter();
432                let mut response = Response::new_empty();
433                response.headers.set(hyper::header::Allow(url_adapter.allowed_methods()));
434                return Some(response);
435            }
436        }
437        None
438    }
439
440    /// Get a module by its name.
441    fn get_module(&self, module_name: Option<String>) -> Option<&Module> {
442        if let Some(name) = module_name {
443            self.modules.get(&name)
444        } else {
445            None
446        }
447    }
448
449    /// Modify the response object before it's sent to the HTTP server.
450    fn process_response(&self, request: &Request, response: &mut Response) {
451        if let Some(module) = self.get_module(request.module_name()) {
452            for func in module.after_request_funcs.iter().rev() {
453                func(request, response);
454            }
455        }
456        for func in self.after_request_funcs.iter().rev() {
457            func(request, response);
458        }
459    }
460
461    /// Called after the actual request dispatching.
462    fn do_teardown_request(&self, request: &Request, e: Option<&PencilError>) {
463        if let Some(module) = self.get_module(request.module_name()) {
464            for func in module.teardown_request_funcs.iter().rev() {
465                func(e);
466            }
467        }
468        for func in self.teardown_request_funcs.iter().rev() {
469            func(e);
470        }
471    }
472
473    /// This method is called whenever an error occurs that should be handled.
474    fn handle_all_error(&self, request: &Request, e: PencilError) -> PencilResult {
475        match e {
476            PenHTTPError(e) => self.handle_http_error(request, e),
477            PenUserError(e) => self.handle_user_error(request, e),
478        }
479    }
480
481    /// Handles an User error.
482    fn handle_user_error(&self, request: &Request, e: UserError) -> PencilResult {
483        if let Some(module) = self.get_module(request.module_name()) {
484            if let Some(handler) = module.user_error_handlers.get(&e.desc) {
485                return handler(e);
486            }
487        }
488        if let Some(handler) = self.user_error_handlers.get(&e.desc) {
489            return handler(e);
490        }
491        Err(PenUserError(e))
492    }
493
494    /// Handles an HTTP error.
495    fn handle_http_error(&self, request: &Request, e: HTTPError) -> PencilResult {
496        if let Some(module) = self.get_module(request.module_name()) {
497            if let Some(handler) = module.http_error_handlers.get(&e.code()) {
498                return handler(e);
499            }
500        }
501        if let Some(handler) = self.http_error_handlers.get(&e.code()) {
502            return handler(e);
503        }
504        Ok(e.to_response())
505    }
506
507    /// Default error handing that kicks in when an error occurs that is not
508    /// handled.
509    fn handle_error(&self, request: &Request, e: &PencilError) -> Response {
510        self.log_error(request, e);
511        let internal_server_error = InternalServerError;
512        if let Ok(response) = self.handle_http_error(request, internal_server_error) {
513            return response;
514        } else {
515            let e = InternalServerError;
516            return e.to_response();
517        }
518    }
519
520    /// Logs an error.
521    fn log_error(&self, request: &Request, e: &PencilError) {
522        error!("Error on {} [{}]: {}", request.path(), request.method(), e.description());
523    }
524
525    /// Dispatches the request and performs request pre and postprocessing
526    /// as well as HTTP error handling and User error handling.
527    fn full_dispatch_request(&self, request: &mut Request) -> Result<Response, PencilError> {
528        let result = match self.preprocess_request(request) {
529            Some(result) => result,
530            None => self.dispatch_request(request),
531        };
532        let rv = match result {
533            Ok(response) => Ok(response),
534            Err(e) => self.handle_all_error(request, e),
535        };
536        match rv {
537            Ok(mut response) => {
538                self.process_response(request, &mut response);
539                Ok(response)
540            },
541            Err(e) => Err(e),
542        }
543    }
544
545    /// Load and compile and register a template.
546    pub fn register_template(&mut self, template_name: &str) {
547        let registry_write_rv = self.handlebars_registry.write();
548        if registry_write_rv.is_err() {
549            panic!("Can't write handlebars registry");
550        }
551        let mut registry = registry_write_rv.unwrap();
552        match load_template(self, template_name) {
553            Some(source_rv) => {
554                match source_rv {
555                    Ok(source) => {
556                        if let Err(err) = registry.register_template_string(template_name, source) {
557                            panic!(format!("Template compile error: {}", err));
558                        }
559                    },
560                    Err(err) => {
561                        panic!(format!("Template {} can't be loaded: {}", template_name, err));
562                    }
563                }
564            },
565            None => {
566                panic!(format!("Template not found: {}", template_name));
567            }
568        }
569    }
570
571    /// We use `handlebars-rs` as template engine.
572    /// Renders a template from the template folder with the given context.
573    /// The template name is the name of the template to be rendered.
574    /// The context is the variables that should be available in the template.
575    pub fn render_template<T: Serialize>(&self, template_name: &str, context: &T)
576    -> PencilResult
577    {
578        render_template(self, template_name, context)
579    }
580
581    /// We use `handlebars-rs` as template engine.
582    /// Renders a template from the given template source string
583    /// with the given context.
584    /// The source is the sourcecode of the template to be rendered.
585    /// The context is the variables that should be available in the template.
586    pub fn render_template_string<T: Serialize>(&self, source: &str, context: &T)
587    -> PencilResult
588    {
589        render_template_string(self, source, context)
590    }
591
592    /// The actual application handler.
593    pub fn handle_request(&self, request: &mut Request) -> Response {
594        request.match_request();
595        match self.full_dispatch_request(request) {
596            Ok(response) => {
597                self.do_teardown_request(request, None);
598                return response;
599            },
600            Err(e) => {
601                let response = self.handle_error(request, &e);
602                self.do_teardown_request(request, Some(&e));
603                return response;
604            }
605        };
606    }
607
608    /// Runs the application on a hyper HTTP server.
609    pub fn run<A: ToSocketAddrs>(self, addr: A) {
610        run_server(self, addr, num_cpus::get());
611    }
612
613    /// Runs the application on a hyper HTTP server.
614    pub fn run_threads<A: ToSocketAddrs>(self, addr: A, threads: usize) {
615        run_server(self, addr, threads);
616    }
617}
618
619impl hyper::server::Handler for Pencil {
620    fn handle(&self, req: HTTPRequest, mut res: HTTPResponse) {
621        debug!("Request: {}", req.uri);
622        match Request::new(self, req) {
623            Ok(mut request) => {
624                let response = self.handle_request(&mut request);
625                response.write(request.method(), res);
626            }
627            Err(_) => {
628                *res.status_mut() = StatusCode::BadRequest;
629                if let Ok(w) = res.start() {
630                    let _ = w.end();
631                }
632            }
633        };
634    }
635}
636
637impl PathBound for Pencil {
638    fn open_resource(&self, resource: &str) -> File {
639        let mut pathbuf = PathBuf::from(&self.root_path);
640        pathbuf.push(resource);
641        File::open(&pathbuf.as_path()).unwrap()
642    }
643}
644
645impl fmt::Display for Pencil {
646    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
647        write!(f, "<Pencil application {}>", self.name)
648    }
649}
650
651impl fmt::Debug for Pencil {
652    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
653        write!(f, "<Pencil application {}>", self.name)
654    }
655}
656
657/// View function used internally to send static files from the static folder
658/// to the browser.
659fn send_app_static_file(request: &mut Request) -> PencilResult {
660    let mut static_path = PathBuf::from(&request.app.root_path);
661    static_path.push(&request.app.static_folder);
662    let static_path_str = static_path.to_str().unwrap();
663    let filename = request.view_args.get("filename").unwrap();
664    send_from_directory_range(static_path_str, filename, false, request.headers().get())
665}
666
667
668
669fn check_if_cached(req: &mut Request) -> Option<PencilResult> {
670
671    let mod_time = req.app.extensions.get::<TimeAtServerStartKey>().expect("TimeAtServerStartKey should've been set up.");
672
673    match req.headers().get::<IfModifiedSince>() {
674        Some(&IfModifiedSince(HttpDate(tm))) if tm >= *mod_time => {
675            let mut cached_resp = Response::new_empty();
676            cached_resp.status_code = 304;
677            return Some(Ok(cached_resp));
678        },
679        None => { // No caching requested
680            return None;
681        },
682        Some(_) => { // Stale cache
683            return None;
684        }
685    }
686}
687
688struct MaxAgeKey;
689
690impl Key for MaxAgeKey {
691    type Value = ::std::time::Duration;
692}
693
694struct TimeAtServerStartKey;
695
696impl Key for TimeAtServerStartKey {
697    type Value = time::Tm;
698}
699
700/// View function used internally to send static files from the static folder
701/// to the browser. Using cache, and serving 304 Not Modified if possible.
702fn send_app_static_file_with_cache(request: &mut Request) -> PencilResult {
703    if let Some(resp) = check_if_cached(request) {
704        return resp;
705    }
706    let mut static_path = PathBuf::from(&request.app.root_path);
707    static_path.push(&request.app.static_folder);
708    let static_path_str = static_path.to_str().unwrap();
709    let filename = request.view_args.get("filename").unwrap();
710    let resp = send_from_directory_range(static_path_str, filename, false, request.headers().get());
711    resp.map(|mut r| {
712        let mod_time = request.app.extensions.get::<TimeAtServerStartKey>().expect("TimeAtServerStartKey should've been set up.");
713        r.headers.set(LastModified(HttpDate(*mod_time)));
714        let max_age = request.app.extensions.get::<MaxAgeKey>().expect("MaxAgeKey should've been set up.").as_secs();
715        if max_age > 0 {
716            r.headers.set(CacheControl(vec![CacheDirective::MaxAge(max_age as u32)]));
717        }
718        r
719    })
720}
721