1+ use std:: borrow:: Cow ;
12use crate :: config:: Config ;
23use crate :: domain_storage:: DomainStorage ;
34use crate :: service:: ServiceConfig ;
45use salvo:: fs:: NamedFile ;
56use salvo:: http:: header:: LOCATION ;
6- use salvo:: http:: uri:: { Authority , Uri } ;
7+ use salvo:: http:: uri:: { Authority , PathAndQuery , Uri } ;
78use salvo:: prelude:: * ;
89use std:: str:: FromStr ;
910use std:: sync:: Arc ;
11+ use salvo:: http:: { ParseError , ResBody } ;
1012
1113#[ inline]
1214pub ( crate ) fn decode_url_path_safely ( path : & str ) -> String {
@@ -60,11 +62,26 @@ fn alias_redirect(uri: &Uri, https: bool, host: &str, external_port: u16, res: &
6062 res. status_code ( StatusCode :: MOVED_PERMANENTLY ) ;
6163}
6264
65+ fn replace_uri_path ( original_uri : & Uri , new_path : & str ) -> Result < Uri , ParseError > {
66+ let mut uri_parts = original_uri. clone ( ) . into_parts ( ) ;
67+ uri_parts. authority = None ;
68+ uri_parts. scheme = None ;
69+ let path = match original_uri. query ( ) {
70+ Some ( query) => Cow :: from ( format ! ( "{new_path}?{query}" ) ) ,
71+ None => Cow :: from ( new_path) ,
72+ } ;
73+
74+ uri_parts. path_and_query = Some ( PathAndQuery :: from_str ( path. as_ref ( ) ) ?) ;
75+ Ok ( Uri :: from_parts ( uri_parts) ?)
76+ }
77+
78+
6379#[ handler]
6480async fn file_resp ( req : & mut Request , depot : & mut Depot , res : & mut Response , _ctrl : & mut FlowCtrl ) {
81+
6582 let domain_storage = depot. obtain :: < Arc < DomainStorage > > ( ) . unwrap ( ) ;
6683 let service_config = depot. obtain :: < Arc < ServiceConfig > > ( ) . unwrap ( ) ;
67- let config = depot. obtain :: < Arc < Config > > ( ) . unwrap ( ) ;
84+ // let config = depot.obtain::<Arc<Config>>().unwrap();
6885
6986 let author_opt = get_authority ( req) ;
7087 if let Some ( author) = author_opt {
@@ -96,13 +113,37 @@ async fn file_resp(req: &mut Request, depot: &mut Depot, res: &mut Response, _ct
96113 & * decode_url_path_safely ( req_path)
97114 } ;
98115 let rel_path = format_url_path_safely ( rel_path) ;
116+ tracing:: debug!( "hit {rel_path}" ) ;
99117 match domain_storage. get_file ( host, & rel_path) {
100118 Some ( item) => {
101119 NamedFile :: builder ( & item. data )
102120 . send ( req. headers ( ) , res)
103121 . await ;
104122 }
105123 None => {
124+ // trailing slash
125+ let original_path = req. uri ( ) . path ( ) ;
126+ if !original_path. is_empty ( ) && original_path != "/" {
127+ let ends_with_slash = original_path. ends_with ( '/' ) ;
128+
129+ if !ends_with_slash {
130+ if let Ok ( new_uri) = replace_uri_path ( req. uri ( ) , & format ! ( "{original_path}/" ) ) {
131+ res. body ( ResBody :: None ) ;
132+
133+ match Redirect :: with_status_code ( StatusCode :: MOVED_PERMANENTLY , new_uri) {
134+ Ok ( redirect) => {
135+ res. render ( redirect) ;
136+ }
137+ Err ( e) => {
138+ tracing:: error!( error = ?e, "redirect failed" ) ;
139+ }
140+ }
141+ return
142+ }
143+
144+ }
145+ }
146+
106147 res. status_code ( StatusCode :: NOT_FOUND ) ;
107148 }
108149 }
@@ -122,13 +163,11 @@ pub async fn init_http_server(
122163 . await ;
123164
124165 // StaticDir::new();
125- let router = Router :: with_hoop ( trailing_slash:: add_slash ( ) )
126- . hoop (
166+ let router = Router :: with_hoop (
127167 affix_state:: inject ( service_config. clone ( ) )
128168 . inject ( storage. clone ( ) )
129169 . inject ( conf. clone ( ) ) ,
130- )
131- . path ( "{*path}" )
170+ ) . path ( "{*path}" )
132171 . get ( file_resp) ;
133172
134173 Server :: new ( listener) . serve ( router) . await ;
0 commit comments