-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathclient.rs
More file actions
120 lines (104 loc) · 3.44 KB
/
client.rs
File metadata and controls
120 lines (104 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::time::Duration;
use wreq_util::Emulation;
#[derive(Debug, Clone)]
pub struct RequestOptions {
pub url: String,
pub emulation: Emulation,
pub headers: HashMap<String, String>,
pub method: String,
pub body: Option<String>,
pub proxy: Option<String>,
pub timeout: u64,
}
#[derive(Debug, Clone)]
pub struct Response {
pub status: u16,
pub headers: HashMap<String, String>,
pub body: String,
pub cookies: HashMap<String, String>,
pub url: String,
}
pub async fn make_request(options: RequestOptions) -> Result<Response> {
// Create client builder with emulation
let mut client_builder = wreq::Client::builder()
.emulation(options.emulation)
.cookie_store(true);
// Apply proxy if present (must be set at client builder level)
if let Some(proxy_url) = &options.proxy {
let proxy = wreq::Proxy::all(proxy_url)
.context("Failed to create proxy")?;
client_builder = client_builder.proxy(proxy);
}
// Build the client
let client = client_builder
.build()
.context("Failed to build HTTP client")?;
let method = if options.method.is_empty() {
"GET"
} else {
&options.method
};
// Build request
let mut request = match method.to_uppercase().as_str() {
"GET" => client.get(&options.url),
"POST" => client.post(&options.url),
"PUT" => client.put(&options.url),
"DELETE" => client.delete(&options.url),
"PATCH" => client.patch(&options.url),
"HEAD" => client.head(&options.url),
_ => return Err(anyhow::anyhow!("Unsupported HTTP method: {}", method)),
};
// Apply custom headers
for (key, value) in &options.headers {
request = request.header(key, value);
}
// Apply body if present
if let Some(body) = options.body {
request = request.body(body);
}
// Apply timeout
request = request.timeout(Duration::from_millis(options.timeout));
// Execute request
let response = request
.send()
.await
.with_context(|| format!("{} {}", method, options.url))?;
// Extract response data
let status = response.status().as_u16();
let final_url = response.uri().to_string();
// Extract headers
let mut response_headers = HashMap::new();
for (key, value) in response.headers() {
if let Ok(value_str) = value.to_str() {
response_headers.insert(key.to_string(), value_str.to_string());
}
}
// Extract cookies
let mut cookies = HashMap::new();
for cookie_header in response.headers().get_all("set-cookie") {
if let Ok(cookie_str) = cookie_header.to_str() {
// Each Set-Cookie header contains one cookie
// Format: name=value; attribute1; attribute2=value2; ...
// We only care about the first part (name=value)
if let Some(name_value_part) = cookie_str.split(';').next() {
if let Some((key, value)) = name_value_part.trim().split_once('=') {
cookies.insert(key.to_string(), value.to_string());
}
}
}
}
// Get body
let body = response
.text()
.await
.context("Failed to read response body")?;
Ok(Response {
status,
headers: response_headers,
body,
cookies,
url: final_url,
})
}