How to do it...

  1. Open the Cargo.toml file that has been generated for you.
  2. Under [dependencies], if you didn't do so in the last recipe, add the following lines:
futures = "0.1.18"
hyper = "0.11.21"
  1. If you want, you can go to futures' (https://crates.io/crates/futures) and hyper's (https://crates.io/crates/hyper) crates.io pages to check for the newest version and use that one instead.
  2. In the folder chapter-nine, create a folder called files.
  3. In the folder files, create a file called index.html and add the following code to it:
<!doctype html>
<html>

<head>
<link rel="stylesheet" type="text/css" href="/style.css">
<title>Home</title>
</head>

<body>
<h1>Home</h1>
<p>Welcome. You can access other files on this web server
aswell! Available links:</p>
<ul>
<li>
<a href="/foo.html">Foo!</a>
</li>
<li>
<a href="/bar.html">Bar!</a>
</li>
</ul>
</body>

</html>
  1. In the folder files, create a file called foo.html and add the following code to it:
<!doctype html>
<html>

<head>
<link rel="stylesheet" type="text/css" href="/style.css">
<title>Foo</title>
</head>

<body>
<p>Foo!</p>
</body>

</html>
  1. In the folder files, create a file called bar.html and add the following code to it:
<!doctype html>
<html>

<head>
<link rel="stylesheet" type="text/css" href="/style.css">
<title>Bar</title>
</head>

<body>
<p>Bar!</p>
</body>

</html>
  1. In the folder files, create a file called not_found.html and add the following code to it:
<!doctype html>
<html>

<head>
<link rel="stylesheet" type="text/css" href="/style.css">
<title>Page Not Found</title>
</head>

<body>
<h1>Page Not Found</h1>
<p>We're sorry, we couldn't find the page you requested.</p>
<p>Maybe it was renamed or moved?</p>
<p>Try searching at the
<a href="/index.html">start page</a>
</p>
</body>

</html>
  1. In the folder files, create a file called invalid_method.html and add the following code to it:
<!doctype html>
<html>

<head>
<link rel="stylesheet" type="text/css" href="/style.css">
<title>Error 405 (Method Not Allowed)</title>
</head>

<body>
<h1>Error 405</h1>
<p>The method used is not allowed for this URL</p>
</body>

</html>
  1. In the folder src/bin, create a file called file_server.rs.
  2. Add the following code and run it with cargo run --bin echo_server_with_routing:
1   extern crate futures;
2   extern crate hyper;
3
4   use hyper::{Method, StatusCode};
5   use hyper::server::{const_service, service_fn, Http, Request,  
Response}; 6 use hyper::header::{ContentLength, ContentType}; 7 use hyper::mime; 8 use futures::Future; 9 use futures::sync::oneshot; 10 use std::net::SocketAddr; 11 use std::thread; 12 use std::fs::File; 13 use std::io::{self, copy}; 14 15 fn main() { 16 let addr = "[::1]:3000".parse().expect("Failed to parse
address"); 17 run_file_server(&addr).expect("Failed to run web server"); 18 } 19 20 fn run_file_server(addr: &SocketAddr) -> Result<(),
hyper::Error> { 21 let file_service = const_service(service_fn(|req: Request| { 22 // Setting up our routes 23 match (req.method(), req.path()) { 24 (&Method::Get, "/") => handle_root(), 25 (&Method::Get, path) => handle_get_file(path), 26 _ => handle_invalid_method(), 27 } 28 })); 29 30 let server = Http::new().bind(addr, file_service)?; 31 server.run() 32 }

The following are the route handlers:

34  // Because we don't want the entire server to block when serving 
a file, 35 // we are going to return a response wrapped in a future 36 type ResponseFuture = Box<Future>; 37 fn handle_root() -> ResponseFuture { 38 // Send the landing page 39 send_file_or_404("index.html") 40 } 41 42 fn handle_get_file(file: &str) -> ResponseFuture { 43 // Send whatever page was requested or fall back to a 404 page 44 send_file_or_404(file) 45 } 46 47 fn handle_invalid_method() -> ResponseFuture { 48 // Send a page telling the user that the method he used is not
supported 49 let response_future = send_file_or_404("invalid_method.html") 50 // Set the correct status code 51 .and_then(|response|
Ok(response.with_status(StatusCode::MethodNotAllowed))); 52 Box::new(response_future) 53 }

The following is the code for the functions returning the futures with our files:

55  // Send a future containing a response with the requested file  
or a 404 page 56 fn send_file_or_404(path: &str) -> ResponseFuture { 57 // Sanitize the input to prevent unwanted data access 58 let path = sanitize_path(path); 59 60 let response_future = try_to_send_file(&path) 61 // try_to_send_file returns a future of Result<Response,
io::Error> 62 // turn it into a future of a future of Response with an
error of hyper::Error 63 .and_then(|response_result| response_result.map_err(|error|
error.into())) 64 // If something went wrong, send the 404 page instead 65 .or_else(|_| send_404()); 66 Box::new(response_future) 67 } 68 69 // Return a requested file in a future of Result<Response,
io::Error> 70 // to indicate whether it exists or not 71 type ResponseResultFuture = Box<Future, Error = hyper::Error>>; 72 fn try_to_send_file(file: &str) -> ResponseResultFuture { 73 // Prepend "files/" to the file 74 let path = path_on_disk(file); 75 // Load the file in a separate thread into memory. 76 // As soon as it's done, send it back through a channel 77 let (tx, rx) = oneshot::channel(); 78 thread::spawn(move || { 79 let mut file = match File::open(&path) { 80 Ok(file) => file, 81 Err(err) => { 82 println!("Failed to find file: {}", path); 83 // Send error through channel 84 tx.send(Err(err)).expect("Send error on file not
found"); 85 return; 86 } 87 }; 88 89 // buf is our in-memory representation of the file 90 let mut buf: Vec = Vec::new(); 91 match copy(&mut file, &mut buf) { 92 Ok(_) => { 93 println!("Sending file: {}", path); 94 // Detect the content type by checking the file
extension 95 // or fall back to plaintext 96 let content_type =
get_content_type(&path).unwrap_or_else
(ContentType::plaintext); 97 let res = Response::new() 98 .with_header(ContentLength(buf.len() as u64)) 99 .with_header(content_type) 100 .with_body(buf); 101 // Send file through channel 102 tx.send(Ok(res)) 103 .expect("Send error on successful file read"); 104 } 105 Err(err) => { 106 // Send error through channel 107 tx.send(Err(err)).expect("Send error on error reading
file"); 108 } 109 }; 110 }); 111 // Convert all encountered errors to hyper::Error 112 Box::new(rx.map_err(|error|
io::Error::new(io::ErrorKind::Other,
error).into())) 113 } 114 115 fn send_404() -> ResponseFuture { 116 // Try to send our 404 page 117 let response_future =
try_to_send_file("not_found.html").and_then(|response_result|
{ 118 Ok(response_result.unwrap_or_else(|_| { 119 // If the 404 page doesn't exist, sent fallback text
instead 120 const ERROR_MSG: &str = "Failed to find "File not found"
page. How ironic "; 121 Response::new() 122 .with_status(StatusCode::NotFound) 123 .with_header(ContentLength(ERROR_MSG.len() as u64)) 124 .with_body(ERROR_MSG) 125 })) 126 }); 127 Box::new(response_future) 128 }

The following are some helper functions:

130  fn sanitize_path(path: &str) -> String {
131    // Normalize the separators for the next steps
132    path.replace("\", "/")
133      // Prevent the user from going up the filesystem
134      .replace("../", "")
135      // If the path comes straigh from the router,
136      // it will begin with a slash
137      .trim_left_matches(|c| c == '/')
138      // Remove slashes at the end as we only serve files
139      .trim_right_matches(|c| c == '/')
140      .to_string()
141  }
142
143  fn path_on_disk(path_to_file: &str) -> String {
144    "files/".to_string() + path_to_file
145  }
146
147  fn get_content_type(file: &str) -> Option {
148    // Check the file extension and return the respective MIME type
149   let pos = file.rfind('.')? + 1;
150   let mime_type = match &file[pos..] {
151     "txt" => mime::TEXT_PLAIN_UTF_8,
152     "html" => mime::TEXT_HTML_UTF_8,
153     "css" => mime::TEXT_CSS,
154     // This list can be extended for all types your server 
should support 155 _ => return None, 156 }; 157 Some(ContentType(mime_type)) 158 }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.46.92