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
7pub trait ValidRequest {
9 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 fn send(&self) -> Result<Response, RequestError>;
21}
22
23trait 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#[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 pub fn async_send(&self) -> impl Future<Output = Result<Response, RequestError>> + '_ {
57 async move {
58 self.send()
59 }
60 }
61}
62
63impl 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 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 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}