1
1
use anyhow:: { anyhow, Context , Result } ;
2
- use log:: trace;
3
- use std:: { collections:: HashMap , str:: FromStr } ;
4
-
5
- use crate :: http:: { HttpMethod , HttpRequest , HttpResponse } ;
2
+ use log:: { trace, warn} ;
3
+ use std:: {
4
+ collections:: HashMap ,
5
+ fs,
6
+ path:: { Path , PathBuf } ,
7
+ str:: FromStr ,
8
+ } ;
9
+
10
+ use crate :: http:: {
11
+ response_status_codes:: HttpStatusCode , HttpMethod , HttpRequest , HttpResponse ,
12
+ HttpResponseBuilder ,
13
+ } ;
6
14
7
15
type RoutingCallback = fn ( & HttpRequest ) -> Result < HttpResponse > ;
16
+ type StaticFilesMount = ( String , String ) ;
8
17
9
18
#[ derive( Debug ) ]
10
19
pub struct Router {
11
20
pub routes : HashMap < Route , RoutingCallback > ,
21
+ pub file_server : Option < StaticFilesMount > ,
12
22
}
13
23
14
24
impl Default for Router {
@@ -21,6 +31,7 @@ impl Router {
21
31
pub fn new ( ) -> Self {
22
32
Router {
23
33
routes : HashMap :: new ( ) ,
34
+ file_server : None ,
24
35
}
25
36
}
26
37
@@ -29,6 +40,33 @@ impl Router {
29
40
let route = Route :: from_str ( & route_def) ?;
30
41
trace ! ( "trying to match route: {route_def}" ) ;
31
42
43
+ if let Some ( file_server) = & self . file_server {
44
+ let ( route_prefix, dir_path) = file_server;
45
+ if let Some ( filename) = route. path . strip_prefix ( route_prefix) {
46
+ let safe_filename = match Path :: new ( filename) . file_name ( ) {
47
+ Some ( filename) => Ok ( filename. to_owned ( ) ) ,
48
+ None => Err ( anyhow ! ( "not a file: {filename}" ) ) ,
49
+ } ;
50
+
51
+ if let Err ( e) = safe_filename {
52
+ warn ! ( "failed to serve file '{filename}': {e}" ) ;
53
+ return HttpResponseBuilder :: new ( )
54
+ . set_status ( HttpStatusCode :: BadRequest )
55
+ . build ( ) ;
56
+ }
57
+
58
+ let safe_filename = safe_filename. unwrap ( ) ;
59
+ let filepath = PathBuf :: from ( dir_path) . join ( safe_filename) ;
60
+ let mime_type = mime_guess:: from_path ( & filepath) . first_or_octet_stream ( ) ;
61
+ let content = fs:: read ( filepath) ?;
62
+
63
+ return HttpResponseBuilder :: new ( )
64
+ . set_raw_body ( content)
65
+ . set_content_type ( mime_type. as_ref ( ) )
66
+ . build ( ) ;
67
+ }
68
+ }
69
+
32
70
let response = if let Some ( route_callback) = self . routes . get ( & route) {
33
71
route_callback ( request)
34
72
} else {
@@ -71,6 +109,11 @@ impl Router {
71
109
Ok ( ( ) )
72
110
}
73
111
112
+ pub fn setup_file_serving ( mut self , route : & str , directory_path : & str ) -> Result < Self > {
113
+ self . file_server = Some ( ( route. to_owned ( ) , directory_path. to_owned ( ) ) ) ;
114
+ Ok ( self )
115
+ }
116
+
74
117
pub fn get ( mut self , path : & str , callback : RoutingCallback ) -> Result < Self > {
75
118
self . add_route ( HttpMethod :: GET , path, callback) ?;
76
119
Ok ( self )
0 commit comments