Rust, Actix 및 MongoDB를 사용하여 GitHub 프로필 보기를 계산하는 API 구축
조회수를 수집하는 아이디어는 간단합니다. 사용자가 수집된 통계를 보고 표시할 수 있는 github 페이지에 추적 이미지를 삽입해야 합니다. 수집된 통계를 표시하기 위해 shields 프로젝트를 사용합니다.
사용자 지정 데이터 공급자로 배지를 만들 수 있습니다.
전제 조건
먼저 개발을 시작하려면 적절한 도구를 설정해야 합니다. 최소한의 목록은 다음과 같습니다.
Rust Language - 응용 프로그램을 빌드하고 실행하기 위한 Rust SDK
VSCode - 응용 프로그램 소스 코드를 수정하는 편집기
Rust Analyzer - VSCode 확장
Rust로 더 나은 개발 환경을 제공하는 것
Docker Desktop - Docker 이미지 빌드 또는 사용
Docker는 또한 운영 체제에 데이터베이스 인스턴스를 설치하지 않고 데이터베이스 인스턴스가 필요한 경우 애플리케이션을 로컬에서 개발하는 데 유용합니다.
설치 프로젝트
이 cargo 명령으로 Rust 프로젝트를 만들어 봅시다:
cargo new counter
이제
VSCode
에서 프로젝트 폴더를 열고 HTTP API 개발에 필요한 종속성을 추가할 수 있습니다. 이 프로젝트에서 종속성은 다음과 같습니다.Actix - 널리 사용되는 Rust 웹 프레임워크
MongoDB - MongoDB용 Rust 드라이버
Serde - JSON 직렬화/역직렬화를 위한 Rust 라이브러리
Cargo.toml
파일을 편집하거나 프로젝트 디렉토리에서 다음 명령을 사용하여 이를 수행할 수 있습니다.cargo add actix-web mongodb serde
또한 보다 선언적인 직렬화를 위해
serde
매크로를 사용할 수 있도록 derive
기능 플래그를 수정해야 합니다.종속성 섹션은 다음과 같습니다.
[dependencies]
actix-web = "4.1.0"
mongodb = "2.3.0"
serde = { version = "1.0.140", features = ["derive"] }
데이터 베이스
코딩을 시작하기 전에 애플리케이션 데이터를 저장할
MongoDB
인스턴스가 필요합니다. DockerHub Registry의 공식 Docker 이미지를 사용하여 만들겠습니다.로컬 개발을 위한 Docker 컨테이너를 생성해 보겠습니다.
docker run -d --name local-mongo \
-p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=pass \
mongo:latest
완료되면 이 연결 문자열
mongodb://admin:pass@localhost:27017
을 사용하여 로컬 MongoDB에 연결할 수 있습니다.응용 프로그램이 데이터베이스에 액세스할 수 있도록 하려면 프로젝트의
config.toml
디렉터리에 있는 .cargo
파일에 연결 문자열을 추가해야 합니다.[env]
DATABASE_URL = "mongodb://admin:pass@localhost:27017"
MongoDB 컨테이너를 (재)시작하려면 이 명령이 필요할 수도 있습니다.
docker restart local-mongo
프로젝트 구조
이 프로젝트에서는 코드를 구성하는 두 개의 레이어가 있습니다. 첫 번째 계층은 MongoDB 컬렉션을 사용하여 GitHub 프로필 보기를 추가하고 계산하는 데 사용할 데이터 서비스를 나타냅니다. 두 번째 레이어는 보기를 추적하고 수집된 통계를 검색하기 위한 API 끝점을 노출합니다.
위에서 설명한 두 가지 기능을 사용하여 데이터 서비스를 만들어 봅시다.
// service.rs
// This structure represents a view event of a GitHub or Web page.
#[derive(Serialize, Deserialize)]
pub struct View {
// The unique tracker name of this view event.
name: String,
// The date time of this view event.
date: DateTime,
}
// The data service to add views and collect stats.
#[derive(Clone)]
pub struct ViewService {
collection: Collection<View>,
}
impl ViewService {
// Create an instance of the ViewService struct from a given db collection.
pub fn new(collection: Collection<View>) -> Self {
Self { collection }
}
// Register a view event in the storage.
pub async fn add_view<T: AsRef<str>>(&self, name: T) -> Result<InsertOneResult> {
let view = View {
name: name.as_ref().to_owned(),
date: DateTime::now(),
};
self.collection.insert_one(view, None).await
}
// Collect the view counts for a give unique tracker name.
pub async fn get_view_count<T: AsRef<str>>(&self, name: T) -> Result<u64> {
self.collection
.count_documents(doc! {"name": name.as_ref()}, None)
.await
}
}
API용 엔드포인트 레이어:
// endpoint.rs
// The `shields.io` custom endpoint contract.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Shield {
schema_version: i32,
label: String,
message: String,
cache_seconds: Option<u32>,
}
impl Shield {
pub fn new(label: String, message: String) -> Self {
Self {
schema_version: 1,
label,
message,
cache_seconds: Some(300),
}
}
}
#[derive(Deserialize)]
pub struct ViewParams {
name: String,
label: Option<String>,
}
#[derive(Deserialize)]
pub struct AddViewParams {
name: String,
}
// Count the view events for a given unique tracker name.
// The response is compatible with the `shields.io` project.
// GET /views?name=NAME&label=LABEL
#[get("/views")]
pub async fn get_view_count(app_state: Data<AppState>, query: Query<ViewParams>) -> impl Responder {
let ViewParams { name, label } = query.into_inner();
if name.is_empty() {
return HttpResponse::BadRequest().finish();
}
match app_state.view_service.get_view_count(name.as_str()).await {
Ok(count) => {
let label = label.unwrap_or_else(|| String::from("Views"));
let message = count.to_string();
let shield = Shield::new(label, message);
HttpResponse::Ok().json(shield)
}
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
// Render a tracking pixel and register a view in the storage.
// GET /tracker?name=NAME
#[get("/tracker")]
pub async fn add_view(app_state: Data<AppState>, query: Query<AddViewParams>) -> impl Responder {
// Statically load the svg pixel image from the external file during compilation.
// No need for escaping and other funny stuff.
const PIXEL: &str = include_str!("pixel.svg");
const SVG_MIME: &str = "image/svg+xml";
// Disable caching to prevent GitHub or any other proxy to cache the rendered image.
const CACHE_CONTROL: (&str, &str) = (
"Cache-Control",
"max-age=0, no-cache, no-store, must-revalidate",
);
let AddViewParams { name } = query.into_inner();
if name.is_empty() {
return HttpResponse::BadRequest().finish();
}
let _ = app_state.view_service.add_view(name).await;
HttpResponse::Ok()
.append_header(CACHE_CONTROL)
.content_type(SVG_MIME)
.body(PIXEL)
}
함께 정리해 보겠습니다.
// main.rs
// The settings to run an application instance.
struct Settings {
/// The web server port to run on.
port: u16,
/// The MongoDB database url.
database_url: String,
}
impl Settings {
// Create an instance of the Settings struct
// from the environment variables.
pub fn from_env() -> Self {
let port: u16 = env::var("PORT")
.expect("PORT expected")
.parse()
.expect("PORT must be a number");
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL expected");
Self { port, database_url }
}
}
// The shareable state for accessing
// across different parts of the application.
pub struct AppState {
view_service: ViewService,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Read the application settings from the env.
let Settings { port, database_url } = Settings::from_env();
// Create the database connection for the application.
let options = ClientOptions::parse(database_url).await.unwrap();
let client = Client::with_options(options).unwrap();
let db = client.database("counter_db");
let view_collection = db.collection::<View>("views");
// Initialize and start the web server.
HttpServer::new(move || {
// Create the shareable state for the application.
let view_service = ViewService::new(view_collection.clone());
let app_state = AppState { view_service };
// Create the application with the shareable state and
// wired-up API endpoints.
App::new()
.app_data(Data::new(app_state))
.service(endpoint::get_view_count)
.service(endpoint::add_view)
})
.bind(("0.0.0.0", port))?
.run()
.await
}
웹 서버를 실행하려면 애플리케이션을 빌드하고 시작하십시오.
cargo run
결론
따라서 이제 API 엔드포인트를 호출하고 자신만의 배지를 만들어 보기 통계를 표시할 수 있습니다.
재료:
이 튜토리얼이 도움이 되었기를 바랍니다.
Reference
이 문제에 관하여(Rust, Actix 및 MongoDB를 사용하여 GitHub 프로필 보기를 계산하는 API 구축), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/shadeglare/build-an-api-to-count-github-profile-views-with-rust-actix-and-mongodb-533k텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)