mirror of
				https://github.com/Tim-Paik/srv.git
				synced 2024-10-13 00:29:43 +00:00 
			
		
		
		
	v1.0.0-rc.6 Optimize the code and supplement the readme
This commit is contained in:
		
							
								
								
									
										63
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								README.md
									
									
									
									
									
								
							| @ -33,3 +33,66 @@ This is a simple HTTP Server for use in a development environment, inspired by [ | |||||||
| - TLS/SSL support, HTTP/2 support | - TLS/SSL support, HTTP/2 support | ||||||
| - One click to enable CORS, custom CORS header support | - One click to enable CORS, custom CORS header support | ||||||
| - cargo doc support | - cargo doc support | ||||||
|  |  | ||||||
|  | ## Install | ||||||
|  |  | ||||||
|  | ### Pre-compiled Version | ||||||
|  |  | ||||||
|  | #### Linux | ||||||
|  |  | ||||||
|  | ##### Archlinux | ||||||
|  |  | ||||||
|  | ```shell | ||||||
|  | yay -S srv-bin | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ##### Other Linux | ||||||
|  |  | ||||||
|  | Download the pre-compiled `srv-x86_64-unknown-linux-musl.tar.gz` on the [releases](https://github.com/Tim-Paik/srv/releases/latest), and copy the srv file in the compressed package to `/usr/bin` as a ROOT user with 755 permissions. | ||||||
|  |  | ||||||
|  | ```shell | ||||||
|  | wget https://github.com/Tim-Paik/srv/releases/download/v1.0.0-rc.6/srv-x86_64-unknown-linux-musl.tar.gz | ||||||
|  | tar -xzvf srv-x86_64-unknown-linux-musl.tar.gz | ||||||
|  | install -Dm0755 -t /usr/bin/ srv | ||||||
|  | rm srv srv-x86_64-unknown-linux-musl.tar.gz | ||||||
|  | ``` | ||||||
|  | for reference only | ||||||
|  |  | ||||||
|  | #### MacOS | ||||||
|  |  | ||||||
|  | I'm sorry I don't have the corresponding equipment, but I can only provide `srv-x86_64-apple-darwin.tar.gz` (Of course, if someone can sponsor me a Mac I would be very grateful) | ||||||
|  |  | ||||||
|  | #### Windows | ||||||
|  |  | ||||||
|  | Download the pre-compiled `srv-x86_64-pc-windows-msvc.zip` in the [releases](https://github.com/Tim-Paik/srv/releases/latest), unzip the srv.exe and copy it to your `%PATH%` (if you don’t know what this is, move `srv.exe` to `%SystemRoot%\System32`) | ||||||
|  |  | ||||||
|  | ### Compile and Install | ||||||
|  |  | ||||||
|  | You Need: | ||||||
|  |  | ||||||
|  |  - Rust & Cargo Installation | ||||||
|  |  - Git Installation | ||||||
|  |  - Gcc/Msvc Toolchain Installation | ||||||
|  |  | ||||||
|  | ```shell | ||||||
|  | git clone git@github.com:Tim-Paik/srv.git | ||||||
|  | cd srv | ||||||
|  | cargo build --release | ||||||
|  | strip target/release/srv | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Then you can find the compiled executable file named `srv` in the `target/release/` folder. | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  |  | ||||||
|  | Execute `srv --help` to get all the usage methods | ||||||
|  |  | ||||||
|  | Waiting to be added... | ||||||
|  |  | ||||||
|  | ## License | ||||||
|  |  | ||||||
|  | ```text | ||||||
|  | This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  | License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  | file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||||
|  | ``` | ||||||
							
								
								
									
										65
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -203,13 +203,13 @@ fn render_index( | |||||||
|         }; |         }; | ||||||
|         return Ok(ServiceResponse::new(req.clone(), res)); |         return Ok(ServiceResponse::new(req.clone(), res)); | ||||||
|     } |     } | ||||||
|     if var("NOINDEX").unwrap_or("false".to_string()) == "true" { |     if var("NOINDEX").unwrap_or_else(|_| "false".to_string()) == "true" { | ||||||
|         return Ok(ServiceResponse::new( |         return Ok(ServiceResponse::new( | ||||||
|             req.clone(), |             req.clone(), | ||||||
|             HttpResponse::NotFound().body(""), |             HttpResponse::NotFound().body(""), | ||||||
|         )); |         )); | ||||||
|     } |     } | ||||||
|     let show_dot_files = var("DOTFILES").unwrap_or("false".to_string()) == "true"; |     let show_dot_files = var("DOTFILES").unwrap_or_else(|_| "false".to_string()) == "true"; | ||||||
|     let mut context = IndexContext { |     let mut context = IndexContext { | ||||||
|         title: "".to_string(), |         title: "".to_string(), | ||||||
|         paths: vec![], |         paths: vec![], | ||||||
| @ -217,7 +217,7 @@ fn render_index( | |||||||
|         files: vec![], |         files: vec![], | ||||||
|     }; |     }; | ||||||
|     for path in req.path().split('/') { |     for path in req.path().split('/') { | ||||||
|         if path == "" { |         if path.is_empty() { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|         let path = |         let path = | ||||||
| @ -245,7 +245,7 @@ fn render_index( | |||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|                 }; |                 }; | ||||||
|                 if !show_dot_files && name.starts_with(".") { |                 if !show_dot_files && name.starts_with('.') { | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|                 let metadata = match path.metadata() { |                 let metadata = match path.metadata() { | ||||||
| @ -292,7 +292,7 @@ fn render_index( | |||||||
|     }; |     }; | ||||||
|     let index = TEMPLATE |     let index = TEMPLATE | ||||||
|         .render("index", &content) |         .render("index", &content) | ||||||
|         .unwrap_or("TEMPLATE RENDER ERROR".to_string()); |         .unwrap_or_else(|_| "TEMPLATE RENDER ERROR".to_string()); | ||||||
|     let res = HttpResponse::Ok() |     let res = HttpResponse::Ok() | ||||||
|         .content_type("text/html; charset=utf-8") |         .content_type("text/html; charset=utf-8") | ||||||
|         .body(index); |         .body(index); | ||||||
| @ -305,7 +305,7 @@ fn display_path(path: &Path) -> String { | |||||||
|     if root.starts_with("\\\\?\\") { |     if root.starts_with("\\\\?\\") { | ||||||
|         root[4..root.len()].to_string() |         root[4..root.len()].to_string() | ||||||
|     } else { |     } else { | ||||||
|         root.to_string() |         root | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -321,9 +321,14 @@ async fn validator( | |||||||
|     req: dev::ServiceRequest, |     req: dev::ServiceRequest, | ||||||
|     auth: actix_web_httpauth::extractors::basic::BasicAuth, |     auth: actix_web_httpauth::extractors::basic::BasicAuth, | ||||||
| ) -> Result<dev::ServiceRequest, actix_web::Error> { | ) -> Result<dev::ServiceRequest, actix_web::Error> { | ||||||
|     if auth.user_id() == var("AUTH_USERNAME").unwrap_or("".to_string()).as_str() |     if auth.user_id() | ||||||
|  |         == var("AUTH_USERNAME") | ||||||
|  |             .unwrap_or_else(|_| "".to_string()) | ||||||
|  |             .as_str() | ||||||
|         && hash(auth.password().unwrap_or(&std::borrow::Cow::from(""))) |         && hash(auth.password().unwrap_or(&std::borrow::Cow::from(""))) | ||||||
|             == var("AUTH_PASSWORD").unwrap_or("".to_string()).as_str() |             == var("AUTH_PASSWORD") | ||||||
|  |                 .unwrap_or_else(|_| "".to_string()) | ||||||
|  |                 .as_str() | ||||||
|     { |     { | ||||||
|         return Ok(req); |         return Ok(req); | ||||||
|     } |     } | ||||||
| @ -365,7 +370,7 @@ async fn main() -> std::io::Result<()> { | |||||||
|             } |             } | ||||||
|         } "Root directory") |         } "Root directory") | ||||||
|         (@arg address: -a --address +takes_value default_value["0.0.0.0"] { |         (@arg address: -a --address +takes_value default_value["0.0.0.0"] { | ||||||
|             |s| match IpAddr::from_str(&s) { |             |s| match IpAddr::from_str(s) { | ||||||
|                 Ok(_) => Ok(()), |                 Ok(_) => Ok(()), | ||||||
|                 Err(e) => Err(e.to_string()), |                 Err(e) => Err(e.to_string()), | ||||||
|             } |             } | ||||||
| @ -419,7 +424,7 @@ async fn main() -> std::io::Result<()> { | |||||||
|             (@arg log: --log "Enable access log output [default: false]") |             (@arg log: --log "Enable access log output [default: false]") | ||||||
|             (@arg quietall: --quietall "Disable all output") |             (@arg quietall: --quietall "Disable all output") | ||||||
|             (@arg address: -a --address +takes_value default_value["0.0.0.0"] { |             (@arg address: -a --address +takes_value default_value["0.0.0.0"] { | ||||||
|                 |s| match IpAddr::from_str(&s) { |                 |s| match IpAddr::from_str(s) { | ||||||
|                     Ok(_) => Ok(()), |                     Ok(_) => Ok(()), | ||||||
|                     Err(e) => Err(e.to_string()), |                     Err(e) => Err(e.to_string()), | ||||||
|                 } |                 } | ||||||
| @ -455,15 +460,12 @@ async fn main() -> std::io::Result<()> { | |||||||
|         set_var("RUST_LOG_STYLE", "never"); |         set_var("RUST_LOG_STYLE", "never"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     match matches.value_of("auth") { |     if let Some(s) = matches.value_of("auth") { | ||||||
|         Some(s) => { |  | ||||||
|         set_var("ENABLE_AUTH", matches.is_present("auth").to_string()); |         set_var("ENABLE_AUTH", matches.is_present("auth").to_string()); | ||||||
|         let parts = s.splitn(2, ':').collect::<Vec<&str>>(); |         let parts = s.splitn(2, ':').collect::<Vec<&str>>(); | ||||||
|         set_var("AUTH_USERNAME", parts[0]); |         set_var("AUTH_USERNAME", parts[0]); | ||||||
|         set_var("AUTH_PASSWORD", hash(parts[1])); |         set_var("AUTH_PASSWORD", hash(parts[1])); | ||||||
|     } |     } | ||||||
|         None => {} |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if matches.is_present("cors") { |     if matches.is_present("cors") { | ||||||
|         set_var("ENABLE_CORS", matches.is_present("cors").to_string()); |         set_var("ENABLE_CORS", matches.is_present("cors").to_string()); | ||||||
| @ -612,7 +614,7 @@ async fn main() -> std::io::Result<()> { | |||||||
|             let mut style = buf.style(); |             let mut style = buf.style(); | ||||||
|             let green = style.set_color(Color::Rgb(76, 175, 80)); |             let green = style.set_color(Color::Rgb(76, 175, 80)); | ||||||
|             if record.target() == "actix_web::middleware::logger" { |             if record.target() == "actix_web::middleware::logger" { | ||||||
|                 let data: Vec<&str> = data.splitn(5, "^").collect(); |                 let data: Vec<&str> = data.splitn(5, '^').collect(); | ||||||
|                 let time = blue.value( |                 let time = blue.value( | ||||||
|                     chrono::NaiveDateTime::from_str(data[0]) |                     chrono::NaiveDateTime::from_str(data[0]) | ||||||
|                         .unwrap() |                         .unwrap() | ||||||
| @ -626,7 +628,7 @@ async fn main() -> std::io::Result<()> { | |||||||
|                 } else { |                 } else { | ||||||
|                     red.value(status_code) |                     red.value(status_code) | ||||||
|                 }; |                 }; | ||||||
|                 let process_time: Vec<&str> = data[3].splitn(2, ".").collect(); |                 let process_time: Vec<&str> = data[3].splitn(2, '.').collect(); | ||||||
|                 let process_time = process_time[0].to_string() + "ms"; |                 let process_time = process_time[0].to_string() + "ms"; | ||||||
|                 let process_time = blue.value(if process_time.len() == 3 { |                 let process_time = blue.value(if process_time.len() == 3 { | ||||||
|                     "  ".to_string() + &process_time |                     "  ".to_string() + &process_time | ||||||
| @ -664,7 +666,7 @@ async fn main() -> std::io::Result<()> { | |||||||
|                             .map_or("", |m| m.as_str()); |                             .map_or("", |m| m.as_str()); | ||||||
|                         let data = format!( |                         let data = format!( | ||||||
|                             "[INFO] Serving {} on {}", |                             "[INFO] Serving {} on {}", | ||||||
|                             var("ROOT").unwrap_or(".".to_string()), |                             var("ROOT").unwrap_or_else(|_| ".".to_string()), | ||||||
|                             addr |                             addr | ||||||
|                         ); |                         ); | ||||||
|                         return writeln!(buf, "\r{}", green.value(data)); |                         return writeln!(buf, "\r{}", green.value(data)); | ||||||
| @ -683,14 +685,14 @@ async fn main() -> std::io::Result<()> { | |||||||
|         .init(); |         .init(); | ||||||
|  |  | ||||||
|     let server = HttpServer::new(move || { |     let server = HttpServer::new(move || { | ||||||
|         let compress = if var("COMPRESS").unwrap_or("false".to_string()) == "true" { |         let compress = if var("COMPRESS").unwrap_or_else(|_| "false".to_string()) == "true" { | ||||||
|             http::header::ContentEncoding::Auto |             http::header::ContentEncoding::Auto | ||||||
|         } else { |         } else { | ||||||
|             http::header::ContentEncoding::Identity |             http::header::ContentEncoding::Identity | ||||||
|         }; |         }; | ||||||
|         let app = App::new() |         let app = App::new() | ||||||
|             .wrap_fn(|req, srv| { |             .wrap_fn(|req, srv| { | ||||||
|                 let paths = PathBuf::from_str(req.path()).unwrap_or(PathBuf::default()); |                 let paths = PathBuf::from_str(req.path()).unwrap_or_default(); | ||||||
|                 let mut isdotfile = false; |                 let mut isdotfile = false; | ||||||
|                 for path in paths.iter() { |                 for path in paths.iter() { | ||||||
|                     if path.to_string_lossy().starts_with('.') { |                     if path.to_string_lossy().starts_with('.') { | ||||||
| @ -700,20 +702,21 @@ async fn main() -> std::io::Result<()> { | |||||||
|                 let fut = srv.call(req); |                 let fut = srv.call(req); | ||||||
|                 async move { |                 async move { | ||||||
|                     Ok(fut.await?.map_body(|head, body| { |                     Ok(fut.await?.map_body(|head, body| { | ||||||
|                         if var("NOCACHE").unwrap_or("false".to_string()) == "true" { |                         if var("NOCACHE").unwrap_or_else(|_| "false".to_string()) == "true" { | ||||||
|                             head.headers_mut().insert( |                             head.headers_mut().insert( | ||||||
|                                 http::header::CACHE_CONTROL, |                                 http::header::CACHE_CONTROL, | ||||||
|                                 http::HeaderValue::from_static("no-store"), |                                 http::HeaderValue::from_static("no-store"), | ||||||
|                             ); |                             ); | ||||||
|                         } |                         } | ||||||
|                         if var("ENABLE_CORS").unwrap_or("false".to_string()) == "true" { |                         if var("ENABLE_CORS").unwrap_or_else(|_| "false".to_string()) == "true" { | ||||||
|                             let cors = var("CORS").unwrap_or("*".to_string()); |                             let cors = var("CORS").unwrap_or_else(|_| "*".to_string()); | ||||||
|                             let cors = http::HeaderValue::from_str(&cors) |                             let cors = http::HeaderValue::from_str(&cors) | ||||||
|                                 .unwrap_or(http::HeaderValue::from_static("*")); |                                 .unwrap_or_else(|_| http::HeaderValue::from_static("*")); | ||||||
|                             head.headers_mut() |                             head.headers_mut() | ||||||
|                                 .insert(http::header::ACCESS_CONTROL_ALLOW_ORIGIN, cors); |                                 .insert(http::header::ACCESS_CONTROL_ALLOW_ORIGIN, cors); | ||||||
|                         } |                         } | ||||||
|                         if isdotfile && !(var("DOTFILES").unwrap_or("false".to_string()) == "true") |                         if isdotfile | ||||||
|  |                             && var("DOTFILES").unwrap_or_else(|_| "false".to_string()) != "true" | ||||||
|                         { |                         { | ||||||
|                             head.status = http::StatusCode::FORBIDDEN; |                             head.status = http::StatusCode::FORBIDDEN; | ||||||
|                             *head.headers_mut() = http::HeaderMap::new(); |                             *head.headers_mut() = http::HeaderMap::new(); | ||||||
| @ -725,11 +728,11 @@ async fn main() -> std::io::Result<()> { | |||||||
|             }) |             }) | ||||||
|             .wrap(middleware::Compress::new(compress)) |             .wrap(middleware::Compress::new(compress)) | ||||||
|             .wrap(middleware::Condition::new( |             .wrap(middleware::Condition::new( | ||||||
|                 var("ENABLE_AUTH").unwrap_or("false".to_string()) == "true", |                 var("ENABLE_AUTH").unwrap_or_else(|_| "false".to_string()) == "true", | ||||||
|                 actix_web_httpauth::middleware::HttpAuthentication::basic(validator), |                 actix_web_httpauth::middleware::HttpAuthentication::basic(validator), | ||||||
|             )) |             )) | ||||||
|             .wrap(middleware::Logger::new("%t^%a^%s^%D^%r")); |             .wrap(middleware::Logger::new("%t^%a^%s^%D^%r")); | ||||||
|         let files = fs::Files::new("/", var("ROOT").unwrap_or(".".to_string())) |         let files = fs::Files::new("/", var("ROOT").unwrap_or_else(|_| ".".to_string())) | ||||||
|             .use_hidden_files() |             .use_hidden_files() | ||||||
|             .prefer_utf8(true) |             .prefer_utf8(true) | ||||||
|             .show_files_listing() |             .show_files_listing() | ||||||
| @ -737,23 +740,23 @@ async fn main() -> std::io::Result<()> { | |||||||
|             .default_handler(|req: dev::ServiceRequest| { |             .default_handler(|req: dev::ServiceRequest| { | ||||||
|                 let (http_req, _payload) = req.into_parts(); |                 let (http_req, _payload) = req.into_parts(); | ||||||
|                 async { |                 async { | ||||||
|                     let path = var("ROOT").unwrap_or(".".to_string()); |                     let path = var("ROOT").unwrap_or_else(|_| ".".to_string()); | ||||||
|                     let mut path = Path::new(&path).to_path_buf(); |                     let mut path = Path::new(&path).to_path_buf(); | ||||||
|                     path.push("index.html"); |                     path.push("index.html"); | ||||||
|                     if path.exists() |                     if path.exists() | ||||||
|                         && path.is_file() |                         && path.is_file() | ||||||
|                         && var("SPA").unwrap_or("false".to_string()) == "true" |                         && var("SPA").unwrap_or_else(|_| "false".to_string()) == "true" | ||||||
|                     { |                     { | ||||||
|                         let res = fs::NamedFile::open(path)?.into_response(&http_req)?; |                         let res = fs::NamedFile::open(path)?.into_response(&http_req)?; | ||||||
|                         return Ok(ServiceResponse::new(http_req, res)); |                         return Ok(ServiceResponse::new(http_req, res)); | ||||||
|                     } |                     } | ||||||
|                     return Ok(ServiceResponse::new( |                     Ok(ServiceResponse::new( | ||||||
|                         http_req, |                         http_req, | ||||||
|                         HttpResponse::NotFound().body(""), |                         HttpResponse::NotFound().body(""), | ||||||
|                     )); |                     )) | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         return app.service(files); |         app.service(files) | ||||||
|     }); |     }); | ||||||
|     let server = if enable_tls { |     let server = if enable_tls { | ||||||
|         let cert = &mut BufReader::new( |         let cert = &mut BufReader::new( | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user