1
- use anyhow:: { anyhow, Context , Result } ;
1
+ use anyhow:: { anyhow, bail , Context , Result } ;
2
2
use log:: { trace, warn} ;
3
3
use std:: {
4
- collections:: HashMap ,
4
+ collections:: { HashMap , HashSet } ,
5
5
fs,
6
6
path:: { Path , PathBuf } ,
7
7
str:: FromStr ,
8
8
} ;
9
9
10
- use crate :: http:: {
11
- response_status_codes:: HttpStatusCode , HttpMethod , HttpRequest , HttpResponse ,
12
- HttpResponseBuilder ,
13
- } ;
10
+ use crate :: http:: { HttpMethod , HttpRequest , HttpResponse , HttpResponseBuilder } ;
14
11
15
12
type RoutingCallback = fn ( & HttpRequest ) -> Result < HttpResponse > ;
16
- type StaticFilesMount = ( String , String ) ;
13
+
14
+ #[ derive( Debug ) ]
15
+ pub struct FileServer {
16
+ pub mount_points : HashSet < MountPoint > ,
17
+ }
18
+
19
+ impl FileServer {
20
+ pub fn new ( ) -> Self {
21
+ Self {
22
+ mount_points : HashSet :: new ( ) ,
23
+ }
24
+ }
25
+
26
+ pub fn add_dir_mount ( mut self , route : & str , dir_path : & str ) -> Result < Self > {
27
+ let mount_point = MountPoint {
28
+ route : route. to_owned ( ) ,
29
+ fs_path : PathBuf :: from ( dir_path) ,
30
+ is_directory : true ,
31
+ } ;
32
+
33
+ if !self . mount_points . insert ( mount_point) {
34
+ bail ! ( "directory mount already exists" ) ;
35
+ }
36
+
37
+ Ok ( self )
38
+ }
39
+
40
+ pub fn map_file ( mut self , route : & str , file_path : & str ) -> Result < Self > {
41
+ let mount_point = MountPoint {
42
+ route : route. to_owned ( ) ,
43
+ fs_path : PathBuf :: from ( file_path) ,
44
+ is_directory : false ,
45
+ } ;
46
+
47
+ if !self . mount_points . insert ( mount_point) {
48
+ bail ! ( "a similar file map already exists" ) ;
49
+ }
50
+
51
+ Ok ( self )
52
+ }
53
+
54
+ fn get_file ( file_path : PathBuf ) -> Result < PathBuf > {
55
+ if !file_path. exists ( ) {
56
+ bail ! ( "file not found: {}" , file_path. display( ) ) ;
57
+ }
58
+
59
+ if !file_path. is_file ( ) {
60
+ bail ! ( "not a file: {}" , file_path. display( ) ) ;
61
+ }
62
+
63
+ Ok ( file_path)
64
+ }
65
+
66
+ pub fn handle_static_file_access ( & self , file : & str ) -> Result < PathBuf > {
67
+ let file_path = self
68
+ . mount_points
69
+ . iter ( )
70
+ . filter ( |mp| !mp. is_directory )
71
+ . find ( |mp| mp. route == file)
72
+ . map ( |mp| mp. fs_path . clone ( ) ) ;
73
+
74
+ if let Some ( file_path) = file_path {
75
+ return FileServer :: get_file ( file_path) ;
76
+ }
77
+
78
+ let dir_mount_point = self
79
+ . mount_points
80
+ . iter ( )
81
+ . filter ( |mp| mp. is_directory )
82
+ . find ( |mp| file. starts_with ( & mp. route ) ) ;
83
+
84
+ if let Some ( dir_mount_point) = dir_mount_point {
85
+ let file_name = file
86
+ . strip_prefix ( & dir_mount_point. route )
87
+ . with_context ( || format ! ( "file should have prefix: {}" , dir_mount_point. route) ) ?;
88
+
89
+ let safe_file_name = match Path :: new ( file_name) . file_name ( ) {
90
+ Some ( filename) => Ok ( filename. to_owned ( ) ) ,
91
+ None => Err ( anyhow ! ( "invalid file name: {file}" ) ) ,
92
+ } ?;
93
+
94
+ let file_path = dir_mount_point. fs_path . join ( safe_file_name) ;
95
+ return Self :: get_file ( file_path) ;
96
+ }
97
+
98
+ bail ! ( "failed to map file: {file}" )
99
+ }
100
+ }
101
+
102
+ #[ derive( Debug , Hash , PartialEq , Eq ) ]
103
+ pub struct MountPoint {
104
+ pub route : String ,
105
+ pub fs_path : PathBuf ,
106
+ pub is_directory : bool ,
107
+ }
17
108
18
109
#[ derive( Debug ) ]
19
110
pub struct Router {
20
111
pub routes : HashMap < Route , RoutingCallback > ,
21
- pub file_server : Option < StaticFilesMount > ,
112
+ pub file_server : Option < FileServer > ,
22
113
}
23
114
24
115
impl Default for Router {
@@ -35,46 +126,28 @@ impl Router {
35
126
}
36
127
}
37
128
129
+ pub fn set_file_server ( mut self , file_server : FileServer ) -> Self {
130
+ self . file_server = Some ( file_server) ;
131
+ self
132
+ }
133
+
38
134
pub fn handle_request ( & self , request : & HttpRequest ) -> Result < HttpResponse > {
39
135
let route_def = format ! ( "{} {}" , request. method, request. url) ;
40
136
let route = Route :: from_str ( & route_def) ?;
41
137
trace ! ( "trying to match route: {route_def}" ) ;
42
138
43
139
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}" ) ;
140
+ match file_server. handle_static_file_access ( & route. path ) {
141
+ Ok ( file_path) => {
142
+ let mime_type = mime_guess:: from_path ( & file_path) . first_or_octet_stream ( ) ;
143
+ let content = fs:: read ( file_path) ?;
144
+
53
145
return HttpResponseBuilder :: new ( )
54
- . set_status ( HttpStatusCode :: BadRequest )
146
+ . set_raw_body ( content)
147
+ . set_content_type ( mime_type. as_ref ( ) )
55
148
. build ( ) ;
56
149
}
57
-
58
- let safe_filename = safe_filename. unwrap ( ) ;
59
- let filepath = PathBuf :: from ( dir_path) . join ( safe_filename) ;
60
- if !filepath. exists ( ) {
61
- let catch_all_route = Route :: from_str ( "GET /*" ) ?;
62
- if let Some ( catch_all_callback) = self . routes . get ( & catch_all_route) {
63
- return catch_all_callback ( request) ;
64
- } else {
65
- return HttpResponseBuilder :: new ( )
66
- . set_status ( HttpStatusCode :: NotFound )
67
- . build ( ) ;
68
- }
69
- }
70
-
71
- let mime_type = mime_guess:: from_path ( & filepath) . first_or_octet_stream ( ) ;
72
- let content = fs:: read ( filepath) ?;
73
-
74
- return HttpResponseBuilder :: new ( )
75
- . set_raw_body ( content)
76
- . set_content_type ( mime_type. as_ref ( ) )
77
- . build ( ) ;
150
+ Err ( e) => warn ! ( "failed to match file: {e}" ) ,
78
151
}
79
152
}
80
153
@@ -120,11 +193,6 @@ impl Router {
120
193
Ok ( ( ) )
121
194
}
122
195
123
- pub fn setup_file_serving ( mut self , route : & str , directory_path : & str ) -> Result < Self > {
124
- self . file_server = Some ( ( route. to_owned ( ) , directory_path. to_owned ( ) ) ) ;
125
- Ok ( self )
126
- }
127
-
128
196
pub fn get ( mut self , path : & str , callback : RoutingCallback ) -> Result < Self > {
129
197
self . add_route ( HttpMethod :: GET , path, callback) ?;
130
198
Ok ( self )
0 commit comments