GET requests
Table of contents
In this part, you will implement the remainder of the basic HTTP server in src/server.rs
by supporting GET requests for files and directories.
Although there are many types of HTTP requests such as GET, POST, and HEAD, you are only expected to handle GET requests. In other words, you can ignore the request type in the parsed request and safely assume that your server will only receive GET requests.
Serving files
Implement server::handle_socket
to handle HTTP GET requests for files. You should be able to handle requests to files in subdirectories of the files directory (e.g. GET /images/hero.jpg
).
- If the file denoted by path exists, serve the file. Read the contents of the file and write it to the client socket.
- Make sure to set the
Content-Type
header to be the MIME type indicated by the file extension. - Also make sure to correctly set the
Content-Length
HTTP header. The value of this header should be the size of the HTTP response body, measured in bytes. For example,Content-Length: 7810
. - No more than 1024 bytes of the file should be loaded into memory at any given time. You will likely want to alternate between reading the file into a buffer and writing the buffer contents to the socket.
- The request path cannot be used directly as the file path. To access local files, prepend a “.” to the request path.
- Make sure to set the
- Otherwise, if the file does not exist, serve a 404 Not Found response (the HTTP headers and body are optional) to the client. There are many things that can go wrong during an HTTP request, but we only expect you to support the 404 Not Found error message for a non-existent file.
- If you encounter any other errors, use
log::warn!
to print out the error and continue serving requests. We will not verify that you are logging errors, but we will be testing that your server does not panic upon an internal error.
You may find the http::parse_request
function helpful here. It takes in a mutable reference to an object with type T
, where T
implements the traits AsyncReadExt
and Unpin
. You do not need to understand all of the syntax here or what the traits do, but implementing these traits essentially means that T
must have a certain set of methods. If you would like to get a deeper understanding of what is going on here, we recommend taking a look at the docs on traits, generics, and bounds.
Since we are using asynchronous functions, make sure to use the tokio::fs::File
API when reading files (do not use std::fs::File
). The documention for tokio::io::AsyncReadExt
and tokio::io::AsyncWriteExt
may be useful as well. When testing the files on your local browser, be sure to “hard refresh” pages as they can often become cached and may misrepresent your requests.
After finishing this part, curling for /index.html
should output the contents of the file index.html
. You should also be passing all of the Basic Server tests on the autograder other than the ones related to directories, as well as the Thread Server test.
Serving directories
Update server::handle_socket
to handle HTTP GET requests for both files and directories.
You will now need to determine if path in handle files request refers to a file or a directory. The
tokio::fs::metadata
function will be useful for this purpose.Do not use errors from
File::open
to determine whether a file is a directory or not. While trying to open a directory withFile::open
will result in an error on Windows, it will not do so on Linux or Mac (and hence you will get different behavior on the autograder).- If the directory contains an
index.html
file, respond with a200 OK
and the full contents of theindex.html
file. You may not assume that directory requests will have a trailing slash in the query string.- The
http::format_index
function may be useful.
- The
- If the directory does not contain an
index.html
file, respond with an HTML page containing links to all of the immediate children of the directory (similar tols -1
), as well as a link to the parent directory.- The
http::format_href
function may be useful. - To list the contents of a directory, a good function to use is
tokio::fs::read_dir
. - Be sure to set the
Content-Type
HTTP header. You may omit theContent-Length
HTTP header, but it is an interesting exercise to try including one that gives the length of bytes you are sending in your response. For retrieving relative paths (such as for the parent directory), it may be helpful to use Rust’s
Path
andPathBuf
API. You may also find the following code snippet useful for converting aPath
orPathBuf
to a string:// Converts `Path` to `&str`. let path = Path::new("./foo"); let path_str = path.as_os_str().into_str().unwrap(); // Converts `PathBuf` to `String`. let path_buf = PathBuf::from("./foo"); let path_buf_str = path_buf.into_os_string().into_string().unwrap();
- The
- If the directory does not exist, serve a 404 Not Found response to the client.
- You don’t need to worry about extra slashes in your links (e.g.
//files///a.jpg
is perfectly fine). Both the file system and your web browser are tolerant of it. - You do not need to handle file system objects other than files and directories (i.e. you do not need to handle symbolic links, pipes, or special files).
- Make helper functions to re-use similar code when you can. It will make your code easier to debug!
Directly using format!
to manipulate URLs can result in some nasty URLs with extra slashes and periods. Though these URLs may be valid, we suggest that you use Path
s and PathBuf
s instead of raw strings for path manipulations.
After finishing this part, curling for the root directory /
should output the contents of the file index.html
. All Basic Server and Thread Server tests should pass on the autograder.