Rust Rocket async and Reqwest : Drop runtime

Hi everyone,

I am switching on Rocket 0.5, and I have an endpoint that dowload resource and serve it.
But now I can't call it because I get the error :

thread 'rocket-worker-thread' panicked at 'Cannot drop a runtime in a context where blocking is not allowed.
This happens when a runtime is dropped from within an asynchronous context.

I have understand that I maybe a problem with reqwest::blocking, so I convert it to reqwest::get.await, but that don't correct my problem...
So I've tried to call task::spawn_blocking, but I get lifetime problems and I've read that's not the best trick...

So could you explain to me where is the problem, and give me hints to resolve it please ?

EndPoint
#[get("/<product_type>/<agency_id>/<img_ref>/<url>")]
async fn get_or_download_resource(
    product_type: ProductTypeEntity,
    agency_id: &str,
    img_ref: &str,
    url: &str,
    use_cases_state: &State<UseCasesState>,
) -> Result<Custom<NamedFile>, Custom<Json<ErrorEntity>>> {
    trace!("Getting or downloading resource for agency '{}'", agency_id);

    match use_cases_state
        .inner()
        .resource
        .get_or_download_resource_from_encoded_url(product_type, agency_id, img_ref, url)
    {
        Ok(p) => {
            info!("Successfully get or download resource : {}", p.display());
            match NamedFile::open(p).await {
                Ok(f) => Ok(Custom(Status::Ok, f)),
                Err(e) => {
                    let message = format!("An internal error occured - {}", e);
                    error!("{}", message);
                    Err(Custom(
                        Status::InternalServerError,
                        Json(ErrorEntity::Technical(message)),
                    ))
                }
            }
        }
        Err(e) => {
            error!(
                "An error occured while getting or download resource : {}",
                e
            );
            Err(Custom(Status::InternalServerError, Json(e)))
        }
    }
}
UseCase
pub fn get_or_download_resource_from_encoded_url(
    &self,
    product_type: ProductTypeEntity,
    agency_id: &str,
    img_ref: &str,
    url: &str,
) -> Result<PathBuf, ErrorEntity> {
    let decoded_url = self.decode_url(url);
    let filename = format!(
        "{}-{}/{}-{}",
        product_type,
        agency_id,
        img_ref,
        self.extract_filename_from_url(&decoded_url)
    );
    let target_filepath = self.static_resources_port.get_image_filepath(&filename);
    if !target_filepath.exists() {
        self.static_resources_port
            .download_resource(decoded_url, &target_filepath)?;
    }
    Ok(target_filepath)
}

fn decode_url(&self, url: &str) -> String {
    match base64::decode_config(url.as_bytes(), base64::URL_SAFE) {
        Ok(decoded) => match String::from_utf8(decoded.clone()) {
            Ok(v) => v,
            Err(e) => {
                warn!("Cannot parse from UTF8 decoded url : {}", e);
                String::from_utf8_lossy(&decoded[..]).to_string()
            }
        },
        Err(e) => {
            warn!("Cannot decode url : {}", e);
            url.to_string()
        }
    }
}

fn extract_filename_from_url(&self, url: &str) -> String {
    let mut url_filename = url;
    if let Some(n) = url_filename.split('?').next() {
        url_filename = n;
    }
    if let Some(n) = url_filename.split('/').last() {
        url_filename = n;
    }
    url_filename.to_string()
Repository
fn get_image_filepath(&self, filename: &str) -> PathBuf {
    let mut filepath = self.dir_path.clone();
    filepath.push("resources");
    filepath.push(filename);
    filepath
}

fn download_resource(&self, uri: String, filepath: &Path) -> Result<(), ErrorEntity> {
    let mut response = match reqwest::blocking::get(&uri) {
        Ok(r) => r,
        Err(e) => {
            return Err(ErrorEntity::Technical(format!(
                "Error while downloading image '{}' : {}",
                uri, e
            )))
        }
    };
    if let Some(p) = filepath.parent() {
        if let Err(e) = std::fs::create_dir_all(p) {
            return Err(ErrorEntity::Technical(format!(
                "Cannot create dir '{}' : {}",
                filepath.display(),
                e
            )));
        }
    }
    let mut target_file = match std::fs::File::create(filepath) {
        Ok(f) => f,
        Err(e) => {
            return Err(ErrorEntity::Technical(format!(
                "Cannont create file '{}' : {}",
                filepath.display(),
                e
            )));
        }
    };
    match response.copy_to(&mut target_file) {
        Ok(_) => Ok(()),
        Err(e) => Err(ErrorEntity::Technical(format!(
            "Error while saving resource bytes to file '{}' : {}",
            filepath.display(),
            e
        ))),
    }
}

// async fn download_resource(&self, uri: String, filepath: &Path) -> Result<(), ErrorEntity> {
//     let response = match reqwest::get(&uri).await {
//         Ok(r) => r,
//         Err(e) => {
//             return Err(ErrorEntity::Technical(format!(
//                 "Error while downloading image '{}' : {}",
//                 uri, e
//             )))
//         }
//     };
//     if let Some(p) = filepath.parent() {
//         if let Err(e) = std::fs::create_dir_all(p) {
//             return Err(ErrorEntity::Technical(format!(
//                 "Cannot create dir '{}' : {}",
//                 filepath.display(),
//                 e
//             )));
//         }
//     }
//     let mut target_file = match std::fs::File::create(filepath) {
//         Ok(f) => f,
//         Err(e) => {
//             return Err(ErrorEntity::Technical(format!(
//                 "Cannont create file '{}' : {}",
//                 filepath.display(),
//                 e
//             )));
//         }
//     };
//     match response.text().await {
//         Ok(content) => {
//             if let Err(e) = std::io::copy(&mut content.as_bytes(), &mut target_file) {
//                 return Err(ErrorEntity::Technical(format!(
//                     "Error while saving resource bytes to file '{}' : {}",
//                     filepath.display(),
//                     e
//                 )));
//             }
//             Ok(())
//         }
//         Err(e) => Err(ErrorEntity::Technical(format!(
//             "Error while retrieving resource content : {}",
//             e
//         ))),
//     }

Could you help me please ?

You should switch to the async version of reqwest.

You say this didn't work for you, but I suspect you just did it wrong somehow. What did you try, more specifically?

Ok that's really weird.
The error came on my Windows laptop but not on my desktop WSL...
On my WSL, that's the file downloading that do something weird : the downloaded image is broken, but that's another subject

Can you show us the line of code where it fails?

Ok I don't know why but now on windows it works too.

For resource download problem : There is no error in the code, but the file where the resource was saved is not readable. I have maybe miss something when saving the image...

Code that download and save resource :

async fn download_resource(&self, uri: String, filepath: &Path) -> Result<(), ErrorEntity> {
    let response = match reqwest::get(&uri).await {
        Ok(r) => r,
        Err(e) => {
            return Err(ErrorEntity::Technical(format!(
                "Error while downloading image '{}' : {}",
                uri, e
            )))
        }
    };
    if !response.status().is_success() {
        return Err(ErrorEntity::Technical(format!("HTTP request status {} for url : {}", response.status().as_u16(), uri)));
    }
    if let Some(p) = filepath.parent() {
        if let Err(e) = std::fs::create_dir_all(p) {
            return Err(ErrorEntity::Technical(format!(
                "Cannot create dir '{}' : {}",
                filepath.display(),
                e
            )));
        }
    }
    let mut target_file = match std::fs::File::create(filepath) {
        Ok(f) => f,
        Err(e) => {
            return Err(ErrorEntity::Technical(format!(
                "Cannont create file '{}' : {}",
                filepath.display(),
                e
            )));
        }
    };
    match response.text().await {
        Ok(content) => {
            if let Err(e) = std::io::copy(&mut content.as_bytes(), &mut target_file) {
                return Err(ErrorEntity::Technical(format!(
                    "Error while saving resource bytes to file '{}' : {}",
                    filepath.display(),
                    e
                )));
            }
            Ok(())
        }
        Err(e) => Err(ErrorEntity::Technical(format!(
            "Error while retrieving resource content : {}",
            e
        ))),
    }
}

Okay, well, if it works now, that's great. I will add here that you should prefer to use Tokio's tokio::fs module rather than std::fs for the reasons explained in this blog post.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.