quickhttp/
request.rs

1use std::collections::HashMap;
2use std::future::Future;
3use std::{io::{Read, Write}, net::TcpStream};
4use crate::StatusCode;
5use crate::{errors::{Error, RequestError}, response::Response};
6
7/// Describes a valid request
8pub trait ValidRequest {
9    /// Create a new request
10    fn new(
11        method: String,
12        path: String,
13        headers: HashMap<String, String>,
14        body: String,
15        host: String,
16        port: u16,
17        http_version: String,
18    ) -> Self;
19    /// Send the request synchronously
20    fn send(&self) -> Result<Response, RequestError>;
21}
22
23/// Trait to convert headers to a string
24trait HeadersToString {
25    fn headers_to_string(&self) -> String;
26}
27
28impl HeadersToString for HashMap<String, String> {
29    fn headers_to_string(&self) -> String {
30        let mut headers = String::new();
31        for (key, value) in self.iter() {
32            headers.push_str(&format!("{}: {}\r\n", key, value));
33        }
34        headers
35    }
36}
37
38/// The request type
39#[derive(Clone, Debug)]
40pub struct Request {
41    pub method: String,
42    pub path: String,
43    pub headers: HashMap<String, String>,
44    pub body: String,
45    pub host: String,
46    pub port: u16,
47    pub http_version: String,
48}
49
50impl Request {
51    fn fail(&self, message: &str) -> RequestError {
52        RequestError::new(format!("RequestError: {}", message))
53    }
54
55    /// Send the request asynchronously, returning a future
56    pub fn async_send(&self) -> impl Future<Output = Result<Response, RequestError>> + '_ {
57        async move {
58            self.send()
59        }
60    }
61}
62
63/// Implement the ValidRequest trait for the Request type
64impl ValidRequest for Request {
65    fn new(
66        method: String,
67        path: String,
68        headers: HashMap<String, String>,
69        body: String,
70        host: String,
71        port: u16,
72        http_version: String,
73    ) -> Request {
74        Request {
75            method,
76            path,
77            headers,
78            body,
79            host,
80            port,
81            http_version,
82        }
83    }
84
85    fn send(&self) -> Result<Response, RequestError> {
86        // no propagation of errors, just return a new error using self.fail and the match statement
87
88        let stream = TcpStream::connect(format!("{}:{}", self.host, self.port));
89        let mut stream = match stream {
90            Ok(stream) => stream,
91            Err(_) => return Err(self.fail("could not connect to server")),
92        };
93
94        let mut headers = self.headers.clone();
95
96        if !headers.contains_key("Content-Length") {
97            let content_length = self.body.len();
98            headers.insert("Content-Length".to_string(), content_length.to_string());
99        }
100
101        let request = format!(
102            "{} {} HTTP/{}\r\nHost: {}\r\n{}\r\n{}",
103            self.method, self.path, self.http_version, self.host, headers.headers_to_string(), self.body
104        );
105
106        let _ = match stream.write(request.as_bytes()) {
107            Ok(_) => (),
108            Err(_) => return Err(self.fail("could not write request")),
109        };
110
111        let mut response = Vec::new();
112
113        let _ = match stream.read_to_end(&mut response) {
114            Ok(_) => (),
115            Err(_) => return Err(self.fail("could not read response")),
116        };
117
118        // parse the response
119        let response = String::from_utf8(response).unwrap();
120
121        let mut parts = response.split("\r\n\r\n");
122        let headers = parts.next().unwrap();
123        let body = parts.next().unwrap();
124
125        let mut headers = headers.split("\r\n");
126        let status_line = headers.next().unwrap();
127        let status_code = status_line.split(" ").nth(1).unwrap().parse::<u16>().unwrap();
128
129        let headers = headers.map(|header| {
130            let mut parts = header.split(": ");
131            let key = parts.next().unwrap();
132            let value = parts.next().unwrap();
133            (key.to_string(), value.to_string())
134        });
135
136        let mut headers_map = HashMap::new();
137
138        for (key, value) in headers {
139            headers_map.insert(key, value);
140        }
141
142        Ok(Response {
143            raw_response: response.clone(),
144            status_code: StatusCode::from_u16(status_code).unwrap(),
145            headers: headers_map,
146            body: body.to_string(),
147            request_used: self.clone(),
148        })
149    }
150}