【问题标题】:Rust API working locally but not in dockerRust API 在本地工作,但不在 docker 中
【发布时间】:2022-07-01 04:43:08
【问题描述】:

我正在开发一个 rust 的示例 api。在本地一切正常。我正在使用 Windows 10 和这个版本的 rust and cargo。

rustc --version
rustc 1.60.0 (7737e0b5c 2022-04-04)
cargo --version
cargo 1.60.0 (d1fd9fe2c 2022-03-01)

当我尝试创建 docker 映像时会出现此问题。这是我的 dockerfile

FROM rust:1.60.0

# Copy local code to the container image.
WORKDIR /usr/src/app
COPY . .

# Install production dependencies and build a release artifact.
RUN cargo build --release

# Service must listen to $PORT environment variable.
# This default value facilitates local development.
ENV PORT 8080

# Run the web service on container startup.
CMD ["./target/release/simple_server.exe"]

错误发生在 RUN cargo build --realease,它显示这个错误:

#8 402.4    Compiling actix-router v0.5.0
#8 404.6 error[E0597]: `version_regex` does not live long enough
#8 404.6   --> /usr/local/cargo/registry/src/github.com-1ecc6299db9ec823/os_info-1.3.3/src/linux/file_release.rs:36:13
#8 404.6    |
#8 404.6 36 | /             version_regex
#8 404.6 37 | |                 .captures_iter(&file_content)
#8 404.6    | |                                             ^
#8 404.6    | |                                             |
#8 404.6    | |_____________________________________________borrowed value does not live long enough
#8 404.6    |                                               a temporary with access to the borrow is created here ...
#8 404.6 ...
#8 404.6 41 |           } else {
#8 404.6    |           -
#8 404.6    |           |
#8 404.6    |           `version_regex` dropped here while still borrowed
#8 404.6    |           ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `regex::CaptureMatches<'_, '_>`       
#8 404.6    |
#8 404.6    = note: the temporary is part of an expression at the end of a block;
#8 404.6            consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
#8 404.6 help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
#8 404.6    |
#8 404.6 36 ~             let x = version_regex
#8 404.6 37 |                 .captures_iter(&file_content)
#8 404.6 38 |                 .next()
#8 404.6 39 |                 .and_then(|c| c.get(1))
#8 404.6 40 ~                 .map(|v| v.as_str().trim_end().to_owned()); x
#8 404.6    |
#8 404.6
#8 404.7 For more information about this error, try `rustc --explain E0597`.
#8 404.7 error: could not compile `os_info` due to previous error
#8 404.7 warning: build failed, waiting for other jobs to finish...
#8 420.3 error: build failed

我一直试图在本地复制此错误,但从未发生过。我真的不明白我做错了什么,我还在学习 Rust。

源代码

货物.tml

[package]
name = "simple_server"
version = "0.1.0"
authors = ["AlexLeo99 <baker-plan-b@hotmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
mongodb = "0.9.2"
actix-web = "4"
actix-rt = "1"
bson = "0.14.0"
serde = "1.0.103"
futures = "0.3.4"
env_logger = "0.7"
dotenv = "0.15.0"
actix-cors = "0.2.0"
serde_json = "1.0"

main.rs

// External imports
use actix_cors::Cors;
use actix_web::{http, middleware, App, HttpServer};
use dotenv::dotenv;
use mongodb::{options::ClientOptions, Client};
use std::env;
use api_service::ApiService;

// External modules reference
mod api_router;
mod api_service;

// Api Service constructor
pub struct ServiceManager {
    api: ApiService,
}

// Api Servie Implementation
impl ServiceManager {
    pub fn new(api: ApiService) -> Self {
        ServiceManager { api }
    }
}

// Service Manager constructor
pub struct AppState {
    service_manager: ServiceManager,
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    // init env
    dotenv().ok();

    // init logger middleware
    env::set_var("RUST_LOG", "actix_web=debug,actix_server=info");
    env_logger::init();

    // Parse a connection string into an options struct.
    let database_url = env::var("DATABASE_URL").expect("DATABASE URL is not in .env file");
    let client_options = ClientOptions::parse(&database_url).unwrap();

    // Get the reference to Mongo DB
    let client = Client::with_options(client_options).unwrap();

    // get the reference to the Data Base
    let database_name = env::var("DATABASE_NAME").expect("DATABASE NAME is not in .env file");
    let db = client.database(&database_name);

    // get the reference to the Collection
    let user_collection_name = env::var("USER_COLLECTION_NAME").expect("COLLECTION NAME is not in .env file");
    let user_collection = db.collection(&user_collection_name);

    // Gte the server URL
    let server_url = env::var("SERVER_URL").expect("SERVER URL is not in .env file");

    // Start the server
    HttpServer::new(move || {
        let user_service_worker = ApiService::new(user_collection.clone());
        let service_manager = ServiceManager::new(user_service_worker);

        // cors
        let cors_middleware = Cors::new()
            .allowed_methods(vec!["GET", "POST", "DELETE", "PUT"])
            .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
            .allowed_header(http::header::CONTENT_TYPE)
            .max_age(3600)
            .finish();

        // Init http server
        App::new()
            .wrap(cors_middleware)
            .wrap(middleware::Logger::default())
            .data(AppState { service_manager })
            .configure(api_router::init)
    })
    .bind(server_url)?
    .run()
    .await
}

api_router/mod.rs

use crate::api_service::Data;
use actix_web::{delete, get, post, web, HttpResponse, Responder};

#[get("/get-all")]
async fn get_all_json(app_data: web::Data<crate::AppState>) -> impl Responder {
    let action = app_data.service_manager.api.get_json();
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[get("/get-by/{param}")]
async fn get_user_email(app_data: web::Data<crate::AppState>, param: web::Path<String>) -> impl Responder {
    let action = app_data.service_manager.api.get_by(&param);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[post("/add")]
async fn add_user(app_data: web::Data<crate::AppState>, data: web::Json<Data>) -> impl Responder {
    let action = app_data.service_manager.api.create(&data);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result.inserted_id),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[post("/update/{param}")]
async fn update_user(app_data: web::Data<crate::AppState>, data: web::Json<Data>, param: web::Path<String>) -> impl Responder {
    let action = app_data.service_manager.api.update(&data, &param);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result.modified_count),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[delete("/delete")]
async fn delete_user(app_data: web::Data<crate::AppState>, data: web::Json<Data>) -> impl Responder {
    let action = app_data.service_manager.api.delete(&data.title);
    let result = web::block(move || action).await;
    match result {
        Ok(result) => HttpResponse::Ok().json(result.deleted_count),
        Err(e) => {
            println!("Error while getting, {:?}", e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

// function that will be called on new Application to configure routes for this module
pub fn init(cfg: &mut web::ServiceConfig) {
            // cfg.service(get_user_email);
            // cfg.service(add_user);
            // cfg.service(update_user);
            // cfg.service(delete_user);
    cfg.service(get_all_json);
    }

api_service/mod.rs

// External imports
use bson::{doc, Document};
use mongodb::results::{DeleteResult, UpdateResult, InsertOneResult};
use mongodb::{error::Error, Collection};
use serde::{Deserialize, Serialize};
// External constructors
extern crate serde;
extern crate serde_json;

// Estructure data for DB
#[derive(Debug, Serialize, Deserialize)]
pub struct Data {
    pub title: String,
    pub author: String,
}

// Reference colection clone
#[derive(Clone)]
pub struct ApiService {
    collection: Collection,
}

// Transform data to mongo db document
fn data_to_document(data: &Data) -> Document {
    let Data {
        title,
        author,
    } = data;
    doc! {
        "title": title,
        "author": author,
    }
}

// Functions with queries to Mongo
impl ApiService {
    pub fn new(collection: Collection) -> ApiService {
        ApiService { collection }
    }

    // Insert data to Mongo DB
    pub fn create(&self, _data:&Data) -> Result<InsertOneResult, Error> {
        self.collection.insert_one(data_to_document(_data), None)
    }

    // Update an existing document 
    pub fn update(&self, _data:&Data, _param: &String) -> Result<UpdateResult, Error> {
        let object_param = bson::oid::ObjectId::with_string(_param).unwrap();
        self.collection.update_one(doc! { "_id": object_param }, data_to_document(_data), None)
    }

    // Delete some document
    pub fn delete(&self, _title: &String) -> Result<DeleteResult, Error> {
        self.collection.delete_one(doc! { "title": _title }, None)
    }

    // Get all documents
    pub fn get_json(&self) -> std::result::Result<std::vec::Vec<bson::ordered::OrderedDocument>, mongodb::error::Error> {
        let cursor = self.collection.find(None, None).ok().expect("Failed to execute find.");
        let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();
        Ok(docs)
    }

    // Get documents with query
    pub fn get_by(&self, param: &String) -> std::result::Result<std::vec::Vec<bson::ordered::OrderedDocument>, mongodb::error::Error> {
        let cursor = self.collection.find(doc! { "author": { "$regex": param } }, None).ok().expect("Failed to execute find.");
        let docs: Vec<_> = cursor.map(|doc| doc.unwrap()).collect();
        let _serialized = serde_json::to_string(&docs).unwrap();
        Ok(docs)
    }
}

【问题讨论】:

  • 你试过在 Linux 机器上复制吗? Docker 映像正在运行 Linux,错误发生在名为 os_info 的 crate 中,因此它在 Windows 和 Linux 机器上的行为可能有所不同似乎是合乎逻辑的……
  • 这确实是os_info 的问题 - 检查自己,Linux 主机 - 构建中断,交叉编译到 Windows - 构建成功。试图找到可能的解决方案来构建答案。
  • 不,实际上,我没有。但我真的不知道代码会如何变化。我的意思是,据我所知,没有任何依赖于操作系统的库。我的 Cargo.toml 上是否有任何在 linux 上表现不同的库?
  • 不在 Cargo.toml 中,但在传递依赖中。

标签: docker rust dockerfile rust-cargo


【解决方案1】:

错误是由于mongodb 0.9.2 取决于os_info 1.3.3,它无法在 Linux 上编译。解决这个问题的正确方法是升级mongodb 版本——0.9.2 相当老了;撰写本文时的最新版本是2.1.0,它使用os_info 3,没有这个问题。


但是,如果由于某种原因不希望进行此更新(例如,如果由于更改的 API 导致的更改太大),并且 crate 不会发布到 crates.io,则可以利用Cargo.toml [patch] 部分:

  • os_info 的源代码复制到某个本地目录中。它可以在$USER_DIR/cargo/registry/src/github.com-$HASH/os_info-1.3.3 中找到(根组件是主目录,例如C:/Users/username;哈希可以不同)。
  • 将以下条目添加到Cargo.toml
[patch.crates-io]
os_info = { path = "path/to/os_info" }
  • 在原始编译器错误之后,从第 34 行开始更改本地副本中的文件 src/linux/file_release.rs。一种可能的更改是使用编译器建议。另一个(可能更容易)是将变量 version_regex 内联到以下表达式中;之后,整个区块将如下所示:
let version = if !release_info.version_regex.is_empty() {
    Regex::new(release_info.version_regex)
        .unwrap()
        .captures_iter(&file_content)
        .next()
        .and_then(|c| c.get(1))
        .map(|v| v.as_str().trim_end().to_owned())
} else {
    Some(file_content.trim_end().to_string())
}

之后,您的代码应该可以编译了。

【讨论】:

  • 我更新了 mongodb 的版本,一切正常!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-18
  • 2021-05-15
  • 2013-01-27
  • 1970-01-01
  • 2017-08-01
  • 2021-07-22
  • 1970-01-01
相关资源
最近更新 更多