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를 설정합니다.App
이 HttpServer
에 전송되어 전송 연결을 탐지한다.서비스 구성 만들기
보시다시피, 현재 우리는 이미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
함수는 필터(모든 내용 반환)와 간단한 정렬 근거가 없음 idcursor
으로 되돌아오는 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를 완성할 때가 되었습니다.두 개의 의존항인 chrono
과 serde
을 추가하면 첫 번째는 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,
}
NewLog
은 derive(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
을 채웁니다.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를 만드는 것은 그리 복잡하지 않습니다.물론 위의 예는 매우 간단하지만, 나는 이것이 녹이 슬는 것에 익숙하지 않아도 좋은 출발점이라고 생각한다.
만약 당신이 이 문장을 좋아하거나 좋아하지 않는다면, 혹은 무슨 문제가 있으면, 평론을 남겨 주십시오.
다음까지 읽어주셔서 감사합니다!
Reference
이 문제에 관하여(Rust+Actix+CosmosDB(MongoDB) 튜토리얼api.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/jbarszczewski/rust-actix-cosmosdb-mongodb-tutorial-api-17i5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)