Rust를 공부해야지 공부해야지 하다가 매우 오랜 시간 동안, 결국은 아무것도 안하고 있길래, 그냥 무작정 만들어보기로 했다.
그리고 아래 글을 읽은 이유도 있다.
http://egloos.zum.com/agile/v/5664879
대충 언어를 새로 배울 때 세가지 원칙을 기준으로 공부를 한다는 점이다.
1. 이 언어로 뭘 만들지를 생각하면서 튜토리얼을 본다.
2. 공부할 때, 표준 라이브러리 코드를 읽는다.
3. 다른 사람이작성한 코드에 내가 필요한 기능을 추가한다.
위 글을 보고 영감을 받아서, 그냥 무작정 지루한 챕터들을 읽기 보다는 바로 무언가를 만들어 보는게 낫지 않나 싶다.
뭘 만들면 좋을까 하다가, 간단한 웹 서버를 만들어 보기로 했다. 어차피 간단한 웹서버는 정말 간단하고, 기능들을 추가하고자 한다면 계속해서 추가할 수 있으니 말이다.
Rust 개발환경 구축은 다음 링크 글에서 확인해볼 수 있다.
https://eine.tistory.com/entry/Rust-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95
일단 개발 환경은, 내가 주로 윈도우 PC를 사용하므로 아래와 같을 예정이다.
개발환경
- Windows 10
- rustc 1.44.0-nightly (f509b26a7 2020-03-18)
- cargo 1.44.0-nightly (7019b3ed3 2020-03-17)
일단 github 레포지토리와 개발할 코드 디렉토리를 만들어보자.
그리고 repository를 clone할 디렉토리에서 WSL win bash를 켠 뒤, git clone을 해준다.
Rust 프로젝트 생성
이제 rust project를 카고로 만들어줘야 하는데, cargo new rust_web_server --bin 이라는 명령어로 프로젝트 디렉토리를 만들 수 있다.
근데 안에 디렉토리가 또 생겨버렸다. 이미 README.md와 .git이 있는 디렉토리므로 저 명령어 말고, 다른 명령어를 써야 하는 것 같다.
cargo init --bin이라고 치니 되는 것 같다.
디렉토리 구조를 보니 바람직하게 만들어진 듯 하다.
일단 VScode로 해당 디렉토리를 열어서 Hello world가 잘 동작하는지 확인해보자.
기본적으로 소스코드에 hello world프로그램이 작성되어 있다.
아래 터미널을 열어서 cargo build와 cargo run을 쳐 보니, Hello, world!가 잘 되었다.
네트워크 기능부터
일단 웹 서버이니 TCP 소캣을 열 수 있어야 한다. Rust에서 소캣 프로그래밍을 어떻게 하는지를 한번 알아보자.
소캣 프로그래밍
https://doc.rust-lang.org/std/net/struct.TcpListener.html
TcpListener라는 녀석이 있다. 일단 소캣을 하나 열어서 Listening하는 예제 코드가 있으니 한번 테스트 해보자.
use std::net::{TcpListener, TcpStream};
fn handle_client(stream: TcpStream) {
println!("Connection!")
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:80")?;
// accept connections and process them serially
for stream in listener.incoming() {
handle_client(stream?);
}
Ok(())
}
접속이 되면 stdout에 Connection!이라고 출력만 된다.
일단 여기까지 하고 initial commit을 한번 해 놓겠다.
이제 Tcp로 데이터를 전송을 해보고싶은데, Docs랑 예시 코드를 좀 보겠다.
https://riptutorial.com/rust/example/4404/a-simple-tcp-client-and-server-application--echo
https://doc.rust-lang.org/std/net/struct.TcpStream.html
stream.write라고 하면 될 것 같은데, 사이에 &는 뭐고 ?는 또 뭔지 모르겠다.
찾아보니, &는 burrow라고 해서 인자를 넘겨줄 때, C++의 Reference variable 처럼 Call by reference로 넘기도록 하는 것이고, ?는 Result<T>를 리턴하는 값을 unwrap을 자동적으로 해주도록 하는 매크로이다.
웹 브라우저로 요청을 보내보니, Response는 안오긴 하는데 Connection이라고 찍히긴 한다.
그래서 대충 static한 HTTP Response를 찍도록 조금 변경해 보았다.
use std::net::{TcpListener, TcpStream};
use std::io::{Write};
fn handle_client(mut stream: TcpStream) {
println!("Connection!");
let data = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
stream.write(data.as_bytes());
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:80")?;
// accept connections and process them serially
for stream in listener.incoming() {
handle_client(stream?);
}
Ok(())
}
그래서 브라우저로 접속을 해볼려고 했는데, 여러번 하면 한번정도 성공하는듯?? 왜 되었다 안되었다 하는지 모르겠다.
성공하는 확률은 10%정도로 뭔가 Tcp 연결을 잘 못 짠거같긴 하다.
그리고 빌드할때마다 다음 warning이 뜬다.
에러 핸들링을 제대로 안해서 그런 것 같다.
아래와 같이 코드를 바꾸니 warning은 안뜨긴 한다만, 여전히 응답은 제대로 안된다.
use std::net::{TcpListener, TcpStream};
use std::io::{Write};
fn handle_client(mut stream: TcpStream) {
println!("Connection!");
let data = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
stream.write(data.as_bytes()).expect("Response error");
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:80")?;
// accept connections and process them serially
for stream in listener.incoming() {
handle_client(stream?);
}
Ok(())
}
근데 약간 이상한것은, Ubuntu 16.04에서 Firefox로 접속하니, hello world!가 잘 찍힌다.
왜 Windows 10에서 크롬으로 접속을 하면 제대로 안되는 것일까..?
나중에 확인해보니, tcpstream 마지막에 flush를 해 주니, chrome이든 firefox든 둘 다 잘 돌아간다.
대충 아래와 같은 코드로 고쳐보았다. /로 요청할때와 그 외의 경로로 요청할때의 응답이 다르다.
from_utf8_lossy는 u8 byte stream 값을 readable한 문자열로 변경해주는 함수이다. lossy는 유효하지 않은 UTF-8 배열을 만났을때 어떻게 처리할 것인가에 대한 내용이다. 유효하지 않은 배열 값은 U+FFFD Replacement character라는 값으로 바꾸게 된다.
use std::net::{TcpListener, TcpStream};
use std::io::{Write};
use std::io::prelude::*;
fn handle_client(mut stream: TcpStream) {
//get request and dump
let mut request = [0; 1024];
stream.read(&mut request).unwrap();
println!("Received HTTP Request...\n{}", String::from_utf8_lossy(&request[..]));
let response;
if request.starts_with(b"GET / HTTP/1.1\r\n") {
//send hello world response
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
} else {
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 4\r\n\r\nNope\r\n";
}
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
fn main() -> std::io::Result<()> {
let server = "127.0.0.1:1024";
let listener = TcpListener::bind(server)?;
// accept connections and process them serially
println!("Server is running on {}...", server);
for stream in listener.incoming() {
handle_client(stream?);
}
Ok(())
}
'개발 & CS 지식 > Rust' 카테고리의 다른 글
[Rust로 PS하기] 백준 2748번 피보나치 수 2번 풀이 (0) | 2020.05.10 |
---|---|
[Rust로 PS하기] 백준1620번 나는야 포켓몬 마스터 이다솜 풀이 (0) | 2020.04.27 |