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.)