How it works...

Wow! That was a lot of files. Of course, the exact content of the HTML and CSS doesn't matter for this recipe, as we're going to be focused on Rust. We've put them all in the files folder because we are going to make its contents publicly accessible by name for any client.

The basics of the server setup are the same as with the echoing recipe: create a Service with const_service and service_fn [21], match the request's method and path, and then handle the routes in different functions. When looking at our return type, however, we can notice a difference [36]:

type ResponseFuture = Box<Future<Item = Response, Error = hyper::Error>>;

We are no longer returning a Response directly, but instead, wrapping it in a Future. This allows us to not block the server when loading a file into memory; we can continue handling requests in the main thread while the file serving Future is run in the background.

When looking at our route handlers, you can see that they all use the send_file_or_404 function. Let's take a look at it [56]:

fn send_file_or_404(path: &str) -> ResponseFuture {
let path = sanitize_path(path);

let response_future = try_to_send_file(&path)
.and_then(|response_result| response_result.map_err(|error|
error.into()))
.or_else(|_| send_404());
Box::new(response_future)
}

First, the function sanitizes our input. The implementation of sanitize_path [130 to 141] should be pretty straightforward. It filters out potential troublemakers so that a malicious client cannot do any shenanigans, such as requesting the file localhost:3000/../../../../home/admin/.ssh/id_rsa.

We then call try_to_send_file on the sanitized path [72]. We are going to look at that function in a minute, but for now, it's enough to look at its signature. It tells us that it returns a Future of a Result that can be a Response or an io::Error, as that's the error encountered on invalid filesystem access. We cannot return this Future directly, since we already told hyper that we are going to return a Future of Response, so we need to convert the types. If the file retrieving Future generated from try_to_send_file succeeded, we act on its item, which is a Result<Response, io::Error>.

Because hyper::Error implements From<io::Error>, we can convert them easily by calling .into() [63] (see Chapter 5, Advanced Data Structures; Converting types, for an introduction to the From trait). This will return a Result<Response, hyper::Error>. Because a Future is constructable from a Result, it will be implicitly converted to a Future<Response, hyper::Error> for us, which is exactly what we want. A little cherry on top is our handling of try_to_send_file returning an error, in which case we can safely assume that the file doesn't exist, so we return a Future with a custom 404 Not Found page by calling send_404() [65]. Before looking at its implementation, let's check out try_to_send_file first [72].

First, we convert the requested path into a local filesystem path with path_on_disk [74], which is simply implemented as follows [144]:

"files/".to_string() + path_to_file

We created an own function for this so it will be easy for you to extend the filesystem logic. For example, for Unix systems, it is usual to put all static HTML in /var/www/, while Windows web servers usually put all of their data in their own installation folder. Or you may want to read a configuration file provided by the user and store its value in a lazy_static, as shown in Chapter 5, Advanced Data Structures; Using lazy static variable, and use that path instead. You can implement all of those rules in this function.

Back in try_to_send_file, we create a oneshot::channel to send data as a Future [77]. This concept is explained in detail in Chapter 8, Working with Futures; Using the oneshot channel.

The rest of the function now creates a new thread to load the file into memory in the background [78]. We first open the file [79] and return an error through the channel if it doesn't exist. We then copy the entire file into a local vector of bytes [91] and again propagate any error that might occur [107]. If the process of copying into RAM succeeded, we return a Response with the content of the file as its body [100]. Along the way, we have to figure out the file's appropriate MIME type [96], as promised in the recipe Setting up a basic HTTP server. For that, we simply match the extension of the file [147 to 158]:

fn get_content_type(file: &str) -> Option<ContentType> {
let pos = file.rfind('.')? + 1;
let mime_type = match &file[pos..] {
"txt" => mime::TEXT_PLAIN_UTF_8,
"html" => mime::TEXT_HTML_UTF_8,
"css" => mime::TEXT_CSS,
_ => return None,
};
Some(ContentType(mime_type))
}

You may think this implementation is pretty lazy and that there should be a better way, but, trust me, this is exactly how all big web servers do it. Case in point, you can find nginx (https://nginx.org/en/) mime detection algorithm here: https://github.com/nginx/nginx/blob/master/conf/mime.types. If you plan on serving new file types, you can extend the match for their extensions. The nginx source is a good resource for this.

get_content_type returns None if there was no match [155] instead of a default content type, so that every caller can decide on a default for themselves. In try_to_send_file, we use .unwrap_or_else(ContentType::plaintext); [96] to set the fallback MIME type to text/plain.

The last unexplained function left in our example is send_404, which we use a lot as a fallback. You can see that all it really does is call try_to_send_file on the 404 page [117] and on error send a static message instead [124].

The fallback in send_404 really shows us the beauty in Rust's error handling concept. Because strongly typed errors are part of a function's signature, as opposed to languages such as C++, where you never know who might throw an exception, you are forced to consciously handle the error cases. Try to remove and_then and its associated closure and you'll see that the compiler doesn't let you compile your program because you didn't handle the Result of try_to_send_file in any way.

Go ahead now and see the results of our file server with your own eyes by pointing your browser to http://localhost:3000/.

..................Content has been hidden....................

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