Skip to content

Commit ffa084e

Browse files
committed
cleanup
1 parent ae1783e commit ffa084e

File tree

3 files changed

+42
-91
lines changed

3 files changed

+42
-91
lines changed

lightning-block-sync/src/http.rs

Lines changed: 38 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ use bitreq::RequestExt;
88

99
use std::convert::TryFrom;
1010
use std::fmt;
11-
use std::net::{SocketAddr, ToSocketAddrs};
1211
use std::time::Duration;
1312

14-
/// Timeout for reading the first byte of a response. This is separate from the general read
15-
/// timeout as it is not uncommon for Bitcoin Core to be blocked waiting on UTXO cache flushes for
16-
/// upwards of 10 minutes on slow devices (e.g. RPis with SSDs over USB). Note that we always retry
17-
/// once when we time out, so the maximum time we allow Bitcoin Core to block for is twice this
18-
/// value.
13+
/// Timeout for requests. This is set to a high value as it is not uncommon for Bitcoin Core to be
14+
/// blocked waiting on UTXO cache flushes for upwards of 10 minutes on slow devices (e.g. RPis with
15+
/// SSDs over USB).
1916
const TCP_STREAM_RESPONSE_TIMEOUT: Duration = Duration::from_secs(300);
2017

2118
/// Maximum HTTP message body size in bytes. Enough for a hex-encoded block in JSON format and any
@@ -48,7 +45,7 @@ impl fmt::Display for HttpClientError {
4845
match self {
4946
HttpClientError::Transport(e) => write!(f, "transport error: {}", e),
5047
HttpClientError::Http(e) => write!(f, "HTTP error: {}", e),
51-
HttpClientError::Io(e) => write!(f, "I/O error: {}", e),
48+
HttpClientError::Io(e) => write!(f, "Response parsing/conversion error: {}", e),
5249
}
5350
}
5451
}
@@ -130,49 +127,38 @@ const MAX_CONNECTIONS: usize = 10;
130127

131128
/// Client for making HTTP requests.
132129
pub(crate) struct HttpClient {
133-
address: SocketAddr,
130+
host: String,
131+
port: u16,
134132
#[cfg(feature = "tokio")]
135133
client: bitreq::Client,
136134
}
137135

138136
impl HttpClient {
139-
/// Opens a connection to an HTTP endpoint.
140-
pub fn connect<E: ToSocketAddrs>(endpoint: E) -> Result<Self, HttpClientError> {
141-
let address = match endpoint.to_socket_addrs()?.next() {
142-
None => {
143-
return Err(std::io::Error::new(
144-
std::io::ErrorKind::InvalidInput,
145-
"could not resolve to any addresses",
146-
)
147-
.into());
148-
},
149-
Some(address) => address,
150-
};
151-
152-
Ok(Self {
153-
address,
137+
/// Creates a new HTTP client for the given endpoint.
138+
///
139+
/// DNS resolution is deferred until the first request is made.
140+
pub fn new(endpoint: &HttpEndpoint) -> Self {
141+
Self {
142+
host: endpoint.host().to_string(),
143+
port: endpoint.port(),
154144
#[cfg(feature = "tokio")]
155145
client: bitreq::Client::new(MAX_CONNECTIONS),
156-
})
146+
}
157147
}
158148

159149
/// Sends a `GET` request for a resource identified by `uri`.
160150
///
161151
/// Returns the response body in `F` format.
162152
#[allow(dead_code)]
163-
pub async fn get<F>(&mut self, uri: &str) -> Result<F, HttpClientError>
153+
pub async fn get<F>(&self, uri: &str) -> Result<F, HttpClientError>
164154
where
165155
F: TryFrom<Vec<u8>, Error = std::io::Error>,
166156
{
167-
let address = self.address;
168-
let response_body = self
169-
.send_request_with_retry(|| {
170-
let url = format!("http://{}{}", address, uri);
171-
bitreq::get(url)
172-
.with_timeout(TCP_STREAM_RESPONSE_TIMEOUT.as_secs())
173-
.with_max_body_size(Some(MAX_HTTP_MESSAGE_BODY_SIZE))
174-
})
175-
.await?;
157+
let url = format!("http://{}:{}{}", self.host, self.port, uri);
158+
let request = bitreq::get(url)
159+
.with_timeout(TCP_STREAM_RESPONSE_TIMEOUT.as_secs())
160+
.with_max_body_size(Some(MAX_HTTP_MESSAGE_BODY_SIZE));
161+
let response_body = self.send_request(request).await?;
176162
F::try_from(response_body).map_err(HttpClientError::Io)
177163
}
178164

@@ -183,57 +169,22 @@ impl HttpClient {
183169
/// format.
184170
#[allow(dead_code)]
185171
pub async fn post<F>(
186-
&mut self, uri: &str, auth: &str, content: serde_json::Value,
172+
&self, uri: &str, auth: &str, content: serde_json::Value,
187173
) -> Result<F, HttpClientError>
188174
where
189175
F: TryFrom<Vec<u8>, Error = std::io::Error>,
190176
{
191-
let address = self.address;
192-
let content = content.to_string();
193-
let response_body = self
194-
.send_request_with_retry(|| {
195-
let url = format!("http://{}{}", address, uri);
196-
bitreq::post(url)
197-
.with_header("Authorization", auth)
198-
.with_header("Content-Type", "application/json")
199-
.with_timeout(TCP_STREAM_RESPONSE_TIMEOUT.as_secs())
200-
.with_max_body_size(Some(MAX_HTTP_MESSAGE_BODY_SIZE))
201-
.with_body(content.clone())
202-
})
203-
.await?;
177+
let url = format!("http://{}:{}{}", self.host, self.port, uri);
178+
let request = bitreq::post(url)
179+
.with_header("Authorization", auth)
180+
.with_header("Content-Type", "application/json")
181+
.with_timeout(TCP_STREAM_RESPONSE_TIMEOUT.as_secs())
182+
.with_max_body_size(Some(MAX_HTTP_MESSAGE_BODY_SIZE))
183+
.with_body(content.to_string());
184+
let response_body = self.send_request(request).await?;
204185
F::try_from(response_body).map_err(HttpClientError::Io)
205186
}
206187

207-
/// Sends an HTTP request message and reads the response, returning its body. Attempts to
208-
/// reconnect and retry only on transport failures (not on HTTP errors like 500/404).
209-
async fn send_request_with_retry(
210-
&mut self, build_request: impl Fn() -> bitreq::Request,
211-
) -> Result<Vec<u8>, HttpClientError> {
212-
match self.send_request(build_request()).await {
213-
Ok(bytes) => Ok(bytes),
214-
Err(HttpClientError::Http(e)) => {
215-
// Don't retry on HTTP errors (non-2xx responses)
216-
Err(HttpClientError::Http(e))
217-
},
218-
Err(HttpClientError::Io(e)) => {
219-
// Don't retry on I/O errors (e.g., response parsing failures).
220-
Err(HttpClientError::Io(e))
221-
},
222-
Err(HttpClientError::Transport(_)) => {
223-
// Reconnect and retry on transport failures. This can happen if the connection
224-
// was closed after the keep-alive limits are reached, or generally if the
225-
// request timed out due to Bitcoin Core being stuck on a long-running operation
226-
// or its RPC queue being full.
227-
#[cfg(feature = "tokio")]
228-
tokio::time::sleep(Duration::from_millis(100)).await;
229-
#[cfg(not(feature = "tokio"))]
230-
std::thread::sleep(Duration::from_millis(100));
231-
*self = Self::connect(self.address)?;
232-
self.send_request(build_request()).await
233-
},
234-
}
235-
}
236-
237188
/// Sends an HTTP request message and reads the response, returning its body.
238189
async fn send_request(&self, request: bitreq::Request) -> Result<Vec<u8>, HttpClientError> {
239190
#[cfg(feature = "tokio")]
@@ -446,12 +397,12 @@ pub(crate) mod client_tests {
446397
}
447398
}
448399

449-
#[test]
450-
fn connect_with_no_socket_address() {
451-
match HttpClient::connect(&vec![][..]) {
452-
Err(HttpClientError::Io(e)) => {
453-
assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput)
454-
},
400+
#[tokio::test]
401+
async fn connect_with_invalid_host() {
402+
let endpoint = HttpEndpoint::for_host("invalid.host.example".to_string()).with_port(80);
403+
let client = HttpClient::new(&endpoint);
404+
match client.get::<JsonResponse>("/foo").await {
405+
Err(HttpClientError::Transport(_)) => {},
455406
Err(e) => panic!("Unexpected error type: {:?}", e),
456407
Ok(_) => panic!("Expected error"),
457408
}
@@ -461,7 +412,7 @@ pub(crate) mod client_tests {
461412
async fn read_error() {
462413
let server = HttpServer::responding_with_server_error("foo");
463414

464-
let mut client = HttpClient::connect(&server.endpoint()).unwrap();
415+
let client = HttpClient::new(&server.endpoint());
465416
match client.get::<JsonResponse>("/foo").await {
466417
Err(HttpClientError::Http(http_error)) => {
467418
assert_eq!(http_error.status_code, 500);
@@ -478,7 +429,7 @@ pub(crate) mod client_tests {
478429
let content = MessageBody::Content(body.clone());
479430
let server = HttpServer::responding_with_ok::<String>(content);
480431

481-
let mut client = HttpClient::connect(&server.endpoint()).unwrap();
432+
let client = HttpClient::new(&server.endpoint());
482433
match client.get::<BinaryResponse>("/foo").await {
483434
Err(e) => panic!("Unexpected error: {:?}", e),
484435
Ok(bytes) => assert_eq!(bytes.0, body.as_bytes()),
@@ -489,7 +440,7 @@ pub(crate) mod client_tests {
489440
async fn reconnect_closed_connection() {
490441
let server = HttpServer::responding_with_ok::<String>(MessageBody::Empty);
491442

492-
let mut client = HttpClient::connect(&server.endpoint()).unwrap();
443+
let client = HttpClient::new(&server.endpoint());
493444
assert!(client.get::<BinaryResponse>("/foo").await.is_ok());
494445
match client.get::<BinaryResponse>("/foo").await {
495446
Err(e) => panic!("Unexpected error: {:?}", e),

lightning-block-sync/src/rest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ impl RestClient {
3535
{
3636
let uri = format!("{}/{}", self.endpoint.path().trim_end_matches("/"), resource_path);
3737
let reserved_client = self.client.lock().unwrap().take();
38-
let mut client = if let Some(client) = reserved_client {
38+
let client = if let Some(client) = reserved_client {
3939
client
4040
} else {
41-
HttpClient::connect(&self.endpoint)?
41+
HttpClient::new(&self.endpoint)
4242
};
4343
let res = client.get::<F>(&uri).await?.try_into().map_err(HttpClientError::Io);
4444
*self.client.lock().unwrap() = Some(client);

lightning-block-sync/src/rpc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ impl RpcClient {
118118
});
119119

120120
let reserved_client = self.client.lock().unwrap().take();
121-
let mut client = if let Some(client) = reserved_client {
121+
let client = if let Some(client) = reserved_client {
122122
client
123123
} else {
124-
HttpClient::connect(&self.endpoint)?
124+
HttpClient::new(&self.endpoint)
125125
};
126126
let http_response = client.post::<JsonResponse>(&uri, &self.basic_auth, content).await;
127127
*self.client.lock().unwrap() = Some(client);

0 commit comments

Comments
 (0)