582 lines
19 KiB
Rust
582 lines
19 KiB
Rust
use std::io::{Write, BufReader, BufRead, Read};
|
|
use std::thread;
|
|
use std::net::{TcpListener, TcpStream};
|
|
use std::fs;
|
|
use std::fs::File;
|
|
use std::string::{String};
|
|
use std::process::{Command, Stdio};
|
|
use packeteer::http1::*;
|
|
use frostwalker;
|
|
use packeteer::{generate_kvheader, unwrap_url_into_segments};
|
|
|
|
struct Resource {
|
|
contents: Vec<u8>,
|
|
status_code: i32,
|
|
mime: String,
|
|
iscgi: bool,
|
|
}
|
|
|
|
struct GetPageResult {
|
|
is500: bool,
|
|
is502: bool,
|
|
is403: bool,
|
|
contents: Vec<u8>,
|
|
iscgi: bool,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Settings {
|
|
cgi: bool,
|
|
index_generation: bool,
|
|
address: String,
|
|
logging: bool,
|
|
server_header: bool,
|
|
time_header: bool,
|
|
}
|
|
|
|
impl Settings {
|
|
fn new() -> Settings {
|
|
return Settings { cgi: true, index_generation: true, address: "0.0.0.0:8080".to_string(), logging: false, server_header: true, time_header: false};
|
|
}
|
|
}
|
|
|
|
fn process_cgi(filename: String, post_data: Option<String>) -> Option<Vec<u8>> {
|
|
// This is gonna be the bodgiest implementation of CGI that anyone
|
|
// has ever seen in the history of the fucking world
|
|
|
|
let query;
|
|
let script;
|
|
|
|
if filename.contains("?") {
|
|
let vars = filename.to_string().find("?").unwrap();
|
|
let vars1 = vars + 1;
|
|
query = &filename[vars1..];
|
|
script = &filename[..vars];
|
|
} else {
|
|
query = "";
|
|
script = &filename;
|
|
}
|
|
|
|
let output;
|
|
if query != "".to_string() && post_data.is_some() {
|
|
let mut child = Command::new(format!("./{}", script))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "POST")
|
|
.env("QUERY_STRING", query)
|
|
.env("SCRIPT_NAME", script)
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
let mut stdin = child.stdin.take().unwrap();
|
|
stdin.write_all(post_data.unwrap().as_bytes()).unwrap();
|
|
output = child.wait_with_output();
|
|
} else if query == "".to_string() && post_data.is_some() {
|
|
let mut child = Command::new(format!("./{}", script))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "POST")
|
|
.env("SCRIPT_NAME", script)
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
let mut stdin = child.stdin.take().unwrap();
|
|
stdin.write_all(post_data.unwrap().as_bytes()).unwrap();
|
|
output = child.wait_with_output();
|
|
}
|
|
else if query != "" {
|
|
output = Command::new(format!("./{}", script))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "GET")
|
|
.env("QUERY_STRING", query)
|
|
.env("SCRIPT_NAME", script)
|
|
.output();
|
|
} else {
|
|
output = Command::new(format!("./{}", script))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "GET")
|
|
.env("SCRIPT_NAME", script)
|
|
.output();
|
|
}
|
|
if output.is_ok() {
|
|
return Some(output.unwrap().stdout);
|
|
} else {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
fn process_cgi_with_path(filename: String, segments: Vec<String>, location: i32, post_data: Option<String>) -> Option<Vec<u8>> {
|
|
let mut query: String = "".to_string();
|
|
let script = filename;
|
|
|
|
let mut i = 0;
|
|
let mut path = "".to_string();
|
|
for seg in segments {
|
|
if !(i <= location) && !(seg.contains("?")) {
|
|
if path == "".to_string() {
|
|
path = format!("/{}", seg);
|
|
} else {
|
|
path = format!("{}/{}", path, seg);
|
|
}
|
|
}
|
|
|
|
if !(i <= location) && seg.contains("?") {
|
|
let split: Vec<&str> = seg.as_str().split("?").collect();
|
|
query = split[split.len()-1].to_string();
|
|
path = format!("{}/{}", path, split[0].to_string());
|
|
}
|
|
i = i + 1;
|
|
}
|
|
|
|
path = format!("{}/", path);
|
|
let output;
|
|
if query != "".to_string() && post_data.is_some() {
|
|
let mut child = Command::new(format!("./{}", script.clone()))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "POST")
|
|
.env("QUERY_STRING", query)
|
|
.env("PATH_INFO", path.clone())
|
|
.env("SCRIPT_NAME", script.clone())
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
let mut stdin = child.stdin.take().unwrap();
|
|
stdin.write_all(post_data.unwrap().as_bytes()).unwrap();
|
|
output = child.wait_with_output();
|
|
} else if query == "".to_string() && post_data.is_some() {
|
|
let mut child = Command::new(format!("./{}", script.clone()))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "POST")
|
|
.env("PATH_INFO", path.clone())
|
|
.env("SCRIPT_NAME", script.clone())
|
|
.stdin(Stdio::piped())
|
|
.stdout(Stdio::piped())
|
|
.spawn()
|
|
.unwrap();
|
|
let mut stdin = child.stdin.take().unwrap();
|
|
stdin.write_all(post_data.unwrap().as_bytes()).unwrap();
|
|
output = child.wait_with_output();
|
|
}
|
|
else if query != "".to_string() && post_data.is_none() {
|
|
output = Command::new(format!("./{}", script.clone()))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "GET")
|
|
.env("QUERY_STRING", query)
|
|
.env("PATH_INFO", path.clone())
|
|
.env("SCRIPT_NAME", script.clone())
|
|
.output();
|
|
} else {
|
|
output = Command::new(format!("./{}", script.clone()))
|
|
.env("GATEWAY_INTERFACE", "CGI/1.1")
|
|
.env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION")))
|
|
.env("REQUEST_METHOD", "GET")
|
|
.env("PATH_INFO", path.clone())
|
|
.env("SCRIPT_NAME", script.clone())
|
|
.output();
|
|
}
|
|
if output.is_ok() {
|
|
return Some(output.unwrap().stdout);
|
|
} else {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
fn detect_media_type(filename: String) -> String {
|
|
|
|
// The Lynx terminal browser made me do this.
|
|
|
|
let path = filename.to_string();
|
|
let paths = check_if_path_exists(path);
|
|
let path = filename.to_string();
|
|
let dir = check_if_dir(path);
|
|
|
|
if paths == true && dir == false && filename.contains(".") == false {
|
|
return "text/html".to_string();
|
|
} else {
|
|
let ext_index = filename.as_str().split(".").collect::<Vec<&str>>();
|
|
let ext = ext_index[ext_index.len()-1];
|
|
|
|
match ext {
|
|
"aac" => "audio/aac".to_string(),
|
|
"avi" => "video/x-msvideo".to_string(),
|
|
"bmp" => "image/bmp".to_string(),
|
|
"bz2" => "application/x-bzip2".to_string(),
|
|
"css" => "text/css".to_string(),
|
|
"gz" => "application/gzip".to_string(),
|
|
"gif" => "image/gif".to_string(),
|
|
"png" => "image/png".to_string(),
|
|
"pdf" => "application/pdf".to_string(),
|
|
"jpeg" => "image/jpeg".to_string(),
|
|
"jpg" => "image/jpeg".to_string(),
|
|
"js" => "text/javascript".to_string(),
|
|
"mid" => "audio/midi".to_string(),
|
|
"midi" => "audio/midi".to_string(),
|
|
"mp3" => "audio/mpeg".to_string(),
|
|
"mp4" => "video/mp4".to_string(),
|
|
"mpeg" => "video/mpeg".to_string(),
|
|
"ogg" => "audio/ogg".to_string(),
|
|
"oga" => "audio/ogg".to_string(),
|
|
"ogv" => "video/ogg".to_string(),
|
|
"opus" => "audio/opus".to_string(),
|
|
"txt" => "text/plain".to_string(),
|
|
"html" => "text/html".to_string(),
|
|
"htm" => "text/html".to_string(),
|
|
_ => "application/octet-stream".to_string(),
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fn check_if_path_exists(path: String) -> bool {
|
|
|
|
// This is probably not the best way of checking if a path
|
|
// exists but I don't care :)
|
|
|
|
let file = fs::metadata(path);
|
|
|
|
match file {
|
|
Ok(_) => true,
|
|
Err(_) => false,
|
|
}
|
|
|
|
}
|
|
|
|
fn check_if_dir(directory: String) -> bool {
|
|
let path = directory + "/index.html";
|
|
let result = check_if_path_exists(path);
|
|
|
|
return result;
|
|
}
|
|
|
|
fn generate_index(directory: String) -> String {
|
|
|
|
let mut index = format!("<!DOCTYPE HTML><html><body><h1>Directory of {}</h1><hr/><a href=\"/{}/../\">Parent Directory</a><br/>", directory, directory);
|
|
for file in fs::read_dir(directory).unwrap() {
|
|
index = format!("{}<a href={}>{}</a><br/>", index, format!("\"/{}\"", file.as_ref().unwrap().path().display().to_string()), file.unwrap().file_name().into_string().unwrap_or("!!".to_string()));
|
|
}
|
|
|
|
return format!("{}<hr/><i>Generated by herb/{}</i>", index, env!("CARGO_PKG_VERSION")).to_string();
|
|
|
|
}
|
|
|
|
fn get_page(filename: String, settings: Settings) -> GetPageResult {
|
|
|
|
// The loaded page should be left immutable as it does
|
|
// not need to be modified by the server.
|
|
|
|
let path = filename.to_string();
|
|
let checks = check_if_path_exists(path.clone());
|
|
let index = check_if_dir(path.clone());
|
|
|
|
let mut resultstruct = GetPageResult { is500: false, is502: false, is403: false, contents: vec![], iscgi: false };
|
|
|
|
if settings.logging {
|
|
println!("{} {} {}", path, checks, index);
|
|
}
|
|
|
|
if checks == true && index == false {
|
|
if path.contains(".") != true && path.contains(".cgi?") != true {
|
|
let result;
|
|
if settings.index_generation == false {
|
|
result = "<!DOCTYPE HTML><html><body><h1>403 Forbidden</h1><p>You do not have permission to access this resource</p></body></html>".to_string();
|
|
resultstruct.is403 = true;
|
|
} else {
|
|
result = generate_index(filename);
|
|
}
|
|
|
|
resultstruct.contents = result.to_string().into_bytes();
|
|
return resultstruct;
|
|
} else if path.contains(".cgi?") {
|
|
if settings.cgi == false {
|
|
let result = "<!DOCTYPE HTML><html><body><h1>403 Forbidden</h1><p>You do not have permission to access this resource</p></body></html>".to_string();
|
|
resultstruct.contents = result.into_bytes();
|
|
resultstruct.is403 = true;
|
|
return resultstruct;
|
|
}
|
|
|
|
let result = process_cgi(filename, None);
|
|
if result.is_some() {
|
|
resultstruct.contents = result.unwrap();
|
|
resultstruct.iscgi = true;
|
|
return resultstruct;
|
|
} else {
|
|
resultstruct.contents = "<!DOCTYPE HTML><html><body><h1>500 Internal Server Error</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html>".to_string().into_bytes();
|
|
resultstruct.is500 = true;
|
|
return resultstruct;
|
|
}
|
|
}
|
|
}
|
|
|
|
if filename.contains(".cgi") {
|
|
if settings.cgi == false {
|
|
let result = "<!DOCTYPE HTML><html><body><h1>403 Forbidden</h1><p>You do not have permission to access this resource</p></body></html>".to_string();
|
|
resultstruct.contents = result.into_bytes();
|
|
resultstruct.is403 = true;
|
|
return resultstruct;
|
|
}
|
|
|
|
let result = process_cgi(filename, None);
|
|
if result.is_some() {
|
|
resultstruct.contents = result.unwrap();
|
|
resultstruct.iscgi = true;
|
|
return resultstruct;
|
|
} else {
|
|
resultstruct.contents = "<!DOCTYPE HTML><html><body><h1>502 Bad Gateway</h1><p>The server is unable to complete your request due to an error.</p></body></html>".to_string().into_bytes();
|
|
resultstruct.is502 = true;
|
|
return resultstruct;
|
|
}
|
|
} else {
|
|
let f = File::open(filename.clone());
|
|
if f.is_ok() {
|
|
let mut buf: Vec<u8> = vec![];
|
|
let result = f.unwrap().read_to_end(&mut buf);
|
|
if result.is_ok() {
|
|
resultstruct.contents = buf;
|
|
return resultstruct;
|
|
} else {
|
|
resultstruct.contents = "<!DOCTYPE HTML><html><body><h1>502 Bad Gateway</h1><p>The server is unable to complete your request due to an error.</p></body></html>".to_string().into_bytes();
|
|
resultstruct.is502 = true;
|
|
return resultstruct;
|
|
}
|
|
} else {
|
|
if filename == "index.html" {
|
|
let newresult;
|
|
if settings.index_generation == false {
|
|
newresult = "<!DOCTYPE HTML><html><body><h1>403 Forbidden</h1><p>You do not have permission to access this resource</p></body></html>".to_string();
|
|
resultstruct.is403 = true;
|
|
} else {
|
|
newresult = generate_index(".".to_string());
|
|
}
|
|
resultstruct.contents = newresult.to_string().into_bytes();
|
|
return resultstruct;
|
|
}
|
|
resultstruct.contents = "<!DOCTYPE HTML><html><body><h1>500 Internal Server Error</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html>".to_string().into_bytes();
|
|
resultstruct.is500 = true;
|
|
return resultstruct;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn grab_time() -> String{
|
|
let output = Command::new("date")
|
|
.arg("+'%a, %d %b %Y %H:%m:%S %Z'")
|
|
.output()
|
|
.expect("failed to execute process");
|
|
|
|
return String::from_utf8_lossy(&output.stdout).to_string().replace("'","").replace("\n","");
|
|
}
|
|
|
|
|
|
fn process_request(request: Vec<u8>, settings: Settings) -> Resource {
|
|
let input = String::from_utf8_lossy(&request).to_string();
|
|
let prerequest = construct_request(&input);
|
|
let request;
|
|
if prerequest.is_some() {
|
|
request = prerequest.unwrap();
|
|
} else {
|
|
let resource = Resource { contents: "<!DOCTYPE HTML><html><body><h1>400 Bad Request</h1><p>The request you sent appears to be malformed.</p></body></html>".to_string().into_bytes(), status_code: 400, mime: "text/html".to_string(), iscgi: false };
|
|
return resource;
|
|
}
|
|
let mut path: String;
|
|
|
|
let mut index = String::new();
|
|
let output;
|
|
|
|
if &request.method == "GET" {
|
|
if settings.logging {
|
|
println!("Stream sent GET request.");
|
|
}
|
|
if request.location.segments.len() != 0 {
|
|
if request.location.segments[0] == ".." || request.location.segments[0] == "." {
|
|
let resource = Resource { contents: "<!DOCTYPE HTML><html><body><h1>400 Bad Request</h1><p>The request you sent appears to be malformed.</p></body></html>".to_string().into_bytes(), status_code: 400, mime: "text/html".to_string(), iscgi: false };
|
|
return resource;
|
|
}
|
|
|
|
let segclone = request.location.segments.clone();
|
|
path = unwrap_url_into_segments(request.location);
|
|
if path.contains(".cgi/") {
|
|
let mut cgiscript = 0;
|
|
let mut i = 0;
|
|
let mut cgipath: Vec<String> = vec![];
|
|
for segment in &segclone {
|
|
if segment.contains(".cgi") {
|
|
cgipath.push(segment.to_string());
|
|
cgiscript = i;
|
|
break;
|
|
} else {
|
|
cgipath.push(segment.to_string());
|
|
i = i + 1;
|
|
}
|
|
}
|
|
let mut cgipathraw = "".to_string();
|
|
for seg in cgipath {
|
|
if cgipathraw == "".to_string() {
|
|
cgipathraw = seg;
|
|
} else {
|
|
cgipathraw = format!("{}/{}", cgipathraw, seg);
|
|
}
|
|
}
|
|
if settings.cgi == false {
|
|
let newcontents = "<!DOCTYPE HTML><html><body><h1>403 Forbidden</h1><p>You do not have permission to access this resource</p></body></html>".to_string().into_bytes();
|
|
let resource = Resource { contents: newcontents, status_code: 403, mime: "text/html".to_string(), iscgi: false };
|
|
return resource;
|
|
}
|
|
|
|
let contents = process_cgi_with_path(cgipathraw, segclone, cgiscript, None);
|
|
if contents.is_some() {
|
|
let resource = Resource { contents: contents.unwrap(), status_code: 200, mime: "text/html".to_string(), iscgi: true };
|
|
return resource;
|
|
} else {
|
|
let newcontents = "<!DOCTYPE HTML><html><body><h1>502 Bad Gateway</h1><p>The server is unable to complete your request due to an error.</p></body></html>".to_string().into_bytes();
|
|
let resource = Resource { contents: newcontents, status_code: 502, mime: "text/html".to_string(), iscgi: false };
|
|
return resource;
|
|
}
|
|
}
|
|
path.remove(0);
|
|
let exists = check_if_path_exists(path.clone());
|
|
|
|
if exists == false && path.contains(".cgi?") == false {
|
|
let resource = Resource { contents: "<!DOCTYPE HTML><html><body><h1>404 Not Found</h1><p>The resource you are trying to locate cannot be accessed!</p></body></html>".to_string().into_bytes(), status_code: 404, mime: "text/html".to_string(), iscgi: false };
|
|
return resource;
|
|
} else {
|
|
let dir = check_if_dir(path.clone());
|
|
if dir == true {
|
|
index += &path;
|
|
index += "/index.html";
|
|
output = index.as_str();
|
|
} else {
|
|
output = path.as_str();
|
|
}
|
|
}
|
|
} else {
|
|
output = "index.html";
|
|
}
|
|
} else {
|
|
let resource = Resource { contents: "<!DOCTYPE HTML><html><body><h1>501 Not Implemented</h1><p>The request sent by your web browser cannot be handled by this server software.</p></body></html>".to_string().into_bytes(), status_code: 501, mime: "text/html".to_string(), iscgi: false };
|
|
if settings.logging {
|
|
println!("Stream sent unhandlable request.");
|
|
}
|
|
return resource;
|
|
}
|
|
|
|
// Did you want to see chars.as_str().to_string()?
|
|
let rescontents = get_page(output.to_string(), settings.clone());
|
|
let mut resource = Resource { contents: rescontents.contents, status_code: 200, mime: detect_media_type(output.split("?").collect::<Vec<&str>>()[0].to_string()), iscgi: false };
|
|
if rescontents.is500 {
|
|
resource.status_code = 500;
|
|
resource.mime = "text/html".to_string();
|
|
}
|
|
|
|
if rescontents.is502 {
|
|
resource.status_code = 502;
|
|
resource.mime = "text/html".to_string();
|
|
}
|
|
|
|
if rescontents.is403 {
|
|
resource.status_code = 403;
|
|
resource.mime = "text/html".to_string();
|
|
}
|
|
|
|
if rescontents.iscgi {
|
|
resource.iscgi = true;
|
|
}
|
|
|
|
return resource;
|
|
|
|
}
|
|
|
|
fn serve(mut stream: TcpStream, settings: Settings) {
|
|
thread::spawn(move || {
|
|
if settings.logging {
|
|
println!("Stream thread created.");
|
|
}
|
|
|
|
let mut request = Vec::new();
|
|
let mut reader = BufReader::new(&mut stream);
|
|
reader
|
|
.read_until(b'\n', &mut request)
|
|
.expect("Failed to read from stream!");
|
|
|
|
let resource = process_request(request, settings.clone());
|
|
|
|
let mut response_constructed = generate_response(resource.status_code, "");
|
|
response_constructed.headers.push(generate_kvheader("Content-Type", &resource.mime));
|
|
response_constructed.headers.push(generate_kvheader("Server", &format!("herb/{}", env!("CARGO_PKG_VERSION"))));
|
|
|
|
if cfg!(unix) {
|
|
response_constructed.headers.push(generate_kvheader("Date", &grab_time()))
|
|
}
|
|
|
|
if resource.iscgi {
|
|
let header = "HTTP/1.1 200 OK\r\n".to_string().into_bytes();
|
|
stream.write(header.as_slice()).unwrap();
|
|
stream.write(&resource.contents).unwrap();
|
|
stream.flush().unwrap();
|
|
return;
|
|
}
|
|
|
|
let mut contents_clone = resource.contents.clone();
|
|
let mut unpacked_response = unpack_response(response_constructed).into_bytes();
|
|
unpacked_response.append(&mut contents_clone);
|
|
|
|
stream.write(unpacked_response.as_slice()).unwrap();
|
|
stream.flush().unwrap();
|
|
});
|
|
}
|
|
|
|
fn process_settings() -> Settings {
|
|
let string = fs::read_to_string("../settings.toml");
|
|
if string.is_err() {
|
|
return Settings::new();
|
|
}
|
|
let parsed = frostwalker::parse(&string.unwrap());
|
|
if parsed.is_err() {
|
|
return Settings::new();
|
|
}
|
|
let hashmap = parsed.unwrap();
|
|
let mut settings = Settings::new();
|
|
if hashmap.get("cgi").is_some() {
|
|
if hashmap.get("cgi").unwrap_or(&"".to_string()).parse::<bool>().is_ok() {
|
|
settings.cgi = hashmap.get("cgi").unwrap_or(&"true".to_string()).parse::<bool>().unwrap_or(true);
|
|
}
|
|
}
|
|
|
|
if hashmap.get("index_generation").is_some() {
|
|
if hashmap.get("index_generation").unwrap_or(&"".to_string()).parse::<bool>().is_ok() {
|
|
settings.index_generation = hashmap.get("index_generation").unwrap_or(&"true".to_string()).parse::<bool>().unwrap_or(true);
|
|
}
|
|
}
|
|
|
|
if hashmap.get("logging").is_some() {
|
|
if hashmap.get("logging").unwrap_or(&"".to_string()).parse::<bool>().is_ok() {
|
|
settings.logging = hashmap.get("logging").unwrap_or(&"false".to_string()).parse::<bool>().unwrap_or(true);
|
|
}
|
|
}
|
|
|
|
if hashmap.get("address").is_some() {
|
|
settings.address = hashmap.get("address").unwrap_or(&"0.0.0.0:8080".to_string()).to_string();
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
fn main() -> std::io::Result<()> {
|
|
let settings = process_settings();
|
|
let listen = TcpListener::bind(settings.address.clone())?;
|
|
|
|
for stream in listen.incoming() {
|
|
if settings.logging {
|
|
println!("Serving incoming stream.");
|
|
}
|
|
serve(stream?, settings.clone());
|
|
}
|
|
Ok(())
|
|
}
|