This is the walkthrough of this Udemy course + a couple of post-course exercises (refer to Devoir section).
To run this server:
-
Make sure that you have Rust installed (using
rustupis probably best) -
Run this command from the root of the project
PUBLIC_PATH=$(pwd)/public cargo run
-
Open up your favorite browser and hit enter for this address
http://127.0.0.1:8080/
- Fix a bug where local css file's contents are read into the response header
- Look at parts of the code to make them more FP palletable
- Add HttpHeaders to both
HttpRequestandHttpResponse - Use Tokio library + native
async/ to convert the single core server into an async multithreaded server
The HeaderKey derive macro is applied to enums such as HttpRequestHeaderKey and HttpResponseHeaderKey.
Example:
#[derive(Debug, Clone, PartialEq, Eq, Hash, HeaderKey)]
pub enum HttpRequestHeaderKey {
Accept,
AcceptEncoding,
AcceptLanguage,
Authorization,
Host,
CacheControl,
ContentType,
ContentLength,
Cookie,
Origin,
Referer,
UserAgent,
Custom(String),
}What this macro generates:
- An implementation of a
HeaderKeytrait with:
fn as_ref(&self) -> &strthat returns the canonical string form of each variant:
HttpRequestHeaderKey::AcceptEncoding.as_ref() → "Accept-Encoding"HttpRequestHeaderKey::ContentType.as_ref() → "Content-Type"HttpRequestHeaderKey::Custom("X-Foo".into()).as_ref() → "X-Foo"- A
Displayimpl, soto_string()just delegates toas_ref() - A
FromStrimpl, so"Host".parse()producesHttpRequestHeaderKey::Host, and unknown keys becomeCustom(String)
This means you can use the enum anywhere a header key string is needed, without worrying about normalization.
To avoid boilerplate when constructing requests/responses, we use declarative macros like add_request_builder_headers! and add_response_builder_headers!.
Example (add_response_builder_headers!):
macro_rules! add_response_builder_headers {
($($variant:ident),* $(,)?) => {
$(
paste! {
pub fn [<$variant:snake>](&mut self, value: &str) -> &mut Self {
self.headers.insert(
HttpResponseHeaderKey::$variant.as_ref().to_string(),
value.to_string(),
);
self
}
}
)*
pub fn custom(&mut self, key: String, value: &str) -> &mut Self {
self.headers.insert(
HttpResponseHeaderKey::Custom(key).as_ref().to_string(),
value.to_string(),
);
self
}
};
}This macro:
Uses paste to generate snake-case builder methods from enum variants:
AcceptEncoding→.accept_encoding("gzip, deflate")ContentType→.content_type("application/json")
Inserts the header into the underlying HashMap<String, String> by calling the derived .as_ref().
So instead of writing:
builder.headers.insert(
HttpResponseHeaderKey::ContentType.as_ref().to_string(),
"application/json".to_string(),
);you can just write:
builder.content_type("application/json");To see some println or eprintln outputs in your test simply append
--show-output to your command, e.g.:
cargo test --test parallel_requests -- --show-output