이 글은 Acticx Web의 공식문서에 있는 내용을 따라하여 웹 앱 제작을 연습하는 글입니다.
애플리케이션의 State는 같은 scope 내의 모든 route와 resource 내에서 공유된다. State는
web::Data\<T\>
를 통해 접근할 수 있다.T
는 state의 타입이다. State는 middleware에서도 접근할 수 있다.
struct AppState {
app_name: String,
}
#[get("/")] // get 방식
async fn hello(data: web::Data<AppState>) -> String {
let app_name = &data.app_name;
format!("Hello {app_name}!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(web::Data::new(AppState {
app_name: String::from("Actix Web"),
}))
.service(hello)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
String 타입의 app_name이라는 필드를 가지는 AppState라는 구조체를 만든다. hello 함수는 web::Data 타입의 data란 매개변수를 가진다. web::Data를 통해서 AppState에 접근할 수 있다. 앱을 시작할 때 state를 전달한다.
localhost:8080에 접속하면 hello에서 AppState를 넘겨받아 Hello Actix Web
를 출력하는 것을 볼 수 있다.
HttpServer
는 애플리케이션 인스턴스가 아닌 애플리케이션 팩토리를 허용한다.HttpServer
는 각 쓰레드마다 애플리케이션 인스턴스를 구성한다. 그렇기에 애플리케이션 데이터를 여러번 구성해야 한다. 서로 다른 쓰레드 간에 데이터를 공유하기 위해서는Send
+Sync
와 같은, 공유 가능한 오브젝트를 사용해야 한다.
이해가 잘 안된다. HttpServer
가 쓰레드 당 하나의 인스턴스만 생성한다는 것인가? 그리고 쓰레드마다 인스턴스를 생성하기 때문에 애플리케이션 데이터는 여러번 생성된다는 뜻인 것일까...
내부적으로,
web::Data
는Arc
를 사용한다. 두 개의Arc
가 만들어지는 걸 피하기 위해서,App::app_data()
를 이용해서 데이터를 등록하기 전에 먼저 데이터를 만들어야 한다.
web::Data
는 Arc
를 사용하는데, 두개의 Arc
가 생성되면 안 되는 것 같다. Arc
가 무엇인지 찾아보니 Atomic Reference Counting, 줄여서 Arc라고 불리는 타입인 것 같다. Arc
에 대해선 추후에 더 자세히 공부해야겠다.
use actix_web::{get, web, App, HttpServer };
use std::sync::Mutex;
struct AppStateWithCounter {
counter: Mutex<i32>, // 쓰레드 간에 안전하게 변경하기 위해 Mutex 사용
}
#[get("/")] // get 방식
async fn index(data: web::Data<AppStateWithCounter>) -> String {
let mut counter = data.counter.lock().unwrap(); // counter의 MutexGuard를 획득
*counter += 1; // MutexGuard 내부의 counter에 접근
format!("Request number: {counter}")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// HttpServer::new closure 외부에서 만들어진 web::Data
let counter = web::Data::new(AppStateWithCounter {
counter: Mutex::new(0),
});
HttpServer::new(move || {
// closure 내부로 counter 이동
App::new()
.app_data(counter.clone()) // 만들어진 데이터를 등록
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
localhost:8080에 접속해보면 새로고침을 할 때마다 계속 숫자가 증가하는 것을 볼 수 있다.
공식문서에서는 이렇게 얘기하고 있다.
핵심 요소
- closure 내부에서 초기화 되어
HttpServer::new
로 전달되는 State는 worker thread에 지역적이며, 수정될 경우 de-sync된다.- 전역적으로 공유되는 state를 만들고 싶을 경우,
HttpServer::new
에 전달되는 closure의 외부에서 만들어져서 이동/복제 되어야 한다.
솔직히 이해 안되는 것들이 투성이다. Rust와 Actix를 계속 공부하면서 깨져나간 뒤에 다시 이 글을 볼 때 이해할 수 있을 것 같다.