Rust를 공부해야지 공부해야지 하다가 매우 오랜 시간 동안, 결국은 아무것도 안하고 있길래, 그냥 무작정 만들어보기로 했다.

 

그리고 아래 글을 읽은 이유도 있다.

http://egloos.zum.com/agile/v/5664879

 

프로그래밍 언어 배우기의 달인

미육군에서는 9년간 약 4천만불을 들여 개발한 지뢰 탐지 시스템(Handheld Standoff Mine Detection System, HSTAMIDS 나중에 PSS-14로 개명)을 취소하려고 하고 있었습니다. 프로토타입의 실 테스트에서 지뢰 탐

egloos.zum.com

대충 언어를 새로 배울 때 세가지 원칙을 기준으로 공부를 한다는 점이다.

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

 

Rust 개발 환경 구축(Windows10에서 Sublime Text 3 / VS code)

느닷없이 Rust를 배우고 싶어져서 Rust 개발환경을 한번 구축해보도록 하겠습니다. 사실 지금 글을 쓰는 이 시점에는 Rust를 공식적으로 지원하는 IDE나 그런 녀석들은 잘 없는데, 그래도 제일 많이

eine.tistory.com

 

일단 개발 환경은, 내가 주로 윈도우 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

 

std::net::TcpListener - Rust

pub struct TcpListener(_); A TCP socket server, listening for connections. After creating a TcpListener by binding it to a socket address, it listens for incoming TCP connections. These can be accepted by calling accept or by iterating over the Incoming it

doc.rust-lang.org

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

 

Rust - A simple TCP client and server application: echo | rust Tutorial

rust documentation: A simple TCP client and server application: echo

riptutorial.com

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

 

std::net::TcpStream - Rust

A TCP stream between a local and a remote socket. After creating a TcpStream by either connecting to a remote host or accepting a connection on a TcpListener, data can be transmitted by reading and writing to it. The connection will be closed when the valu

doc.rust-lang.org

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(())
}

 

+ Recent posts