1pub mod url;
2
3use std::collections::HashMap;
4use crate::url::URL;
5
6pub enum Method {
7 GET,
8 POST,
9 PUT,
10 DELETE,
11 PATCH,
12 HEAD,
13 OPTIONS,
14}
15
16fn get_method_string(method: &Method) -> String {
17 match method {
18 Method::GET => "GET".to_string(),
19 Method::POST => "POST".to_string(),
20 Method::PUT => "PUT".to_string(),
21 Method::DELETE => "DELETE".to_string(),
22 Method::PATCH => "PATCH".to_string(),
23 Method::HEAD => "HEAD".to_string(),
24 Method::OPTIONS => "OPTIONS".to_string(),
25 }
26}
27
28fn header_sort(headers: &HashMap<String, String>) -> Vec<String> {
29 let sorted_headers = headers.iter().collect::<Vec<(&String, &String)>>();
30 sorted_headers.iter().map(|(k, v)| format!("{}: {}", k, v)).collect()
31}
32
33pub struct RequestInit {
34 method: Method,
35 headers: HashMap<String, String>,
36 body: Option<String>,
37}
38
39#[cfg(feature = "tokio-fetch")]
40use tokio::{
41 net::TcpStream,
42 io::{AsyncWriteExt, AsyncReadExt},
43};
44
45#[cfg(feature = "tokio-fetch")]
46pub struct Response {
47 stream: TcpStream,
48}
49#[cfg(feature = "tokio-fetch")]
50impl Response {
51 pub async fn text(&mut self) -> Result<String, Box<dyn std::error::Error>> {
52 let mut buffer = vec![0; 1024];
53 let mut text = String::new();
54 loop {
55 let n = self.stream.read(&mut buffer).await?;
56 if n == 0 {
57 break;
58 }
59 text.push_str(&String::from_utf8_lossy(&buffer[..n]));
60 }
61 Ok(text)
62 }
63}
64
65#[cfg(feature = "tokio-fetch")]
66pub async fn fetch(input: URL, init: RequestInit) -> Result<Response, Box<dyn std::error::Error>> {
67 let mut stream = TcpStream::connect(input.get_hostname()).await?;
68 let request = format!(
69 "{} {} HTTP/1.1\r\nHost: {}\r\n{}\r\n\r\n{}",
70 get_method_string(&init.method),
71 input.get_pathname(),
72 input.get_hostname(),
73 header_sort(&init.headers).join("\r\n"),
74 init.body.unwrap_or("".to_string())
75 );
76 stream.write_all(request.as_bytes()).await?;
77 Ok(Response { stream })
78}
79