1use serde_json;
5
6#[cfg(feature = "tokio")]
7use bitreq::RequestExt;
8
9use std::convert::Infallible;
10use std::convert::TryFrom;
11use std::fmt;
12
13pub trait ToParseErrorMessage {
15 fn to_parse_error_message(self) -> String;
17}
18
19impl ToParseErrorMessage for Infallible {
20 fn to_parse_error_message(self) -> String {
21 match self {}
22 }
23}
24
25impl ToParseErrorMessage for () {
26 fn to_parse_error_message(self) -> String {
27 "invalid data".to_string()
28 }
29}
30
31impl ToParseErrorMessage for &'static str {
32 fn to_parse_error_message(self) -> String {
33 self.to_string()
34 }
35}
36
37impl ToParseErrorMessage for String {
38 fn to_parse_error_message(self) -> String {
39 self
40 }
41}
42
43const TCP_STREAM_RESPONSE_TIMEOUT: u64 = 300;
47
48const MAX_HTTP_MESSAGE_BODY_SIZE: usize = 2 * 4_000_000 + 32_000;
51
52#[derive(Debug)]
54pub enum HttpClientError {
55 Transport(bitreq::Error),
57 Http(HttpError),
59 Parse(String),
61}
62
63impl std::error::Error for HttpClientError {
64 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
65 match self {
66 HttpClientError::Transport(e) => Some(e),
67 HttpClientError::Http(e) => Some(e),
68 HttpClientError::Parse(_) => None,
69 }
70 }
71}
72
73impl fmt::Display for HttpClientError {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 match self {
76 HttpClientError::Transport(e) => write!(f, "transport error: {}", e),
77 HttpClientError::Http(e) => write!(f, "HTTP error: {}", e),
78 HttpClientError::Parse(e) => write!(f, "response parsing error: {}", e),
79 }
80 }
81}
82
83impl From<bitreq::Error> for HttpClientError {
84 fn from(e: bitreq::Error) -> Self {
85 HttpClientError::Transport(e)
86 }
87}
88
89impl From<HttpError> for HttpClientError {
90 fn from(e: HttpError) -> Self {
91 HttpClientError::Http(e)
92 }
93}
94
95#[cfg(feature = "tokio")]
97const MAX_CONNECTIONS: usize = 10;
98
99pub(crate) struct HttpClient {
101 base_url: String,
102 #[cfg(feature = "tokio")]
103 client: bitreq::Client,
104}
105
106impl HttpClient {
107 pub fn new(base_url: String) -> Self {
112 Self {
113 base_url,
114 #[cfg(feature = "tokio")]
115 client: bitreq::Client::new(MAX_CONNECTIONS),
116 }
117 }
118
119 #[allow(dead_code)]
123 pub async fn get<F>(&self, uri: &str) -> Result<F, HttpClientError>
124 where
125 F: TryFrom<Vec<u8>>,
126 <F as TryFrom<Vec<u8>>>::Error: ToParseErrorMessage,
127 {
128 let url = format!("{}{}", self.base_url, uri);
129 let request = bitreq::get(url)
130 .with_timeout(TCP_STREAM_RESPONSE_TIMEOUT)
131 .with_max_body_size(Some(MAX_HTTP_MESSAGE_BODY_SIZE));
132 #[cfg(feature = "tokio")]
133 let request = request.with_pipelining();
134 let response_body = self.send_request(request).await?;
135 F::try_from(response_body).map_err(|e| HttpClientError::Parse(e.to_parse_error_message()))
136 }
137
138 #[allow(dead_code)]
144 pub async fn post<F>(
145 &self, uri: &str, auth: &str, content: serde_json::Value,
146 ) -> Result<F, HttpClientError>
147 where
148 F: TryFrom<Vec<u8>>,
149 <F as TryFrom<Vec<u8>>>::Error: ToParseErrorMessage,
150 {
151 let url = format!("{}{}", self.base_url, uri);
152 let request = bitreq::post(url)
153 .with_header("Authorization", auth)
154 .with_header("Content-Type", "application/json")
155 .with_timeout(TCP_STREAM_RESPONSE_TIMEOUT)
156 .with_max_body_size(Some(MAX_HTTP_MESSAGE_BODY_SIZE))
157 .with_body(content.to_string());
158 #[cfg(feature = "tokio")]
159 let request = request.with_pipelining();
160 let response_body = self.send_request(request).await?;
161 F::try_from(response_body).map_err(|e| HttpClientError::Parse(e.to_parse_error_message()))
162 }
163
164 async fn send_request(&self, request: bitreq::Request) -> Result<Vec<u8>, HttpClientError> {
166 #[cfg(feature = "tokio")]
167 let response = request.send_async_with_client(&self.client).await?;
168 #[cfg(not(feature = "tokio"))]
169 let response = request.send()?;
170
171 let status_code = response.status_code;
172 let body = response.into_bytes();
173
174 if !(200..300).contains(&status_code) {
175 return Err(HttpError { status_code, contents: body }.into());
176 }
177
178 Ok(body)
179 }
180}
181
182#[derive(Debug)]
184pub struct HttpError {
185 pub status_code: i32,
187 pub contents: Vec<u8>,
189}
190
191impl std::error::Error for HttpError {}
192
193impl fmt::Display for HttpError {
194 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195 let contents = String::from_utf8_lossy(&self.contents);
196 write!(f, "status_code: {}, contents: {}", self.status_code, contents)
197 }
198}
199
200pub struct BinaryResponse(pub Vec<u8>);
202
203pub struct JsonResponse(pub serde_json::Value);
205
206impl TryFrom<Vec<u8>> for BinaryResponse {
208 type Error = Infallible;
209
210 fn try_from(bytes: Vec<u8>) -> Result<Self, Infallible> {
211 Ok(BinaryResponse(bytes))
212 }
213}
214
215impl TryFrom<Vec<u8>> for JsonResponse {
217 type Error = String;
218
219 fn try_from(bytes: Vec<u8>) -> Result<Self, String> {
220 serde_json::from_slice(&bytes).map(JsonResponse).map_err(|e| e.to_string())
221 }
222}
223
224#[cfg(test)]
225pub(crate) mod client_tests {
226 use super::*;
227 use std::io::{BufRead, Read, Write};
228 use std::time::Duration;
229
230 pub struct HttpServer {
232 address: std::net::SocketAddr,
233 handler: Option<std::thread::JoinHandle<()>>,
234 shutdown: std::sync::Arc<std::sync::atomic::AtomicBool>,
235 }
236
237 impl Drop for HttpServer {
238 fn drop(&mut self) {
239 self.shutdown.store(true, std::sync::atomic::Ordering::SeqCst);
240 let _ = std::net::TcpStream::connect(self.address);
242 if let Some(handler) = self.handler.take() {
243 let _ = handler.join();
244 }
245 }
246 }
247
248 pub enum MessageBody<T: ToString> {
250 Empty,
251 Content(T),
252 }
253
254 impl HttpServer {
255 fn responding_with_body<T: ToString>(status: &str, body: MessageBody<T>) -> Self {
256 let response = match body {
257 MessageBody::Empty => format!(
258 "{}\r\n\
259 Content-Length: 0\r\n\
260 \r\n",
261 status
262 ),
263 MessageBody::Content(body) => {
264 let body = body.to_string();
265 format!(
266 "{}\r\n\
267 Content-Length: {}\r\n\
268 \r\n\
269 {}",
270 status,
271 body.len(),
272 body
273 )
274 },
275 };
276 HttpServer::responding_with(response)
277 }
278
279 pub fn responding_with_ok<T: ToString>(body: MessageBody<T>) -> Self {
280 HttpServer::responding_with_body("HTTP/1.1 200 OK", body)
281 }
282
283 pub fn responding_with_not_found() -> Self {
284 HttpServer::responding_with_body::<String>("HTTP/1.1 404 Not Found", MessageBody::Empty)
285 }
286
287 pub fn responding_with_server_error<T: ToString>(content: T) -> Self {
288 let body = MessageBody::Content(content);
289 HttpServer::responding_with_body("HTTP/1.1 500 Internal Server Error", body)
290 }
291
292 fn responding_with(response: String) -> Self {
293 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
294 let address = listener.local_addr().unwrap();
295
296 let shutdown = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
297 let shutdown_signaled = std::sync::Arc::clone(&shutdown);
298 let handler = std::thread::spawn(move || {
299 let timeout = Duration::from_secs(5);
300 for stream in listener.incoming() {
301 if shutdown_signaled.load(std::sync::atomic::Ordering::SeqCst) {
302 return;
303 }
304
305 let stream = stream.unwrap();
306 stream.set_write_timeout(Some(timeout)).unwrap();
307 stream.set_read_timeout(Some(timeout)).unwrap();
308
309 let mut reader = std::io::BufReader::new(stream);
310
311 loop {
313 if shutdown_signaled.load(std::sync::atomic::Ordering::SeqCst) {
314 return;
315 }
316
317 let mut lines_read = 0;
319 let mut content_length: usize = 0;
320 loop {
321 let mut line = String::new();
322 match reader.read_line(&mut line) {
323 Ok(0) => break, Ok(_) => {
325 if line == "\r\n" || line == "\n" {
326 break; }
328 if let Some(value) = line.strip_prefix("Content-Length:") {
330 content_length = value.trim().parse().unwrap_or(0);
331 }
332 lines_read += 1;
333 },
334 Err(_) => break, }
336 }
337
338 if lines_read == 0 {
339 break; }
341
342 if content_length > 0 {
344 let mut body = vec![0u8; content_length];
345 if reader.read_exact(&mut body).is_err() {
346 break;
347 }
348 }
349
350 let stream = reader.get_mut();
352 let mut write_error = false;
353 for chunk in response.as_bytes().chunks(16) {
354 if shutdown_signaled.load(std::sync::atomic::Ordering::SeqCst) {
355 return;
356 }
357 if stream.write(chunk).is_err() || stream.flush().is_err() {
358 write_error = true;
359 break;
360 }
361 }
362 if write_error {
363 break;
364 }
365 }
366 }
367 });
368
369 Self { address, handler: Some(handler), shutdown }
370 }
371
372 pub fn endpoint(&self) -> String {
373 format!("http://{}:{}", self.address.ip(), self.address.port())
374 }
375 }
376
377 #[tokio::test]
378 async fn connect_with_invalid_host() {
379 let client = HttpClient::new("http://invalid.host.example:80".to_string());
380 match client.get::<JsonResponse>("/foo").await {
381 Err(HttpClientError::Transport(_)) => {},
382 Err(e) => panic!("Unexpected error type: {:?}", e),
383 Ok(_) => panic!("Expected error"),
384 }
385 }
386
387 #[tokio::test]
388 async fn read_error() {
389 let server = HttpServer::responding_with_server_error("foo");
390
391 let client = HttpClient::new(server.endpoint());
392 match client.get::<JsonResponse>("/foo").await {
393 Err(HttpClientError::Http(http_error)) => {
394 assert_eq!(http_error.status_code, 500);
395 assert_eq!(http_error.contents, "foo".as_bytes());
396 },
397 Err(e) => panic!("Unexpected error type: {:?}", e),
398 Ok(_) => panic!("Expected error"),
399 }
400 }
401
402 #[tokio::test]
403 async fn read_message_body() {
404 let body = "foo bar baz qux".repeat(32);
405 let content = MessageBody::Content(body.clone());
406 let server = HttpServer::responding_with_ok::<String>(content);
407
408 let client = HttpClient::new(server.endpoint());
409 match client.get::<BinaryResponse>("/foo").await {
410 Err(e) => panic!("Unexpected error: {:?}", e),
411 Ok(bytes) => assert_eq!(bytes.0, body.as_bytes()),
412 }
413 }
414
415 #[tokio::test]
416 async fn reconnect_closed_connection() {
417 let server = HttpServer::responding_with_ok::<String>(MessageBody::Empty);
418
419 let client = HttpClient::new(server.endpoint());
420 assert!(client.get::<BinaryResponse>("/foo").await.is_ok());
421 match client.get::<BinaryResponse>("/foo").await {
422 Err(e) => panic!("Unexpected error: {:?}", e),
423 Ok(bytes) => assert_eq!(bytes.0, Vec::<u8>::new()),
424 }
425 }
426
427 #[test]
428 fn from_bytes_into_binary_response() {
429 let bytes = b"foo";
430 match BinaryResponse::try_from(bytes.to_vec()) {
431 Err(e) => panic!("Unexpected error: {:?}", e),
432 Ok(response) => assert_eq!(&response.0, bytes),
433 }
434 }
435
436 #[test]
437 fn from_invalid_bytes_into_json_response() {
438 let json = serde_json::json!({ "result": 42 });
439 match JsonResponse::try_from(json.to_string().as_bytes()[..5].to_vec()) {
440 Err(_) => {},
441 Ok(_) => panic!("Expected error"),
442 }
443 }
444
445 #[test]
446 fn from_valid_bytes_into_json_response() {
447 let json = serde_json::json!({ "result": 42 });
448 match JsonResponse::try_from(json.to_string().as_bytes().to_vec()) {
449 Err(e) => panic!("Unexpected error: {:?}", e),
450 Ok(response) => assert_eq!(response.0, json),
451 }
452 }
453}