Rust+Actix+CosmosDB(MongoDB) 튜토리얼api.


간단한 소개
내 프로젝트에서 일할 때, 나는 간단한 로그 API를 만들기로 결정했다. Rust는 신기술을 배우는 완벽한 선택인 것 같다. Azure CosmosDB도 마찬가지다. 현재 무료tier를 제공하여, 학습과 소규모 개인 프로젝트에 매우 적합하다.
나는 이 강좌가 초보자rustaceans(내가 그중의 하나)의 좋은 출발점이라고 생각하지만, 나는 네가 기초 지식을 알고 있다고 가정한다.공식 rustlings tutorial을 통과할 것을 강력히 건의합니다.
"""최종""코드는 내 github repo에서 찾을 수 있습니다."

"안녕하세요, Rustacean 녀석!"
우선, 새 프로젝트 디렉터리를 만들거나, 새 프로젝트 디렉터리를 만들고, cargo init을 실행하거나, cargo new {project-name}을 사용하여 디렉터리를 만듭니다.준비가 되면 선택한 편집기를 엽니다. (여기 VS 코드는 공식적인 'Rust' 확장과 'cargo' 를 가지고 있습니다. 의존 항목과 동기화하는 데 도움이 됩니다.) 시작합시다.
간단한 http 서버를 만드는 것부터 시작할 것입니다. 이 서버는 클래식greating을 되돌려줍니다.Cargo.toml을 열고 두 개의 새 종속성을 추가합니다.
[dependencies]
actix-rt = "1.1.1"
actix-web = "2.0"
주의: 우선 이 강좌에서 사용한 버전을 사용하고 업데이트하십시오.나는 판자 상자에 파괴적인 변화가 거의 일어나지 않았기 때문에 먼저 프로그램을 실행하는 것이 더욱 안전하다는 것을 발견했다.
그리고main의 코드를 바꿉니다.다음 예는 다음과 같습니다.
use actix_web::{web, App, HttpServer, Responder};
use std::env;

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    env::set_var("RUST_LOG", "actix_web=debug");
    HttpServer::new(|| App::new().route("/", web::get().to(hello)))
        .bind("127.0.0.1:8000")?
        .run()
        .await
}

async fn hello() -> impl Responder {
    format!("Hello fellow Rustacean!")
}

이렇게!이제 cargo run만 있으면 브라우저에서 127.0.0.1:8000으로 이동할 수 있습니다.
우리가 여기서 무엇을 했는지 빨리 봅시다.
  • #[actix_rt::main]은 우리의 주요 비동기 함수를 actix가 실행될 때 실행하는 것으로 표시합니다.
  • "RUST_LOG"에서 오류를 출력하는 기록기에 actix를 설정합니다.
  • 에 등록 요청 처리 프로그램이 있는 새로운 AppHttpServer에 전송되어 전송 연결을 탐지한다.

  • 서비스 구성 만들기
    보시다시피, 현재 우리는 이미main function에 우리의 노선을 등록했습니다.이 강좌에서 우리는 자원이 많지 않지만 가장 좋은 방법은 더욱 깨끗한 구조를 사용하는 것이다.새 파일 생성: src\logs_handlers\mod.rs 및 코드 추가:
    use actix_web::{web, Responder};
    
    pub fn scoped_config(cfg: &mut web::ServiceConfig) {
        cfg.service(
            web::resource("/logs")
                .route(web::get().to(get_logs))
                .route(web::post().to(add_log)),
        );
    }
    
    async fn get_logs() -> impl Responder {
        format!("Not yet implemented!")
    }
    
    async fn add_log() -> impl Responder {
        format!("Not yet implemented!")
    }
    
    
    scoped_config() 함수는 저희 서비스에 로그 자원 설정을 등록하는 것을 책임집니다.이것은 우리가 모든 자원을 위해 여러 개의 모듈을 만들고 main 함수에서 모든 자원을 호출할 수 있다는 것을 의미한다.그러면 App 생성기 코드를 수정하여 다음과 같은 이점을 얻을 수 있습니다.
    App::new().service(web::scope("/api").configure(logs_handlers::scoped_config))
    
    새로 만든 모듈을 가져오고 추가하는 것을 잊지 마십시오
    mod logs_handlers;
    
    use블록 아래에 있습니다.
    현재 우리는 루트 api/logs의 GET와POST 방법을 처리하기 위해api를 설정했습니다.해봐!

    CosmosDB/MongoDB에 연결
    이 강좌에서, 나는 무료의Azure CosmosDB 데이터베이스와MongodB API를 사용하지만, 당연히 선택권은 너에게 있다.db 클라이언트를 만들고 데이터를 되돌려줍니다.API 원형을 제작할 때 프로세서에서 클라이언트를 수동으로 만듭니다.이것은 매우 비효율적인 방법이다. 반대로 우리는 몬goDB 클라이언트 캐비닛의 클라이언트 탱크를 이용하여 구축하고 응용 프로그램이 시작될 때 설정할 수 있다.Cargo.toml에 MongoDB 종속성 추가
    bson = "1.0.0"
    futures = "0.3.5"
    mongodb = "1.0.0"
    
    main.rs을 열고 가져오기 모듈을 추가합니다.
    use mongodb::{options::ClientOptions, Client};
    use std::sync::*;
    
    그런 다음 다음과 같이 주 함수를 수정합니다.
    #[actix_rt::main]
    async fn main() -> std::io::Result<()> {
        env::set_var("RUST_LOG", "actix_web=debug");
        let mut client_options = ClientOptions::parse("mongodb://free-tier-db:yfQtNbXyW2h9HUOOplCeHgjzzbJMnfMQn2BZuzAkw5gv0uBkqbdbQPdnQ98e6UtS5Z3p1ZrG4rgkmEKBURNgwg==@free-tier-db.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@free-tier-db@").await.unwrap();
        client_options.app_name = Some("PlantApi".to_string());
        let client = web::Data::new(Mutex::new(Client::with_options(client_options).unwrap()));
        HttpServer::new(move || {
            App::new()
                .app_data(client.clone())
                .service(web::scope("/api").configure(logs_handlers::scoped_config))
        })
        .bind("127.0.0.1:8000")?
        .run()
        .await
    }
    
    참고: 연결 문자열을 사용자 것으로 바꿉니다.걱정하지 마십시오. 최종 버전에서는 코드에 연결 문자열을 남기지 않습니다.
    우리의 새 코드는 라인의 안전을 확보하기 위해 몬고 DB 클라이언트를 만들었습니다. 몬고 DB 클라이언트는 처리 프로그램에서 사용할 수 있도록 상호 배척 대상에 포장되어 있습니다.

    로그 가져오기
    현재 우리는 데이터베이스에 연결하여 그것을 이용할 준비가 되어 있다.logs_handlers/mod.rs으로 이동하여 몇 개의 모듈을 더 가져오십시오.
    use actix_web::{web, HttpResponse, Responder};
    use bson::{doc, Bson};
    use futures::stream::StreamExt;
    use mongodb::{options::FindOptions, Client};
    use std::sync::Mutex;
    
    const MONGO_DB: &'static str = "iotPlantDB";
    const MONGO_COLL_LOGS: &'static str = "logs";
    
    ...// no changes in  scoped_config
    
    async fn get_logs(data: web::Data<Mutex<Client>>) -> impl Responder {
        let logs_collection = data
            .lock()
            .unwrap()
            .database(MONGO_DB)
            .collection(MONGO_COLL_LOGS);
    
        let filter = doc! {};
        let find_options = FindOptions::builder().sort(doc! { "_id": -1}).build();
        let mut cursor = logs_collection.find(filter, find_options).await.unwrap();
    
        let mut results = Vec::new();
        while let Some(result) = cursor.next().await {
            match result {
                Ok(document) => {
                    results.push(document);
                }
                _ => {
                    return HttpResponse::InternalServerError().finish();
                }
            }
        }
        HttpResponse::Ok().json(results)
    }
    
    ... // no changes in add_log
    
    자, 우리가 여기서 뭘 했는지 봅시다.
  • 데이터베이스와 집합 이름을 저장하기 위해 두 개의 상수를 만들었습니다.
  • 우리의 get_logs 함수는 응용 프로그램 데이터를 받아들인다(main 함수에 설정된 MongoDB 클라이언트).
  • 로그 컬렉션을 처리하려면 클라이언트를 사용합니다.
  • find 함수는 필터(모든 내용 반환)와 간단한 정렬 근거가 없음 id
  • 우리는 cursor으로 되돌아오는 find의 교체 결과를 사용하고 전송된 문서로 결과 벡터를 채운 다음에 json 형식으로 되돌아옵니다.
  • 현재 데이터베이스에 데이터가 있는지 확인하고 첫 번째 호출을 준비합니다.테스트 데이터와 add_log 프로세서에서 사용할 데이터는 다음과 같습니다.
    {
        "_id": {
            "$oid": "5ee3bb1f00bc6d3b007b79ca"
        },
        "deviceId": "mock_device-01",
        "message": "test message",
        "createdOn": {
            "$date": "2020-06-12T17:27:59.404Z"
        }
    }
    
    POST 프로세서를 실행하기 전에 변경 사항을 하나 더 작성하겠습니다.우리는 연결 문자열을 코드에서 옮겨야 한다.CONNECTION_STRING_LOGS이라는 환경 변수로 저장한 다음 main.rs에서 클라이언트 옵션 작성을 담당하는 줄을 다음과 같이 바꿀 수 있습니다.
     let mongo_url = env::var("CONNECTION_STRING_LOGS").unwrap();
     let mut client_options = ClientOptions::parse(&mongo_url).await.unwrap();
    
    더 나은 해결 방안!

    로그 추가add_log 프로세서로 우리api를 완성할 때가 되었습니다.두 개의 의존항인 chronoserde을 추가하면 첫 번째는 DateTime을 사용하는 데 도움을 줄 것입니다. 후자는 가장 유행하는 서열화/반서열화 박스입니다.최종 종속성 목록은 다음과 같습니다.
    [dependencies]
    actix-rt = "1.1.1"
    actix-web = "2.0"
    bson = "1.0.0"
    chrono = "0.4.11"
    futures = "0.3.5"
    mongodb = "1.0.0"
    serde = { version = "1.0", features = ["derive"] }
    
    logs_handlers/mod.rs으로 돌아가서 새로 추가된 모듈을 가져오고 새 로그에 struct를 추가합니다.
    use chrono::prelude::*;
    use serde::Deserialize;
    
    #[derive(Deserialize)]
    pub struct NewLog {
        pub id: String,
        pub message: String,
    }
    
    NewLogderive(Deserialize)의 특징을 지니고 있다. 이는 반서열화로 전송된 게시물의 본문에 사용되기 때문이다.장치/원본 id와 기록할 메시지만 있으면 됩니다. 시간 스탬프는 프로세서 함수에 생성되고,MongodB 대상 id는 데이터베이스에 생성됩니다.add_log 기능을 다음으로 교체:
    async fn add_log(data: web::Data<Mutex<Client>>, new_log: web::Json<NewLog>) -> impl Responder {
        let logs_collection = data
            .lock()
            .unwrap()
            .database(MONGO_DB)
            .collection(MONGO_COLL_LOGS);
    
        match logs_collection.insert_one(doc! {"deviceId": &new_log.id, "message": &new_log.message, "createdOn": Bson::DateTime(Utc::now())}, None).await {
            Ok(db_result) => {
                if let Some(new_id) = db_result.inserted_id.as_object_id() {
                    println!("New document inserted with id {}", new_id);   
                }
                return HttpResponse::Created().json(db_result.inserted_id)
            }
            Err(err) =>
            {
                println!("Failed! {}", err);
                return HttpResponse::InternalServerError().finish()
            }
        }
    }
    
    get_logs과 마찬가지로 이 함수는 응용 프로그램 데이터에 저장된mongo 클라이언트를 사용하여 로그 집합을 처리합니다.이전에 생성한 new_log 유형의 추가 매개 변수 NewLog을 주의하십시오.바디가 구조(이름과 값 유형)와 일치하면 제대로 반서열화되어 사용할 준비가 됩니다.이 함수의 역할은 다음과 같습니다.
  • new_log 데이터를 사용하여 문서를 동적으로 만들고 현재 UTC 날짜와 시간으로 createdOn을 채웁니다.
  • 검사 결과가 성공하면 새 문서 id를 되돌려줍니다.
  • 우리 망했어!두 개의 간단한 함수는 들어오는 GET와 POST 요청을 처리할 수 있습니다.애플리케이션을 실행하고 새 로그를 추가하여 테스트합니다.
    curl --location --request POST 'localhost:8000/api/logs' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "id":"tutorial-client",
        "message":"I'\''m a Rustacean!"
    }'
    

    옵션 get_logs 코드
    이것은 간단한 예시이기 때문에, 우리의 get_logs 함수는 받은 형식으로 문서를 되돌려줍니다.그러나 만약 우리가 결과를 되돌리기 전에 결과에 대해 몇 가지 조작을 하고 싶다면?코드를 조금만 수정하면 문서를 쉽게 반서열화할 수 있습니다(모델과 일치하는지 확인).
    우리는 몇 가지 수입을 늘렸다.
    use bson::{doc, oid::ObjectId, Bson, UtcDateTime};
    use serde::{Deserialize, Serialize};
    
    Log 패브릭을 NewLog과 다르게 지정합니다.
    #[derive(Deserialize, Debug)]
    #[serde(rename_all = "camelCase")]
    pub struct Log {    
        #[serde(rename = "_id")]
        pub id: ObjectId,
        #[serde(rename = "deviceId")]   
        pub device_id: String,  
        pub message: String,
        pub timestamp: UtcDateTime,
    }
    
    수신된 문서를 결과 벡터로 푸시하는 행 위에 있는 위 모델을 사용하여 반정렬합니다.
    let log: Log = bson::from_bson(Bson::Document(document)).unwrap();
    

    결론
    우리는 이 강좌가 끝날 때 도착한다.보시다시피 Rust에서 API를 만드는 것은 그리 복잡하지 않습니다.물론 위의 예는 매우 간단하지만, 나는 이것이 녹이 슬는 것에 익숙하지 않아도 좋은 출발점이라고 생각한다.
    만약 당신이 이 문장을 좋아하거나 좋아하지 않는다면, 혹은 무슨 문제가 있으면, 평론을 남겨 주십시오.
    다음까지 읽어주셔서 감사합니다!

    좋은 웹페이지 즐겨찾기