Skip to content

Commit ce38a89

Browse files
committed
node: Serialize API request once, reuse in retries
1 parent f898ddf commit ce38a89

File tree

4 files changed

+66
-15
lines changed

4 files changed

+66
-15
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ anyhow = "1"
4949
argh = "0.1"
5050
# async fn's in trait methods
5151
async-trait = "0.1"
52+
# Working with bytes
53+
bytes = "1"
5254
# Used to manually poll a future in ldk-sample's networking code TODO remove
5355
futures = "0.3"
5456
# Used to specify the HTTP methods used to interact with the API

node/src/api/client.rs

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::fmt::{self, Display};
44
use std::time::Duration;
55

66
use async_trait::async_trait;
7+
use bytes::Bytes;
78
use common::api::provision::SealedSeedId;
89
use common::api::qs::{GetByUserPk, GetByUserPkAndMeasurement};
910
use common::api::runner::UserPorts;
@@ -15,7 +16,7 @@ use reqwest::Client;
1516
use serde::de::DeserializeOwned;
1617
use serde::Serialize;
1718
use tokio::time;
18-
use tracing::debug;
19+
use tracing::{debug, trace};
1920

2021
use self::ApiVersion::*;
2122
use self::BaseUrl::*;
@@ -26,6 +27,8 @@ const API_REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
2627
/// This is relatively long since if the second try failed, it probably means
2728
/// that the backend is down, which could be the case for a while.
2829
const RETRY_INTERVAL: Duration = Duration::from_secs(15);
30+
/// 0 retries by default
31+
const DEFAULT_RETRIES: usize = 0;
2932

3033
// Avoid `Method::` prefix. Associated constants can't be imported
3134
const GET: Method = Method::GET;
@@ -53,6 +56,12 @@ impl Display for ApiVersion {
5356
}
5457
}
5558

59+
struct RequestParts {
60+
method: Method,
61+
url: String,
62+
body: Bytes,
63+
}
64+
5665
pub struct LexeApiClient {
5766
client: Client,
5867
backend_url: String,
@@ -184,7 +193,27 @@ impl ApiClient for LexeApiClient {
184193
}
185194

186195
impl LexeApiClient {
187-
/// Tries to complete an API request, retrying up to `retries` times.
196+
/// Makes an API request, retrying up to `DEFAULT_RETRIES` times.
197+
async fn request<D: Serialize, T: DeserializeOwned>(
198+
&self,
199+
method: &Method,
200+
base: BaseUrl,
201+
ver: ApiVersion,
202+
endpoint: &str,
203+
data: &D,
204+
) -> Result<T, ApiError> {
205+
self.request_with_retries(
206+
method,
207+
base,
208+
ver,
209+
endpoint,
210+
data,
211+
DEFAULT_RETRIES,
212+
)
213+
.await
214+
}
215+
216+
/// Makes an API request, retrying up to `retries` times.
188217
async fn request_with_retries<D: Serialize, T: DeserializeOwned>(
189218
&self,
190219
method: &Method,
@@ -196,10 +225,12 @@ impl LexeApiClient {
196225
) -> Result<T, ApiError> {
197226
let mut retry_timer = time::interval(RETRY_INTERVAL);
198227

199-
// 'Do the retries first' and return early if successful.
200-
// If retries == 0 this block is a noop.
228+
let parts = self.serialize_parts(method, base, ver, endpoint, data)?;
229+
230+
// Do the 'retries' first and return early if successful.
231+
// This block is a noop if retries == 0.
201232
for _ in 0..retries {
202-
let res = self.request(method, base, ver, endpoint, data).await;
233+
let res = self.send_request(&parts).await;
203234
if res.is_ok() {
204235
return res;
205236
} else {
@@ -215,19 +246,19 @@ impl LexeApiClient {
215246
}
216247
}
217248

218-
// Do the 'main attempt'.
219-
self.request(method, base, ver, endpoint, &data).await
249+
// Do the 'main' attempt.
250+
self.send_request(&parts).await
220251
}
221252

222-
/// Executes an API request once.
223-
async fn request<D: Serialize, T: DeserializeOwned>(
253+
/// Constructs the final, serialized parts of a [`reqwest::Request`].
254+
fn serialize_parts<D: Serialize>(
224255
&self,
225256
method: &Method,
226257
base: BaseUrl,
227258
ver: ApiVersion,
228259
endpoint: &str,
229260
data: &D,
230-
) -> Result<T, ApiError> {
261+
) -> Result<RequestParts, ApiError> {
231262
// Node backend api is versioned but runner api is not
232263
let (base, ver) = match base {
233264
Backend => (&self.backend_url, ver.to_string()),
@@ -251,14 +282,31 @@ impl LexeApiClient {
251282
debug!(%method, %url, "sending request");
252283

253284
// If PUT or POST, serialize the data in the request body
254-
let body = match method {
285+
let body_str = match method {
255286
PUT | POST => serde_json::to_string(data)?,
256287
_ => String::new(),
257288
};
258-
// println!(" Body: {}", body);
289+
trace!(%body_str);
290+
let body = Bytes::from(body_str);
259291

260-
let response =
261-
self.client.request(method, url).body(body).send().await?;
292+
Ok(RequestParts { method, url, body })
293+
}
294+
295+
/// Build a [`reqwest::Request`] from [`RequestParts`], send it, and
296+
/// deserialize into the expected struct or an error message depending on
297+
/// the response status.
298+
async fn send_request<T: DeserializeOwned>(
299+
&self,
300+
parts: &RequestParts,
301+
) -> Result<T, ApiError> {
302+
let response = self
303+
.client
304+
// Method doesn't implement Copy
305+
.request(parts.method.clone(), &parts.url)
306+
// body is Bytes which can be cheaply cloned
307+
.body(parts.body.clone())
308+
.send()
309+
.await?;
262310

263311
if response.status().is_success() {
264312
// Uncomment for debugging

node/src/lexe/persister.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const SCORER_FILENAME: &str = "scorer";
4242
pub const CHANNEL_PEERS_DIRECTORY: &str = "channel_peers";
4343
pub const CHANNEL_MONITORS_DIRECTORY: &str = "channel_monitors";
4444

45-
/// The default number of retries for important persisted state
45+
// The default number of persist retries for important objects
4646
const DEFAULT_RETRIES: usize = 3;
4747

4848
/// An Arc is held internally, so it is fine to clone and use directly.

0 commit comments

Comments
 (0)