Rust 언어로 작성한 간단한 웹서버
목차
서론 #
이번 글에서는 Rust의 멀티 프로세싱를 이용하여 간단한 정적 웹서버를 구현하고자 합니다.
Modules #
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::fs::File;
prelude
→ https://doc.rust-lang.org/std/prelude/index.html
TcpListener
→ https://doc.rust-lang.org/std/net/struct.TcpListener.html
bind
를 통해 소켓 주소에 TcpListener
를 생성한 후, 들어오는 TCP 연결을 listen합니다. 이는 accept
호출하거나 incoming
에 의해 반환된 Incoming
iterator를 통해 수락할 수 있습니다.
TcpStream → https://doc.rust-lang.org/std/net/struct.TcpStream.html
로컬 및 remote 소켓 간의 스트림입니다. TcpListener
에서 연결을 accept
하거나 원격 호스트에 connect
하여 TcpStream
을 생성한 후, 읽기 및 쓰기를 통해 데이터를 전송할 수 있습니다.
File → https://doc.rust-lang.org/std/fs/struct.File.html
index.html과 같은 파일을 여는 기능입니다.
main #
fn main() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream).unwrap();
}
}
listener.incoming()
은 이 listener에서 수신되는 연결에 대한 iterator를 반환합니다. 반복자의 각 stream
은 Result<TcpStream>
이 됩니다.
handle_connection #
fn handle_connection(mut stream: TcpStream) {
let mut buffer: [u8; 512] = [0; 512];
stream.read(&mut buffer).unwrap();
const GET: &[u8; 16] = b"GET / HTTP/1.1\r\n";
// If client sends GET request
if buffer.starts_with(GET) {
let mut file = File::open("index.html").unwrap();
let mut contents = String::new();
// open index.html and put its content to string
file.read_to_string(&mut contents).unwrap();
// create response as HTTP protocol
let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
contents.len(),
contents
);
// write response as bytes
stream.write(response.as_bytes()).unwrap();
// To ensure all buffered content reached its destination, flush this stream
stream.flush().unwrap();
} else {
const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND";
let contents = "Cannot Found its Method or Resources";
let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}",
NOT_FOUND,
contents.len(),
contents.to_string()
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
}
Multi threading Web Server #
위에서 언급한 Rust를 사용한 간단한 웹 서버는 프로덕션 환경에서 사용할 수 없습니다. 왜냐하면, 동시에 여러 요청을 처리할 수 없기 때문입니다. 위의 서버는 한 번에 하나의 요청만 처리할 수 있습니다. 예를 들어, 하나의 요청이 5초 이상 걸릴 경우, 다음 요청은 이전 요청이 처리될 때까지 기다려야 합니다.
fn handle_slow_connection(mut stream: TcpStream) {
// --snip--
thread::sleep(Duration::from_millis(5000));
stream.write(response.as_bytes()).unwrap();
// --snip--
}
이를 방지하기 위해, 요청을 동시에 처리할 수 있는 여러 방법이 있습니다. 이 섹션에서는 멀티 스레딩 방법을 사용하여 구현할 것입니다.
Thread for reqeust #
use std::thread;
fn main() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
thread::spawn(|| {
handle_connection(stream).unwrap();
});
}
}
listener가 새 요청을 받을 때마다, thread::spawn()
은 새 스레드를 생성합니다. 이것은 서버가 동시에 여러 요청을 수락할 수 있게 합니다. 그러나 이 버전의 문제는 새 요청이 있을 때마다 새 스레드를 생성한다는 것입니다. 1000개의 요청이 있을 때, 수천 개의 새 스레드가 생성될 것입니다. 이는 ThreadPool
이라는 용어를 사용하여 방지할 수 있습니다. 나중에 기회가 될 때 ThreadPool
에 대해 다룰 것입니다.