Add read_response helper
This fixes a bug where a chunked response would be partially read and then hang forever waiting for another chunk, and adds additional debug logging to the request process.
This commit is contained in:
parent
c9fc25a0c9
commit
648de8c8e5
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "elefren"
|
name = "elefren"
|
||||||
version = "0.24.0"
|
version = "1.0.0"
|
||||||
authors = ["Aaron Power <theaaronepower@gmail.com>", "Paul Woolcock <paul@woolcock.us>", "D. Scott Boggs <scott@tams.tech>"]
|
authors = ["Aaron Power <theaaronepower@gmail.com>", "Paul Woolcock <paul@woolcock.us>", "D. Scott Boggs <scott@tams.tech>"]
|
||||||
description = "A wrapper around the Mastodon API."
|
description = "A wrapper around the Mastodon API."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::errors::Result;
|
||||||
|
use futures::pin_mut;
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use log::{as_serde, debug, trace, warn};
|
||||||
|
use reqwest::Response;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
/// Adapter for reading JSON data from a response with better logging and a
|
||||||
|
/// fail-safe timeout.
|
||||||
|
pub async fn read_response<T>(response: Response) -> Result<T>
|
||||||
|
where
|
||||||
|
T: for<'de> Deserialize<'de> + Serialize,
|
||||||
|
{
|
||||||
|
let mut bytes = vec![];
|
||||||
|
let url = response.url().clone();
|
||||||
|
// let status = log_serde!(response Status);
|
||||||
|
// let headers = log_serde!(response Headers);
|
||||||
|
let stream = response.bytes_stream();
|
||||||
|
pin_mut!(stream);
|
||||||
|
loop {
|
||||||
|
if let Ok(data) = timeout(Duration::from_secs(10), stream.next()).await {
|
||||||
|
// as of here, we did not time out
|
||||||
|
let Some(data) = data else { break; };
|
||||||
|
// as of here, we have not hit the end of the stream yet
|
||||||
|
let data = data?;
|
||||||
|
// as of here, we did not hit an error while reading the body
|
||||||
|
bytes.extend_from_slice(&data);
|
||||||
|
debug!(
|
||||||
|
data = String::from_utf8_lossy(&data), url = url.as_str(),
|
||||||
|
bytes_received_so_far = bytes.len();
|
||||||
|
"data chunk received"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
url = url.as_str(), // status = status, headers = headers,
|
||||||
|
data_received = bytes.len();
|
||||||
|
"API response timed out"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace!(
|
||||||
|
url = url.as_str(), // status = status, headers = headers,
|
||||||
|
data_received = bytes.len();
|
||||||
|
"parsing response"
|
||||||
|
);
|
||||||
|
let result = serde_json::from_slice(bytes.as_slice())?;
|
||||||
|
debug!(
|
||||||
|
url = url.as_str(), // status = status, headers = headers,
|
||||||
|
result = as_serde!(result);
|
||||||
|
"result parsed successfully"
|
||||||
|
);
|
||||||
|
Ok(result)
|
||||||
|
}
|
|
@ -242,7 +242,7 @@ macro_rules! route {
|
||||||
|
|
||||||
match response.error_for_status() {
|
match response.error_for_status() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let response = response.json().await?;
|
let response = read_response(response).await?;
|
||||||
debug!(response = as_serde!(response), url = url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
|
debug!(response = as_serde!(response), url = url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
@ -334,7 +334,7 @@ macro_rules! route {
|
||||||
|
|
||||||
match response.error_for_status() {
|
match response.error_for_status() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let response = response.json().await?;
|
let response = read_response(response).await?;
|
||||||
debug!(response = as_serde!(response), url = $url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
|
debug!(response = as_serde!(response), url = $url, method = stringify!($method), call_id = as_debug!(call_id); "received API response");
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
errors::{Error, Result},
|
errors::{Error, Result},
|
||||||
event_stream::event_stream,
|
event_stream::event_stream,
|
||||||
|
helpers::read_response::read_response,
|
||||||
AddFilterRequest,
|
AddFilterRequest,
|
||||||
AddPushRequest,
|
AddPushRequest,
|
||||||
Data,
|
Data,
|
||||||
|
@ -164,7 +165,7 @@ impl Mastodon {
|
||||||
return Err(Error::Server(status.clone()));
|
return Err(Error::Server(status.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(response.json().await?)
|
Ok(read_response(response).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the user credentials
|
/// Update the user credentials
|
||||||
|
@ -181,7 +182,7 @@ impl Mastodon {
|
||||||
return Err(Error::Server(status.clone()));
|
return Err(Error::Server(status.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(response.json().await?)
|
Ok(read_response(response).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Post a new status to the account.
|
/// Post a new status to the account.
|
||||||
|
@ -310,7 +311,7 @@ impl Mastodon {
|
||||||
match response.error_for_status() {
|
match response.error_for_status() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let response = response.json().await?;
|
let response = read_response(response).await?;
|
||||||
debug!(status = as_debug!(status), response = as_serde!(response); "received API response");
|
debug!(status = as_debug!(status), response = as_serde!(response); "received API response");
|
||||||
Ok(response)
|
Ok(response)
|
||||||
},
|
},
|
||||||
|
@ -337,7 +338,7 @@ impl Mastodon {
|
||||||
match response.error_for_status() {
|
match response.error_for_status() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let response = response.json().await?;
|
let response = read_response(response).await?;
|
||||||
debug!(status = as_debug!(status), response = as_serde!(response); "received API response");
|
debug!(status = as_debug!(status), response = as_serde!(response); "received API response");
|
||||||
Ok(response)
|
Ok(response)
|
||||||
},
|
},
|
||||||
|
@ -392,13 +393,13 @@ impl Mastodon {
|
||||||
.append_pair("access_token", &self.data.token)
|
.append_pair("access_token", &self.data.token)
|
||||||
.append_pair("stream", "user");
|
.append_pair("stream", "user");
|
||||||
debug!(
|
debug!(
|
||||||
url = as_debug!(url), call_id = as_debug!(call_id);
|
url = url.as_str(), call_id = as_debug!(call_id);
|
||||||
"making user streaming API request"
|
"making user streaming API request"
|
||||||
);
|
);
|
||||||
let response = reqwest::get(url.as_str()).await?;
|
let response = reqwest::get(url.as_str()).await?;
|
||||||
let mut url: Url = response.url().as_str().parse()?;
|
let mut url: Url = response.url().as_str().parse()?;
|
||||||
info!(
|
info!(
|
||||||
url = as_debug!(url), call_id = as_debug!(call_id),
|
url = url.as_str(), call_id = as_debug!(call_id),
|
||||||
status = response.status().as_str();
|
status = response.status().as_str();
|
||||||
"received url from streaming API request"
|
"received url from streaming API request"
|
||||||
);
|
);
|
||||||
|
|
12
src/page.rs
12
src/page.rs
|
@ -1,5 +1,5 @@
|
||||||
use super::{Mastodon, Result};
|
use super::{Mastodon, Result};
|
||||||
use crate::{entities::itemsiter::ItemsIter, format_err};
|
use crate::{entities::itemsiter::ItemsIter, format_err, helpers::read_response::read_response};
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use hyper_old_types::header::{parsing, Link, RelationType};
|
use hyper_old_types::header::{parsing, Link, RelationType};
|
||||||
use log::{as_debug, as_serde, debug, error, trace};
|
use log::{as_debug, as_serde, debug, error, trace};
|
||||||
|
@ -21,7 +21,7 @@ macro_rules! pages {
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
url = as_debug!(url), method = "get",
|
url = url.as_str(), method = "get",
|
||||||
call_id = as_debug!(self.call_id),
|
call_id = as_debug!(self.call_id),
|
||||||
direction = stringify!($direction);
|
direction = stringify!($direction);
|
||||||
"making API request"
|
"making API request"
|
||||||
|
@ -31,7 +31,7 @@ macro_rules! pages {
|
||||||
match response.error_for_status() {
|
match response.error_for_status() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let (prev, next) = get_links(&response, self.call_id)?;
|
let (prev, next) = get_links(&response, self.call_id)?;
|
||||||
let response = response.json().await?;
|
let response = read_response(response).await?;
|
||||||
debug!(
|
debug!(
|
||||||
url = url, method = "get", next = as_debug!(next),
|
url = url, method = "get", next = as_debug!(next),
|
||||||
prev = as_debug!(prev), call_id = as_debug!(self.call_id),
|
prev = as_debug!(prev), call_id = as_debug!(self.call_id),
|
||||||
|
@ -108,7 +108,7 @@ impl<'a, T: for<'de> Deserialize<'de> + Serialize> Page<T> {
|
||||||
/// Create a new Page.
|
/// Create a new Page.
|
||||||
pub(crate) async fn new(mastodon: Mastodon, response: Response, call_id: Uuid) -> Result<Self> {
|
pub(crate) async fn new(mastodon: Mastodon, response: Response, call_id: Uuid) -> Result<Self> {
|
||||||
let (prev, next) = get_links(&response, call_id)?;
|
let (prev, next) = get_links(&response, call_id)?;
|
||||||
let initial_items = response.json().await?;
|
let initial_items = read_response(response).await?;
|
||||||
debug!(
|
debug!(
|
||||||
initial_items = as_serde!(initial_items), prev = as_debug!(prev),
|
initial_items = as_serde!(initial_items), prev = as_debug!(prev),
|
||||||
next = as_debug!(next), call_id = as_debug!(call_id);
|
next = as_debug!(next), call_id = as_debug!(call_id);
|
||||||
|
@ -171,7 +171,7 @@ fn get_links(response: &Response, call_id: Uuid) -> Result<(Option<Url>, Option<
|
||||||
if relations.contains(&RelationType::Next) {
|
if relations.contains(&RelationType::Next) {
|
||||||
// next = Some(Url::parse(value.link())?);
|
// next = Some(Url::parse(value.link())?);
|
||||||
next = if let Ok(url) = Url::parse(value.link()) {
|
next = if let Ok(url) = Url::parse(value.link()) {
|
||||||
trace!(next = as_debug!(url), call_id = as_debug!(call_id); "parsed link header");
|
trace!(next = url.as_str(), call_id = as_debug!(call_id); "parsed link header");
|
||||||
Some(url)
|
Some(url)
|
||||||
} else {
|
} else {
|
||||||
// HACK: url::ParseError::into isn't working for some reason.
|
// HACK: url::ParseError::into isn't working for some reason.
|
||||||
|
@ -181,7 +181,7 @@ fn get_links(response: &Response, call_id: Uuid) -> Result<(Option<Url>, Option<
|
||||||
|
|
||||||
if relations.contains(&RelationType::Prev) {
|
if relations.contains(&RelationType::Prev) {
|
||||||
prev = if let Ok(url) = Url::parse(value.link()) {
|
prev = if let Ok(url) = Url::parse(value.link()) {
|
||||||
trace!(prev = as_debug!(url), call_id = as_debug!(call_id); "parsed link header");
|
trace!(prev = url.as_str(), call_id = as_debug!(call_id); "parsed link header");
|
||||||
Some(url)
|
Some(url)
|
||||||
} else {
|
} else {
|
||||||
// HACK: url::ParseError::into isn't working for some reason.
|
// HACK: url::ParseError::into isn't working for some reason.
|
||||||
|
|
|
@ -7,6 +7,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
apps::{App, AppBuilder},
|
apps::{App, AppBuilder},
|
||||||
|
helpers::read_response::read_response,
|
||||||
log_serde,
|
log_serde,
|
||||||
scopes::Scopes,
|
scopes::Scopes,
|
||||||
Data,
|
Data,
|
||||||
|
@ -191,7 +192,7 @@ impl<'a> Registration<'a> {
|
||||||
|
|
||||||
match response.error_for_status() {
|
match response.error_for_status() {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
let response = response.json().await?;
|
let response = read_response(response).await?;
|
||||||
debug!(
|
debug!(
|
||||||
response = as_serde!(response), app = as_serde!(app),
|
response = as_serde!(response), app = as_serde!(app),
|
||||||
url = url, method = stringify!($method),
|
url = url, method = stringify!($method),
|
||||||
|
@ -351,7 +352,7 @@ impl Registered {
|
||||||
headers = log_serde!(response Headers);
|
headers = log_serde!(response Headers);
|
||||||
"received API response"
|
"received API response"
|
||||||
);
|
);
|
||||||
let token: AccessToken = response.json().await?;
|
let token: AccessToken = read_response(response).await?;
|
||||||
debug!(url = url, body = as_serde!(token); "parsed response body");
|
debug!(url = url, body = as_serde!(token); "parsed response body");
|
||||||
let data = self.registered(token.access_token);
|
let data = self.registered(token.access_token);
|
||||||
trace!(auth_data = as_serde!(data); "registered");
|
trace!(auth_data = as_serde!(data); "registered");
|
||||||
|
|
Loading…
Reference in New Issue