tangled
alpha
login
or
join now
tsiry-sandratraina.com
/
replay
4
fork
atom
Sniff and replay HTTP requests and responses — perfect for mocking APIs during testing.
4
fork
atom
overview
issues
pulls
pipelines
run cargo fmt
tsiry-sandratraina.com
7 months ago
c5fff251
f965ea97
1/1
fmt.yml
success
10s
+350
-326
3 changed files
expand all
collapse all
unified
split
src
main.rs
proxy.rs
replay.rs
+9
-8
src/main.rs
···
1
1
-
2
2
-
use std::sync::{Arc};
1
1
+
use std::sync::Arc;
3
2
4
3
use clap::{Arg, Command};
5
4
use owo_colors::OwoColorize;
···
31
30
Arg::new("target")
32
31
.short('t')
33
32
.long("target")
34
34
-
.help("The target URL to replay the requests to")
33
33
+
.help("The target URL to replay the requests to"),
35
34
)
36
35
.arg(
37
36
Arg::new("listen")
38
37
.short('l')
39
38
.long("listen")
40
39
.help("The address to listen on for incoming requests")
41
41
-
.default_value("127.0.0.1:6677")
40
40
+
.default_value("127.0.0.1:6677"),
42
41
)
43
42
.subcommand(
44
43
Command::new("mock")
45
45
-
.about("Read mocks from replay_mock.json and start a replay server")
44
44
+
.about("Read mocks from replay_mock.json and start a replay server"),
46
45
)
47
46
.get_matches();
48
48
-
49
47
50
48
if let Some(_) = matches.subcommand_matches("mock") {
51
49
let logs = store::load_logs_from_file(proxy::PROXY_LOG_FILE)?;
52
50
let logs = Arc::new(Mutex::new(logs));
53
51
let listen = matches.get_one::<String>("listen").unwrap();
54
54
-
println!("Loaded {} mocks from {}", logs.lock().await.len().magenta(), proxy::PROXY_LOG_FILE.magenta());
52
52
+
println!(
53
53
+
"Loaded {} mocks from {}",
54
54
+
logs.lock().await.len().magenta(),
55
55
+
proxy::PROXY_LOG_FILE.magenta()
56
56
+
);
55
57
println!("Replay server is running on {}", listen.magenta());
56
58
replay::start_replay_server(logs, listen).await?;
57
59
return Ok(());
58
60
}
59
59
-
60
61
61
62
let target = matches.get_one::<String>("target");
62
63
+243
-232
src/proxy.rs
···
1
1
-
use std::{process::exit, sync::Arc, thread, time::{SystemTime, UNIX_EPOCH}};
1
1
+
use std::{
2
2
+
process::exit,
3
3
+
sync::Arc,
4
4
+
thread,
5
5
+
time::{SystemTime, UNIX_EPOCH},
6
6
+
};
2
7
3
8
use http_body_util::{BodyExt, Full};
4
4
-
use hyper::{body::{Buf, Bytes, Incoming}, server::conn::http1, Request, Response};
9
9
+
use hyper::{
10
10
+
Request, Response,
11
11
+
body::{Buf, Bytes, Incoming},
12
12
+
server::conn::http1,
13
13
+
};
14
14
+
use hyper_util::rt::TokioIo;
5
15
use owo_colors::OwoColorize;
6
16
use serde::{Deserialize, Serialize};
7
17
use tokio::{net::TcpListener, sync::Mutex};
8
8
-
use hyper_util::rt::TokioIo;
9
18
10
10
-
use crate::{replay::start_replay_server, store::{save_logs_to_file, LogStore}};
19
19
+
use crate::{
20
20
+
replay::start_replay_server,
21
21
+
store::{LogStore, save_logs_to_file},
22
22
+
};
11
23
12
24
pub const PROXY_LOG_FILE: &str = "replay_mocks.json";
13
25
···
30
42
31
43
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
32
44
pub struct ProxyLog {
33
33
-
pub request: RequestLog,
34
34
-
pub response: ResponseLog,
45
45
+
pub request: RequestLog,
46
46
+
pub response: ResponseLog,
35
47
}
36
48
37
37
-
pub async fn start_server(target: &str, listen: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
38
38
-
let target_uri = target.parse::<hyper::Uri>()?;
39
39
-
let target_authority = target_uri.authority().ok_or("Invalid target URL")?;
40
40
-
let target_scheme = target_uri.scheme_str().ok_or("http")?;
41
41
-
let target_host = target_authority.host();
42
42
-
let target_port = target_authority.port_u16().unwrap_or(if target_scheme == "https" { 443 } else { 80 });
49
49
+
pub async fn start_server(
50
50
+
target: &str,
51
51
+
listen: &str,
52
52
+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
53
53
+
let target_uri = target.parse::<hyper::Uri>()?;
54
54
+
let target_authority = target_uri.authority().ok_or("Invalid target URL")?;
55
55
+
let target_scheme = target_uri.scheme_str().ok_or("http")?;
56
56
+
let target_host = target_authority.host();
57
57
+
let target_port = target_authority
58
58
+
.port_u16()
59
59
+
.unwrap_or(if target_scheme == "https" { 443 } else { 80 });
43
60
44
44
-
let logs = Arc::new(Mutex::new(Vec::<ProxyLog>::new()));
45
45
-
let logs_for_saving = logs.clone();
46
46
-
tokio::spawn(async move {
47
47
-
loop {
48
48
-
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
49
49
-
save_logs_to_file(&logs_for_saving, PROXY_LOG_FILE).await
61
61
+
let logs = Arc::new(Mutex::new(Vec::<ProxyLog>::new()));
62
62
+
let logs_for_saving = logs.clone();
63
63
+
tokio::spawn(async move {
64
64
+
loop {
65
65
+
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
66
66
+
save_logs_to_file(&logs_for_saving, PROXY_LOG_FILE)
67
67
+
.await
50
68
.unwrap_or_else(|e| eprintln!("Error saving logs to file: {}", e));
51
51
-
}
52
52
-
});
69
69
+
}
70
70
+
});
53
71
54
54
-
let logs_for_replay = logs.clone();
55
55
-
thread::spawn(move || {
56
56
-
let rt = tokio::runtime::Builder::new_multi_thread()
57
57
-
.enable_all()
58
58
-
.build()
59
59
-
.unwrap();
60
60
-
rt.block_on(async {
61
61
-
match start_replay_server(logs_for_replay, "127.0.0.1:6688").await {
62
62
-
Ok(_) => {
63
63
-
println!("Replay server stopped");
64
64
-
exit(0);
65
65
-
},
66
66
-
Err(e) => eprintln!("Replay server error: {}", e),
67
67
-
}
72
72
+
let logs_for_replay = logs.clone();
73
73
+
thread::spawn(move || {
74
74
+
let rt = tokio::runtime::Builder::new_multi_thread()
75
75
+
.enable_all()
76
76
+
.build()
77
77
+
.unwrap();
78
78
+
rt.block_on(async {
79
79
+
match start_replay_server(logs_for_replay, "127.0.0.1:6688").await {
80
80
+
Ok(_) => {
81
81
+
println!("Replay server stopped");
82
82
+
exit(0);
83
83
+
}
84
84
+
Err(e) => eprintln!("Replay server error: {}", e),
85
85
+
}
86
86
+
});
68
87
});
69
69
-
});
70
88
71
71
-
let listener = TcpListener::bind(listen).await?;
72
72
-
println!("Target URL: {}", target.magenta());
73
73
-
println!("Proxy server is listening on {}", listen.green());
74
74
-
println!("Replay server is running on {}", "127.0.0.1:6688".green());
89
89
+
let listener = TcpListener::bind(listen).await?;
90
90
+
println!("Target URL: {}", target.magenta());
91
91
+
println!("Proxy server is listening on {}", listen.green());
92
92
+
println!("Replay server is running on {}", "127.0.0.1:6688".green());
75
93
76
76
-
loop {
77
77
-
let (stream, _) = listener.accept().await?;
78
78
-
let io = TokioIo::new(stream);
94
94
+
loop {
95
95
+
let (stream, _) = listener.accept().await?;
96
96
+
let io = TokioIo::new(stream);
79
97
80
80
-
let target_host_str = target_host.to_string();
81
81
-
let target_scheme = target_scheme.to_string();
82
82
-
let logs_clone = logs.clone();
98
98
+
let target_host_str = target_host.to_string();
99
99
+
let target_scheme = target_scheme.to_string();
100
100
+
let logs_clone = logs.clone();
83
101
84
84
-
tokio::task::spawn(async move {
85
85
-
let service = hyper::service::service_fn(move |req: Request<Incoming>| {
86
86
-
let target_host = target_host_str.clone();
87
87
-
let scheme = target_scheme.clone();
88
88
-
let logs = logs_clone.clone();
102
102
+
tokio::task::spawn(async move {
103
103
+
let service = hyper::service::service_fn(move |req: Request<Incoming>| {
104
104
+
let target_host = target_host_str.clone();
105
105
+
let scheme = target_scheme.clone();
106
106
+
let logs = logs_clone.clone();
89
107
90
90
-
async move {
91
91
-
proxy_handler(req, &target_host, target_port, &scheme, logs).await
92
92
-
}
93
93
-
});
108
108
+
async move { proxy_handler(req, &target_host, target_port, &scheme, logs).await }
109
109
+
});
94
110
95
95
-
if let Err(err) = http1::Builder::new()
111
111
+
if let Err(err) = http1::Builder::new()
96
112
.keep_alive(false)
97
113
.max_buf_size(30 * 1024 * 1024)
98
98
-
.serve_connection(io, service)
99
99
-
.await
100
100
-
{
101
101
-
eprintln!("> Connection error: {}", err);
102
102
-
}
103
103
-
});
104
104
-
}
105
105
-
114
114
+
.serve_connection(io, service)
115
115
+
.await
116
116
+
{
117
117
+
eprintln!("> Connection error: {}", err);
118
118
+
}
119
119
+
});
120
120
+
}
106
121
}
107
122
108
123
pub async fn proxy_handler(
109
109
-
req: Request<Incoming>,
110
110
-
target_host: &str,
111
111
-
target_port: u16,
112
112
-
scheme: &str,
113
113
-
logs: LogStore,
124
124
+
req: Request<Incoming>,
125
125
+
target_host: &str,
126
126
+
target_port: u16,
127
127
+
scheme: &str,
128
128
+
logs: LogStore,
114
129
) -> Result<Response<Full<Bytes>>, hyper::Error> {
115
115
-
let timestamp = SystemTime::now()
116
116
-
.duration_since(UNIX_EPOCH)
117
117
-
.unwrap()
118
118
-
.as_secs();
130
130
+
let timestamp = SystemTime::now()
131
131
+
.duration_since(UNIX_EPOCH)
132
132
+
.unwrap()
133
133
+
.as_secs();
119
134
120
120
-
let method = req.method().clone();
121
121
-
let path = req.uri().path().to_string();
122
122
-
let query = req.uri().query().map(|q| q.to_string());
135
135
+
let method = req.method().clone();
136
136
+
let path = req.uri().path().to_string();
137
137
+
let query = req.uri().query().map(|q| q.to_string());
123
138
124
124
-
let headers: Vec<(String, String)> = req
125
125
-
.headers()
126
126
-
.iter()
127
127
-
.map(|(name, value)| {
128
128
-
(
129
129
-
name.to_string(),
130
130
-
value.to_str().unwrap_or("").to_string(),
131
131
-
)
132
132
-
})
133
133
-
.collect();
139
139
+
let headers: Vec<(String, String)> = req
140
140
+
.headers()
141
141
+
.iter()
142
142
+
.map(|(name, value)| (name.to_string(), value.to_str().unwrap_or("").to_string()))
143
143
+
.collect();
134
144
135
135
-
let (parts, body) = req.into_parts();
136
136
-
let body_bytes = match body.collect().await {
137
137
-
Ok(collected) => collected.aggregate(),
138
138
-
Err(e) => {
139
139
-
eprintln!("Error collecting request body: {}", e);
140
140
-
return Ok(Response::builder()
141
141
-
.status(500)
142
142
-
.body(Full::new(Bytes::from("Internal Server Error")))
143
143
-
.unwrap());
144
144
-
}
145
145
-
};
145
145
+
let (parts, body) = req.into_parts();
146
146
+
let body_bytes = match body.collect().await {
147
147
+
Ok(collected) => collected.aggregate(),
148
148
+
Err(e) => {
149
149
+
eprintln!("Error collecting request body: {}", e);
150
150
+
return Ok(Response::builder()
151
151
+
.status(500)
152
152
+
.body(Full::new(Bytes::from("Internal Server Error")))
153
153
+
.unwrap());
154
154
+
}
155
155
+
};
146
156
147
147
-
let body_vec = body_bytes.chunk().to_vec();
148
148
-
let body_str = String::from_utf8(body_vec.clone()).ok();
157
157
+
let body_vec = body_bytes.chunk().to_vec();
158
158
+
let body_str = String::from_utf8(body_vec.clone()).ok();
149
159
150
150
-
let forward_uri = if target_port != 443 && target_port != 80 {
151
151
-
format!(
152
152
-
"{}://{}:{}{}{}",
153
153
-
scheme,
154
154
-
target_host,
155
155
-
target_port,
156
156
-
parts.uri.path(),
157
157
-
parts.uri.query().map_or(String::new(), |q| format!("?{}", q))
158
158
-
)
159
159
-
} else {
160
160
-
format!(
161
161
-
"{}://{}{}{}",
162
162
-
scheme,
163
163
-
target_host,
164
164
-
parts.uri.path(),
165
165
-
parts.uri.query().map_or(String::new(), |q| format!("?{}", q))
166
166
-
)
167
167
-
};
160
160
+
let forward_uri = if target_port != 443 && target_port != 80 {
161
161
+
format!(
162
162
+
"{}://{}:{}{}{}",
163
163
+
scheme,
164
164
+
target_host,
165
165
+
target_port,
166
166
+
parts.uri.path(),
167
167
+
parts
168
168
+
.uri
169
169
+
.query()
170
170
+
.map_or(String::new(), |q| format!("?{}", q))
171
171
+
)
172
172
+
} else {
173
173
+
format!(
174
174
+
"{}://{}{}{}",
175
175
+
scheme,
176
176
+
target_host,
177
177
+
parts.uri.path(),
178
178
+
parts
179
179
+
.uri
180
180
+
.query()
181
181
+
.map_or(String::new(), |q| format!("?{}", q))
182
182
+
)
183
183
+
};
168
184
169
169
-
println!("{} {} {}", method.yellow(), path, forward_uri.magenta());
185
185
+
println!("{} {} {}", method.yellow(), path, forward_uri.magenta());
170
186
171
171
-
let client = reqwest::Client::builder()
172
172
-
.timeout(std::time::Duration::from_secs(30))
173
173
-
.danger_accept_invalid_certs(true)
174
174
-
.build()
175
175
-
.unwrap_or_else(|_| reqwest::Client::new());
187
187
+
let client = reqwest::Client::builder()
188
188
+
.timeout(std::time::Duration::from_secs(30))
189
189
+
.danger_accept_invalid_certs(true)
190
190
+
.build()
191
191
+
.unwrap_or_else(|_| reqwest::Client::new());
176
192
177
177
-
let mut req_builder = match method.as_str() {
178
178
-
"GET" => client.get(&forward_uri),
179
179
-
"POST" => client.post(&forward_uri),
180
180
-
"PUT" => client.put(&forward_uri),
181
181
-
"DELETE" => client.delete(&forward_uri),
182
182
-
"HEAD" => client.head(&forward_uri),
183
183
-
"OPTIONS" => client.request(reqwest::Method::OPTIONS, &forward_uri),
184
184
-
"PATCH" => client.patch(&forward_uri),
185
185
-
_ => {
186
186
-
eprintln!("Unsupported method: {}", method);
187
187
-
return Ok(Response::builder()
188
188
-
.status(400)
189
189
-
.body(Full::new(Bytes::from("Bad Request: Unsupported Method")))
190
190
-
.unwrap());
191
191
-
}
192
192
-
};
193
193
+
let mut req_builder = match method.as_str() {
194
194
+
"GET" => client.get(&forward_uri),
195
195
+
"POST" => client.post(&forward_uri),
196
196
+
"PUT" => client.put(&forward_uri),
197
197
+
"DELETE" => client.delete(&forward_uri),
198
198
+
"HEAD" => client.head(&forward_uri),
199
199
+
"OPTIONS" => client.request(reqwest::Method::OPTIONS, &forward_uri),
200
200
+
"PATCH" => client.patch(&forward_uri),
201
201
+
_ => {
202
202
+
eprintln!("Unsupported method: {}", method);
203
203
+
return Ok(Response::builder()
204
204
+
.status(400)
205
205
+
.body(Full::new(Bytes::from("Bad Request: Unsupported Method")))
206
206
+
.unwrap());
207
207
+
}
208
208
+
};
193
209
194
194
-
for (name, value) in &headers {
195
195
-
if name.to_lowercase() != "host" &&
196
196
-
name.to_lowercase() != "connection" {
197
197
-
if let Ok(header_name) = reqwest::header::HeaderName::from_bytes(name.as_bytes()) {
198
198
-
if let Ok(header_value) = reqwest::header::HeaderValue::from_str(value) {
199
199
-
req_builder = req_builder.header(header_name, header_value);
200
200
-
}
201
201
-
}
202
202
-
}
203
203
-
}
210
210
+
for (name, value) in &headers {
211
211
+
if name.to_lowercase() != "host" && name.to_lowercase() != "connection" {
212
212
+
if let Ok(header_name) = reqwest::header::HeaderName::from_bytes(name.as_bytes()) {
213
213
+
if let Ok(header_value) = reqwest::header::HeaderValue::from_str(value) {
214
214
+
req_builder = req_builder.header(header_name, header_value);
215
215
+
}
216
216
+
}
217
217
+
}
218
218
+
}
204
219
205
205
-
if !body_vec.is_empty() {
206
206
-
req_builder = req_builder.body(body_vec.clone());
207
207
-
}
220
220
+
if !body_vec.is_empty() {
221
221
+
req_builder = req_builder.body(body_vec.clone());
222
222
+
}
208
223
209
209
-
let resp = match req_builder.send().await {
210
210
-
Ok(resp) => resp,
211
211
-
Err(e) => {
212
212
-
eprintln!("Error sending request: {}", e);
213
213
-
return Ok(Response::builder()
214
214
-
.status(502)
215
215
-
.body(Full::new(Bytes::from(format!("Bad Gateway: {}", e))))
216
216
-
.unwrap());
217
217
-
}
218
218
-
};
224
224
+
let resp = match req_builder.send().await {
225
225
+
Ok(resp) => resp,
226
226
+
Err(e) => {
227
227
+
eprintln!("Error sending request: {}", e);
228
228
+
return Ok(Response::builder()
229
229
+
.status(502)
230
230
+
.body(Full::new(Bytes::from(format!("Bad Gateway: {}", e))))
231
231
+
.unwrap());
232
232
+
}
233
233
+
};
219
234
220
220
-
let status = resp.status().as_u16();
235
235
+
let status = resp.status().as_u16();
221
236
222
222
-
let resp_headers: Vec<(String, String)> = resp
223
223
-
.headers()
224
224
-
.iter()
225
225
-
.map(|(name, value)| {
226
226
-
(
227
227
-
name.to_string(),
228
228
-
value.to_str().unwrap_or("").to_string(),
229
229
-
)
230
230
-
})
231
231
-
.collect();
237
237
+
let resp_headers: Vec<(String, String)> = resp
238
238
+
.headers()
239
239
+
.iter()
240
240
+
.map(|(name, value)| (name.to_string(), value.to_str().unwrap_or("").to_string()))
241
241
+
.collect();
232
242
233
233
-
let resp_bytes = match resp.bytes().await {
234
234
-
Ok(bytes) => bytes,
235
235
-
Err(e) => {
236
236
-
eprintln!("Error reading response body: {}", e);
237
237
-
return Ok(Response::builder()
238
238
-
.status(500)
239
239
-
.body(Full::new(Bytes::from("Internal Server Error")))
240
240
-
.unwrap());
241
241
-
}
242
242
-
};
243
243
+
let resp_bytes = match resp.bytes().await {
244
244
+
Ok(bytes) => bytes,
245
245
+
Err(e) => {
246
246
+
eprintln!("Error reading response body: {}", e);
247
247
+
return Ok(Response::builder()
248
248
+
.status(500)
249
249
+
.body(Full::new(Bytes::from("Internal Server Error")))
250
250
+
.unwrap());
251
251
+
}
252
252
+
};
243
253
244
244
-
let resp_vec = resp_bytes.to_vec();
245
245
-
let resp_str = String::from_utf8(resp_vec.clone()).ok();
254
254
+
let resp_vec = resp_bytes.to_vec();
255
255
+
let resp_str = String::from_utf8(resp_vec.clone()).ok();
246
256
247
247
-
let log_entry = ProxyLog {
248
248
-
request: RequestLog {
249
249
-
timestamp,
250
250
-
method: method.to_string(),
251
251
-
path,
252
252
-
query_params: query,
253
253
-
headers,
254
254
-
body: body_str,
255
255
-
},
256
256
-
response: ResponseLog {
257
257
-
status,
258
258
-
headers: resp_headers.clone(),
259
259
-
body: resp_str.clone(),
260
260
-
},
261
261
-
};
257
257
+
let log_entry = ProxyLog {
258
258
+
request: RequestLog {
259
259
+
timestamp,
260
260
+
method: method.to_string(),
261
261
+
path,
262
262
+
query_params: query,
263
263
+
headers,
264
264
+
body: body_str,
265
265
+
},
266
266
+
response: ResponseLog {
267
267
+
status,
268
268
+
headers: resp_headers.clone(),
269
269
+
body: resp_str.clone(),
270
270
+
},
271
271
+
};
262
272
263
263
-
{
264
264
-
let mut logs_guard = logs.lock().await;
265
265
-
if !logs_guard.iter()
266
266
-
.any(|log|
267
267
-
log.request.method == log_entry.request.method &&
268
268
-
log.request.path == log_entry.request.path &&
269
269
-
log.request.query_params == log_entry.request.query_params
270
270
-
) {
271
271
-
logs_guard.push(log_entry.clone());
273
273
+
{
274
274
+
let mut logs_guard = logs.lock().await;
275
275
+
if !logs_guard.iter().any(|log| {
276
276
+
log.request.method == log_entry.request.method
277
277
+
&& log.request.path == log_entry.request.path
278
278
+
&& log.request.query_params == log_entry.request.query_params
279
279
+
}) {
280
280
+
logs_guard.push(log_entry.clone());
281
281
+
}
272
282
}
273
273
-
}
274
283
275
275
-
println!("Saved request/response to log store {}", PROXY_LOG_FILE.magenta());
284
284
+
println!(
285
285
+
"Saved request/response to log store {}",
286
286
+
PROXY_LOG_FILE.magenta()
287
287
+
);
276
288
277
277
-
let mut builder = Response::builder().status(status);
289
289
+
let mut builder = Response::builder().status(status);
278
290
279
279
-
for (name, value) in resp_headers {
280
280
-
if name.to_lowercase() != "connection" &&
281
281
-
name.to_lowercase() != "transfer-encoding" {
282
282
-
if let Ok(header_name) = hyper::header::HeaderName::from_bytes(name.as_bytes()) {
283
283
-
if let Ok(header_value) = hyper::header::HeaderValue::from_str(&value) {
284
284
-
builder = builder.header(header_name, header_value);
285
285
-
}
286
286
-
}
287
287
-
}
288
288
-
}
291
291
+
for (name, value) in resp_headers {
292
292
+
if name.to_lowercase() != "connection" && name.to_lowercase() != "transfer-encoding" {
293
293
+
if let Ok(header_name) = hyper::header::HeaderName::from_bytes(name.as_bytes()) {
294
294
+
if let Ok(header_value) = hyper::header::HeaderValue::from_str(&value) {
295
295
+
builder = builder.header(header_name, header_value);
296
296
+
}
297
297
+
}
298
298
+
}
299
299
+
}
289
300
290
290
-
builder = builder.header("content-length", resp_vec.len());
291
291
-
builder = builder.header("connection", "close");
301
301
+
builder = builder.header("content-length", resp_vec.len());
302
302
+
builder = builder.header("connection", "close");
292
303
293
293
-
Ok(builder
294
294
-
.body(Full::new(Bytes::from(resp_vec)))
295
295
-
.unwrap_or_else(|_| {
296
296
-
Response::builder()
297
297
-
.status(500)
298
298
-
.body(Full::new(Bytes::from("Internal Server Error")))
299
299
-
.unwrap()
300
300
-
}))
301
301
-
}
304
304
+
Ok(builder
305
305
+
.body(Full::new(Bytes::from(resp_vec)))
306
306
+
.unwrap_or_else(|_| {
307
307
+
Response::builder()
308
308
+
.status(500)
309
309
+
.body(Full::new(Bytes::from("Internal Server Error")))
310
310
+
.unwrap()
311
311
+
}))
312
312
+
}
+98
-86
src/replay.rs
···
1
1
use crate::proxy::PROXY_LOG_FILE;
2
2
-
use crate::store::{load_logs_from_file, LogStore};
3
3
-
use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse, Responder};
2
2
+
use crate::store::{LogStore, load_logs_from_file};
4
3
use actix_web::http::header::{HeaderName, HeaderValue};
4
4
+
use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, web};
5
5
use owo_colors::OwoColorize;
6
6
7
7
-
pub async fn start_replay_server(logs: LogStore, bind_address: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
8
8
-
if let Ok(file_logs) = load_logs_from_file(PROXY_LOG_FILE) {
9
9
-
let mut logs_guard = logs.lock().await;
10
10
-
for log in file_logs {
11
11
-
logs_guard.push(log);
12
12
-
}
13
13
-
}
7
7
+
pub async fn start_replay_server(
8
8
+
logs: LogStore,
9
9
+
bind_address: &str,
10
10
+
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
11
11
+
if let Ok(file_logs) = load_logs_from_file(PROXY_LOG_FILE) {
12
12
+
let mut logs_guard = logs.lock().await;
13
13
+
for log in file_logs {
14
14
+
logs_guard.push(log);
15
15
+
}
16
16
+
}
14
17
15
15
-
fn build_request_key(method: &str, path: &str, query: &Option<String>) -> String {
16
16
-
if let Some(q) = query {
17
17
-
format!("{} {}?{}", method, path, q)
18
18
-
} else {
19
19
-
format!("{} {}", method, path)
20
20
-
}
21
21
-
}
18
18
+
fn build_request_key(method: &str, path: &str, query: &Option<String>) -> String {
19
19
+
if let Some(q) = query {
20
20
+
format!("{} {}?{}", method, path, q)
21
21
+
} else {
22
22
+
format!("{} {}", method, path)
23
23
+
}
24
24
+
}
22
25
23
23
-
async fn replay_handler(
24
24
-
req: HttpRequest,
25
25
-
_body: web::Bytes,
26
26
-
logs: web::Data<LogStore>,
27
27
-
) -> impl Responder {
28
28
-
let method = req.method().to_string();
29
29
-
let path = req.path().to_string();
30
30
-
let query = req.query_string();
31
31
-
let query_opt = if query.is_empty() { None } else { Some(query.to_string()) };
26
26
+
async fn replay_handler(
27
27
+
req: HttpRequest,
28
28
+
_body: web::Bytes,
29
29
+
logs: web::Data<LogStore>,
30
30
+
) -> impl Responder {
31
31
+
let method = req.method().to_string();
32
32
+
let path = req.path().to_string();
33
33
+
let query = req.query_string();
34
34
+
let query_opt = if query.is_empty() {
35
35
+
None
36
36
+
} else {
37
37
+
Some(query.to_string())
38
38
+
};
32
39
33
33
-
let key = build_request_key(&method, &path, &query_opt);
34
34
-
println!("Replay server received request: {}", key.magenta());
40
40
+
let key = build_request_key(&method, &path, &query_opt);
41
41
+
println!("Replay server received request: {}", key.magenta());
35
42
36
36
-
let response = {
37
37
-
let logs_guard = logs.lock().await;
38
38
-
logs_guard.iter()
39
39
-
.find(|log| {
40
40
-
let log_key = build_request_key(
41
41
-
&log.request.method,
42
42
-
&log.request.path,
43
43
-
&log.request.query_params
44
44
-
);
45
45
-
log_key == key
46
46
-
})
47
47
-
.cloned()
48
48
-
};
43
43
+
let response = {
44
44
+
let logs_guard = logs.lock().await;
45
45
+
logs_guard
46
46
+
.iter()
47
47
+
.find(|log| {
48
48
+
let log_key = build_request_key(
49
49
+
&log.request.method,
50
50
+
&log.request.path,
51
51
+
&log.request.query_params,
52
52
+
);
53
53
+
log_key == key
54
54
+
})
55
55
+
.cloned()
56
56
+
};
49
57
50
50
-
if let Some(log) = response {
51
51
-
println!("Found matching response for: {}", key.magenta());
58
58
+
if let Some(log) = response {
59
59
+
println!("Found matching response for: {}", key.magenta());
52
60
53
53
-
let mut response_builder = HttpResponse::build(
54
54
-
actix_web::http::StatusCode::from_u16(log.response.status).unwrap_or(actix_web::http::StatusCode::OK)
55
55
-
);
61
61
+
let mut response_builder = HttpResponse::build(
62
62
+
actix_web::http::StatusCode::from_u16(log.response.status)
63
63
+
.unwrap_or(actix_web::http::StatusCode::OK),
64
64
+
);
56
65
57
57
-
for (name, value) in log.response.headers {
58
58
-
if let (Ok(header_name), Ok(header_value)) = (
59
59
-
HeaderName::try_from(name.as_str()),
60
60
-
HeaderValue::try_from(value.as_str())
61
61
-
) {
62
62
-
response_builder.append_header((header_name, header_value));
63
63
-
}
64
64
-
}
66
66
+
for (name, value) in log.response.headers {
67
67
+
if let (Ok(header_name), Ok(header_value)) = (
68
68
+
HeaderName::try_from(name.as_str()),
69
69
+
HeaderValue::try_from(value.as_str()),
70
70
+
) {
71
71
+
response_builder.append_header((header_name, header_value));
72
72
+
}
73
73
+
}
65
74
66
66
-
if let Some(body) = log.response.body {
67
67
-
response_builder.body(body)
68
68
-
} else {
69
69
-
response_builder.finish()
70
70
-
}
71
71
-
} else {
72
72
-
println!("No matching response found for: {}", key.magenta());
73
73
-
HttpResponse::NotFound().body("No matching request found in logs")
74
74
-
}
75
75
-
}
75
75
+
if let Some(body) = log.response.body {
76
76
+
response_builder.body(body)
77
77
+
} else {
78
78
+
response_builder.finish()
79
79
+
}
80
80
+
} else {
81
81
+
println!("No matching response found for: {}", key.magenta());
82
82
+
HttpResponse::NotFound().body("No matching request found in logs")
83
83
+
}
84
84
+
}
76
85
77
77
-
async fn list_requests(logs: web::Data<LogStore>) -> impl Responder {
78
78
-
let logs_guard = logs.lock().await;
79
79
-
let requests: Vec<_> = logs_guard.iter().map(|log| {
80
80
-
let key = build_request_key(
81
81
-
&log.request.method,
82
82
-
&log.request.path,
83
83
-
&log.request.query_params
84
84
-
);
85
85
-
(key, log.response.status)
86
86
-
}).collect();
86
86
+
async fn list_requests(logs: web::Data<LogStore>) -> impl Responder {
87
87
+
let logs_guard = logs.lock().await;
88
88
+
let requests: Vec<_> = logs_guard
89
89
+
.iter()
90
90
+
.map(|log| {
91
91
+
let key = build_request_key(
92
92
+
&log.request.method,
93
93
+
&log.request.path,
94
94
+
&log.request.query_params,
95
95
+
);
96
96
+
(key, log.response.status)
97
97
+
})
98
98
+
.collect();
87
99
88
88
-
web::Json(requests)
89
89
-
}
100
100
+
web::Json(requests)
101
101
+
}
90
102
91
91
-
HttpServer::new(move || {
92
92
-
App::new()
93
93
-
.app_data(web::Data::new(logs.clone()))
94
94
-
.route("/admin/requests", web::get().to(list_requests))
95
95
-
.default_service(web::route().to(replay_handler))
96
96
-
})
97
97
-
.bind(bind_address)?
98
98
-
.run()
99
99
-
.await?;
103
103
+
HttpServer::new(move || {
104
104
+
App::new()
105
105
+
.app_data(web::Data::new(logs.clone()))
106
106
+
.route("/admin/requests", web::get().to(list_requests))
107
107
+
.default_service(web::route().to(replay_handler))
108
108
+
})
109
109
+
.bind(bind_address)?
110
110
+
.run()
111
111
+
.await?;
100
112
101
101
-
Ok(())
102
102
-
}
113
113
+
Ok(())
114
114
+
}