Rust 웹 프로그래밍과 프레임워크 (24)
1
소개
안녕하세요? 오늘은 Rust 웹 프로그래밍과 프레임워크에 대해 자세히 알아보겠습니다. 웹 개발은 현대 소프트웨어 엔지니어링의 핵심 분야 중 하나입니다. 우리는 웹 애플리케이션을 통해 정보를 공유하고, 서비스를 제공하며, 비즈니스를 수행합니다. 이렇게 중요한 웹 개발 영역에서 Rust 프로그래밍 언어가 주목받는 이유는 무엇일까요?
Rust는 시스템 프로그래밍 언어로 잘 알려져 있지만, 웹 개발에도 적합한 특성을 가지고 있습니다. 우선, Rust는 메모리 안전성을 보장하면서도 성능이 뛰어납니다. 이를 통해 보안성 높고 효율적인 웹 애플리케이션을 구축할 수 있습니다. 또한, Rust의 동시성 모델은 웹 서버와 같이 동시에 많은 요청을 처리해야 하는 환경에 적합합니다. 이 외에도 Rust는 패키지 관리, 크로스 플랫폼 지원, 우수한 도구 생태계 등의 장점을 제공합니다.
이번 장에서는 Rust를 이용한 웹 프로그래밍의 기초부터 프레임워크 활용까지 살펴볼 것입니다. 이를 통해 보안성과 성능을 모두 잡을 수 있는 Rust 웹 개발 능력을 기를 수 있을 것입니다. 지금부터 함께 시작해볼까요?
Rust 웹 프로그래밍 기초
웹 프로그래밍을 시작하기 위해서는 HTTP(Hypertext Transfer Protocol) 프로토콜에 대한 이해가 필수적입니다. HTTP는 웹 상에서 데이터를 주고받는 규약으로, 클라이언트와 서버 간의 요청과 응답으로 이루어집니다. 클라이언트가 서버에 요청을 보내면, 서버는 적절한 응답을 제공합니다.
Rust에서 HTTP 통신을 구현하기 위해서는 표준 라이브러리인 std::net 모듈을 사용할 수 있습니다. 이 모듈에는 TCP/UDP 소켓 프로그래밍을 위한 기능이 포함되어 있습니다. 아래 예제는 간단한 HTTP 서버를 구현한 것입니다.
use std::net::TcpListener;
use std::io::prelude::*;
fn main() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
for stream in listener.incoming() {
let mut stream = stream.unwrap();
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
let response = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!";
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
}
이 예제에서는 TcpListener를 생성하여 127.0.0.1:8000 주소의 포트를 열고, 클라이언트 요청을 기다립니다. 요청이 오면 stream.read(&mut buffer)를 통해 요청 본문을 읽어들이고, 간단한 "Hello World!" 응답을 stream.write를 통해 보냅니다.
이처럼 Rust의 표준 라이브러리를 사용하면 저수준의 네트워크 프로그래밍이 가능하지만, 실제 웹 애플리케이션 개발에는 부족한 점이 있습니다. HTTP 메시지 파싱, 라우팅, 미들웨어, 세션 관리 등의 기능이 필요한데, 이를 직접 구현하기에는 많은 노력이 듭니다.
이러한 문제를 해결하기 위해 Rust 생태계에서는 웹 프레임워크를 제공하고 있습니다. 대표적인 웹 프레임워크로는 Actix Web, Rocket, Warp 등이 있습니다. 이번에는 가장 인기 있는 Actix Web 프레임워크에 대해 자세히 알아보겠습니다.
Actix Web 프레임워크 시작하기
Actix Web은 액터 모델을 기반으로 한 고성능 웹 프레임워크입니다. 액터 모델은 병렬 처리를 위한 설계 패턴으로, 효율적인 동시성 프로그래밍을 돕습니다. Actix Web은 이 모델을 활용하여 비동기 논블로킹 I/O를 제공하며, 작은 메모리 풋프린트와 뛰어난 성능을 자랑합니다.
Actix Web을 시작하기 위해서는 먼저 프로젝트를 생성해야 합니다. 이를 위해 cargo 도구를 사용할 수 있습니다.
cargo new actix-app --bin
생성된 디렉터리로 이동하여 Cargo.toml 파일을 열고, actix-web 및 actix-rt 의존성을 추가합니다.
[dependencies]
actix-web = "4"
actix-rt = "2"
이제 main.rs 파일에서 간단한 웹 애플리케이션을 작성해보겠습니다.
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello World!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(hello))
.bind("127.0.0.1:8000")?
.run()
.await
}
이 코드에서는 hello 핸들러 함수를 정의하고, 이를 App 인스턴스에 서비스로 등록합니다. HttpServer를 생성하여 127.0.0.1:8000 주소에 바인딩하고 실행합니다.
cargo run을 통해 애플리케이션을 실행하면, http://127.0.0.1:8000/에 접속하여 "Hello World!"를 확인할 수 있습니다.
이처럼 Actix Web은 간결하고 직관적인 API를 제공하여 빠르게 웹 애플리케이션을 구축할 수 있습니다. 하지만 실제 프로덕션 레벨의 애플리케이션을 만들기 위해서는 더 많은 기능이 필요합니다.
Actix Web 핵심 기능 살펴보기
이번에는 Actix Web의 주요 기능들을 살펴보겠습니다.
라우팅
Actix Web에서는 /, /users, /users/{id} 등의 경로를 정의하고, 각 경로에 대한 핸들러를 등록할 수 있습니다. 이를 통해 HTTP 메서드에 따라 다른 핸들러를 호출할 수 있습니다.
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello World!")
}
#[post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
// 사용자 생성 로직
HttpResponse::Ok().json(user.into_inner())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
.service(create_user)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
이 예제에서는 GET /에 대한 hello 핸들러와 POST /users에 대한 create_user 핸들러를 정의하고 있습니다. create_user에서는 web::Json 추출기를 사용하여 요청 본문의 JSON 데이터를 User 구조체로 deserialize 합니다.
미들웨어
미들웨어(Middleware)는 요청과 응답을 가로채어 특정 작업을 수행할 수 있는 핸들러입니다. Actix Web에서는 로깅, 인증, 압축, CORS 등 다양한 미들웨어를 제공합니다.
use actix_web::{middleware, web, App, HttpServer};
async fn logging(req: actix_web::dev::ServiceRequest, _: actix_web::dev::ServiceResponse) {
println!("{} {}", req.method(), req.path());
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::Logger::default())
.service(
web::resource("/")
.route(web::get().to(|| async { "Hello World!" }))
.route(web::method(http::Method::GET).to(logging)),
)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
이 예제에서는 middleware::Logger를 사용하여 요청에 대한 로깅을 수행하고 있습니다. 또한 / 경로에 대한 GET 요청에 대해 logging 미들웨어를 등록하여 요청 메서드와 경로를 출력합니다.
정적 파일 서빙
웹 애플리케이션에서는 HTML, CSS, JavaScript 등의 정적 파일을 제공해야 합니다. Actix Web에서는 actix-files 미들웨어를 사용하여 이를 쉽게 구현할 수 있습니다.
use actix_files as fs;
use actix_web::{web, App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
web::scope("/static")
.service(fs::Files::new("/static", "static/").show_files_listing()),
)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
이 예제에서는 /static 경로 아래에 있는 정적 파일을 static/ 디렉터리에서 제공합니다. show_files_listing() 메서드를 호출하면 디렉터리 목록도 확인할 수 있습니다.
JSON 처리
현대 웹 애플리케이션에서는 JSON이 주요 데이터 교환 형식으로 사용됩니다. Actix Web에서는 serde 라이브러리와 연동하여 JSON 직렬화/비직렬화를 쉽게 처리할 수 있습니다.
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
id: u64,
name: String,
email: String,
}
#[post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
HttpResponse::Ok().json(user.into_inner())
}
#[get("/users/{id}")]
async fn get_user(path: web::Path<u64>) -> impl Responder {
let id = path.into_inner();
let user = User {
id,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
};
HttpResponse::Ok().json(user)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(create_user)
.service(get_user)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
이 예제에서는 User 구조체를 정의하고, serde의 Serialize와 Deserialize 트레이트를 구현했습니다. create_user 핸들러에서는 요청 본문의 JSON 데이터를 User 구조체로 deserialize하고, get_user 핸들러에서는 User 인스턴스를 JSON으로 serialize하여 응답합니다.
데이터베이스 연동
웹 애플리케이션에서는 데이터베이스와의 연동이 필수적입니다. Actix Web에서는 ORM(Object-Relational Mapping) 라이브러리인 SQLx를 활용할 수 있습니다. SQLx는 Rust에서 비동기 PostgreSQL, MySQL, SQLite 클라이언트 라이브러리입니다.
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use sqlx::postgres::PgPoolOptions;
use std::env;
#[get("/users")]
async fn get_users(pool: web::Data<sqlx::PgPool>) -> impl Responder {
let users = sqlx::query!("SELECT id, name, email FROM users")
.fetch_all(&**pool)
.await
.unwrap();
HttpResponse::Ok().json(users)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await
.unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.service(get_users)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
이 예제에서는 PostgreSQL 데이터베이스와 연동하여 users 테이블의 데이터를 조회하는 get_users 핸들러를 정의했습니다. PgPoolOptions를 사용하여 데이터베이스 연결 풀을 생성하고, web::Data를 통해 애플리케이션 데이터로 등록합니다. 핸들러에서는 SQL 쿼리를 작성하고 fetch_all 메서드를 호출하여 결과를 가져옵니다.
SQLx는 비동기 코드 작성을 지원하며, Rust의 강력한 타입 시스템과 결합하여 타입 안전성을 보장합니다. 또한 커넥션 풀링을 통해 효율적인 데이터베이스 연결 관리가 가능합니다.
웹소켓
많은 웹 애플리케이션에서는 서버와 클라이언트 간의 실시간 양방향 통신이 필요합니다. Actix Web에서는 웹소켓을 통해 이를 구현할 수 있습니다.
use actix::*;
use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web_actors::ws;
struct ServerWebSocket {
heartbeat: Addr<Heartbeater>,
}
impl Actor for ServerWebSocket {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for ServerWebSocket {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Text(text)) => ctx.text(text),
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
Ok(ws::Message::Close(reason)) => ctx.close(reason),
_ => (),
}
}
}
async fn ws_route(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
ws::start(ServerWebSocket::default(), &req, stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(web::resource("/ws/").route(web::get().to(ws_route))))
.bind("127.0.0.1:8000")?
.run()
.await
}
이 예제에서는 ServerWebSocket 액터를 정의하고, 웹소켓 핸들러인 ws_route를 통해 연결을 처리합니다. ws::start 함수를 호출하여 웹소켓 연결을 시작하고, StreamHandler 트레이트를 구현하여 메시지를 처리합니다.
웹소켓을 사용하면 채팅, 실시간 알림, 협업 도구, 게임 등 다양한 애플리케이션을 구축할 수 있습니다.
Rust 웹 프로그래밍 도구 및 패키지
Rust 웹 프로그래밍을 위한 도구와 패키지 생태계도 살펴보겠습니다.
도구
- Cargo: Rust의 공식 패키지 매니저로, 의존성 관리, 빌드, 테스트, 문서화 등의 기능을 제공합니다.
- Rust Analyzer: Rust IDE 환경을 위한 언어 서버로, 자동 완성, 리팩토링, 진단 등의 기능을 제공합니다.
- Trunk: Vite와 유사한 웹 애플리케이션 번들러로, Rust로 작성된 웹 애플리케이션을 위한 개발 서버와 빌드 파이프라인을 제공합니다.
- wasm-pack: WebAssembly 패키지를 생성하고 NPM에 배포하기 위한 도구입니다.
- cargo-watch: 파일 변경을 감지하고 자동으로 재빌드하는 도구입니다.
패키지
- actix-web: 우리가 살펴본 것처럼, 강력하고 고성능의 웹 프레임워크입니다.
- sqlx: 비동기 PostgreSQL, MySQL, SQLite 클라이언트 라이브러리입니다.
- serde: 직렬화/비직렬화를 위한 프레임워크입니다.
- tokio: 비동기 프로그래밍을 위한 런타임입니다.
- reqwest: isomorphic HTTP 클라이언트로, 클라이언트와 서버 모두에서 사용 가능합니다.
- warp: 간단하고 모듈식인 또 다른 웹 프레임워크입니다.
- yew: 모던 Rust + WebAssembly 프레임워크로, React와 유사한 컨셉을 제공합니다.
이 외에도 다양한 라이브러리와 프레임워크가 있으며, crates.io에서 확인할 수 있습니다.
Rust 웹 프로그래밍의 장점
마지막으로 Rust를 웹 프로그래밍에 사용하는 장점을 정리해보겠습니다.
- 메모리 안전성: Rust의 소유권 개념과 빌림 규칙을 통해 메모리 안전성을 보장합니다. 이를 통해 보안성이 높은 웹 애플리케이션을 구축할 수 있습니다.
- 성능: Rust는 시스템 프로그래밍 언어 수준의 높은 성능을 제공합니다. 이는 높은 트래픽을 처리해야 하는 웹 애플리케이션에 적합합니다.
- 동시성 모델: Rust의 동시성 모델은 웹 서버와 같이 많은 요청을 동시에 처리해야 하는 환경에 매우 적합합니다.
- 패키지 관리 및 생태계: cargo와 crates.io를 통해 패키지 관리와 의존성 관리가 용이하며, 다양한 라이브러리와 도구가 제공됩니다.
- 크로스 플랫폼 지원: Rust는 크로스 플랫폼 컴파일을 지원하므로, 다양한 환경에서 웹 애플리케이션을 배포할 수 있습니다.
- WebAssembly 지원: Rust는 WebAssembly를 공식적으로 지원하므로, 브라우저에서 실행되는 웹 애플리케이션 개발에 적합합니다.
이처럼 Rust는 웹 프로그래밍에 매력적인 언어입니다. 안전성과 성능을 모두 잡을 수 있으며, 강력한 생태계와 크로스 플랫폼 지원을 제공합니다. Rust로 웹 애플리케이션을 개발해보시기 바랍니다.
참고 자료
[1] The Rust Programming Language Book - Rust 공식 문서
[2] Actix Web - Actix Web 프레임워크 공식 사이트
[3] SQLx - SQLx 라이브러리 GitHub 저장소
[4] Rust Web Programming: The Awesome Alternative - Rust 웹 프로그래밍 블로그 포스트
[5] Why You Should Use Rust for Web Development - Rust 웹 개발 장점에 대한 블로그 포스트
[6] Awesome Rust Web - Rust 웹 관련 라이브러리 및 리소스 모음
한 고대 문서 이야기
여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오
gospel79.tistory.com
유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법
올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을
stock79.tistory.com
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."