메인 콘텐츠로 이동하기
  1. Posts/

Rust 언어로 작성한 간단한 웹서버

·401 자

서론 #

이번 글에서는 Rust의 멀티 프로세싱를 이용하여 간단한 정적 웹서버를 구현하고자 합니다.

Modules #

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::fs::File;

preludehttps://doc.rust-lang.org/std/prelude/index.html
TcpListenerhttps://doc.rust-lang.org/std/net/struct.TcpListener.html

bind를 통해 소켓 주소에 TcpListener를 생성한 후, 들어오는 TCP 연결을 listen합니다. 이는 accept 호출하거나 incoming에 의해 반환된 Incoming iterator를 통해 수락할 수 있습니다.

TcpStreamhttps://doc.rust-lang.org/std/net/struct.TcpStream.html

로컬 및 remote 소켓 간의 스트림입니다. TcpListener에서 연결을 accept하거나 원격 호스트에 connect하여 TcpStream을 생성한 후, 읽기쓰기를 통해 데이터를 전송할 수 있습니다.

Filehttps://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를 반환합니다. 반복자의 각 streamResult<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에 대해 다룰 것입니다.