I'm having trouble getting the reqwest crate to perform a bunch of async requests while reusing the same client. If I don't use a client and just use the provided get
interface, everything works fine. Essentially I'm just fetching a list of items and then using async to fetch everything in the list.
However when I pass the client to my get_object
closure the compiler complains the reference requires a static lifetime and while outlive 'client'.
How do I successfully annotate this lifetime? Or is there a better way to accomplish what I want?
extern crate futures; // 0.3.1
extern crate reqwest; // 0.10.1
extern crate serde; // 1.0.104
extern crate serde_json; // 1.0.45
extern crate tokio; // 0.2.10
use serde::Deserialize;
use tokio::task;
#[derive(Debug, Deserialize)]
struct Item {
name: Option<String>,
id: Option<u64>,
}
pub trait ApiObject: Send {
type Uri;
fn from_json(text: &str) -> Result<Self, &'static str>
where
Self: Sized;
fn url(uri: Self::Uri) -> String;
}
impl ApiObject for Item {
type Uri = u64;
fn from_json(text: &str) -> Result<Item, &'static str> {
match serde_json::from_str::<Item>(text) {
Result::Ok(val) => Ok(val),
Result::Err(err) => match err.classify() {
serde_json::error::Category::Data => Err("json input semantically incorrect"),
serde_json::error::Category::Io => Err("failure to read/write bytes to io stream"),
serde_json::error::Category::Syntax => Err("json input not syntactically valid"),
serde_json::error::Category::Eof => Err("json data ended unexpectedly"),
},
}
}
fn url(uri: Self::Uri) -> String {
format!("http://cats.com/items/{}.json", uri.to_string())
}
}
/// Access point for any object inside the hybrid api
pub async fn get_object<O: 'static + ApiObject>(
uri: O::Uri,
client: &reqwest::Client,
) -> Result<O, &'static str> {
let url = O::url(uri);
let res = client.get(&url).send().await.unwrap();
match res.status() {
reqwest::StatusCode::OK => {
let text = res.text().await.unwrap();
task::spawn_blocking(move || to_struct(&text))
.await
.unwrap()
}
_ => Err("Request failed"),
}
}
#[tokio::main]
async fn main() {
let client = reqwest::Client::builder().build().unwrap();
let top: Vec<u64> = vec![1, 2, 3, 4, 5];
let futures: Vec<_> = top
.iter()
.map(|uri| get_object::<Item>(*uri, &client))
.collect();
let handles: Vec<_> = futures.into_iter().map(tokio::task::spawn).collect();
let results = futures::future::join_all(handles).await;
for result in results {
let x = result.unwrap().unwrap();
println!("{:#?}", x.name.unwrap());
}
}
fn to_struct<T: ApiObject>(text: &str) -> Result<T, &'static str> {
T::from_json(text)
}
The error message:
error[E0597]: `client` does not live long enough
--> src/main.rs:73:46
|
73 | .map(|uri| get_object::<Item>(*uri, &client))
| ----- --------------------------^^^^^^-
| | | |
| | | borrowed value does not live long enough
| | returning this value requires that `client` is borrowed for `'static`
| value captured here
...
84 | }
| - `client` dropped here while still borrowed
It differs from How can I perform parallel asynchronous HTTP GET requests with reqwest? because I'm using a closure to pass the client in — using an async move
block did not seem to help me