cs431_homework/hello_server/
handler.rs

1//! Request handler with a cache.
2
3use std::io::prelude::*;
4use std::net::TcpStream;
5use std::sync::{Arc, OnceLock};
6use std::thread;
7use std::time::Duration;
8
9use regex::bytes::Regex;
10
11use super::cache::Cache;
12use super::statistics::Report;
13
14/// Computes the result for the given key. So expensive, much wow.
15fn very_expensive_computation_that_takes_a_few_seconds(key: String) -> String {
16    println!("[handler] doing computation for key: {key}");
17    thread::sleep(Duration::from_secs(3));
18    format!("{key}🐕")
19}
20
21/// Hello handler with a cache.
22#[derive(Debug, Default, Clone)]
23pub struct Handler {
24    cache: Arc<Cache<String, String>>,
25}
26
27impl Handler {
28    const OK: &'static str = "<!DOCTYPE html>
29<html lang=\"en\">
30  <head>
31    <meta charset=\"utf-8\">
32    <title>Hello!</title>
33  </head>
34  <body>
35    <p>Result for key \"{key}\" is \"{result}\"</p>
36  </body>
37</html>";
38
39    const NOT_FOUND: &'static str = "<!DOCTYPE html>
40<html lang=\"en\">
41  <head>
42    <meta charset=\"utf-8\">
43    <title>Hello!</title>
44  </head>
45  <body>
46    <h1>Oops!</h1>
47    <p>Sorry, I don't know what you're asking for.</p>
48  </body>
49</html>";
50
51    /// Process the request and generate report.
52    pub fn handle_conn(&self, request_id: usize, mut stream: TcpStream) -> Report {
53        let mut buf = [0; 512];
54        let _ = stream.read(&mut buf).unwrap();
55
56        static REQUEST_REGEX: OnceLock<Regex> = OnceLock::<Regex>::new();
57
58        let key = REQUEST_REGEX
59            .get_or_init(|| Regex::new(r"GET /(?P<key>\w+) HTTP/1.1\r\n").unwrap())
60            .captures(&buf)
61            .and_then(|cap| cap.name("key"))
62            .map(|key| String::from_utf8_lossy(key.as_bytes()));
63        // TODO: Might be better to just change the strings to not have "{" and "}" in them.
64        #[allow(clippy::literal_string_with_formatting_args)]
65        let resp = if let Some(ref key) = key {
66            let result = self.cache.get_or_insert_with(
67                key.to_string(),
68                very_expensive_computation_that_takes_a_few_seconds,
69            );
70            format!(
71                "HTTP/1.1 200 OK\r\n\r\n{}",
72                Self::OK.replace("{key}", key).replace("{result}", &result)
73            )
74        } else {
75            format!("HTTP/1.1 404 NOT FOUND\r\n\r\n{}", Self::NOT_FOUND)
76        };
77
78        stream.write_all(resp.as_bytes()).unwrap();
79
80        Report::new(request_id, key.map(String::from))
81    }
82}