やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustのサーバ(シングルスレッド)

 HTTP、TCP

成果物

参考

シングルスレッドWebサーバ構築

cargo new hello --bin

src/main.rs

use std::net::TcpListener;
fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        println!("接続が確立しました!");
    }
}
cargo run

 ブラウザのロケール欄に127.0.0.1:7878を入力してアクセスする。

 ターミナルに接続が確立しました!と出力される。

 サーバはCtrl+Cキーなどで強制終了する。(強制終了の方法はターミナル次第)

リクエストを読み取る

use std::io::prelude::*;
use std::net::TcpStream;
use std::net::TcpListener;
fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        handle_connection(stream);
    }
}
fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();
    println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
}
$ cargo run
   Compiling hello v0.1.0 (/tmp/work/Rust.HttpServer.20190709080759/src/1/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 8.38s
     Running `/tmp/work/Rust.HttpServer.20190709080759/src/1/hello/target/debug/hello`
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/72.0.3626.121 Chrome/72.0.3626.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8

HTTPリクエス

 HTTPプロトコルは以下のようなテキストフォーマットである。

Method Request-URI HTTP-Version CRLF
headers CRLF
message-body

 たとえば以下。

HTTP/1.1 200 OK\r\n\r\n

 これをサーバが返すコードを以下に示す。

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();
    let response = "HTTP/1.1 200 OK\r\n\r\n";
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

 ターミナルには何もでない。ブラウザもエラーがでなくなり、真っ白になる。

HTMLを返す

 サーバが返却するhello.htmlファイルをルート直下に配置する。

hello.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Hello!</h1>
    <p>Hi from Rust</p>
  </body>
</html>
use std::io::prelude::*;
use std::net::TcpStream;
use std::net::TcpListener;
use std::fs::File;
fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        handle_connection(stream);
    }
}
fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();

    let mut file = File::open("hello.html").unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}
cargo run

 ブラウザで127.0.0.1:7878にアクセスすると、hello.htmlの内容が表示される。

リクエストにバリデーションをかける

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();

    let get = b"GET / HTTP/1.1\r\n";
    if buffer.starts_with(get) {
        let mut file = File::open("hello.html").unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);
        stream.write(response.as_bytes()).unwrap();
        stream.flush().unwrap();
    } else {
        // 何か他の要求
        // some other request
    }
}

 404ページを実装する。

else {
    let status_line = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
    let mut file = File::open("404.html").unwrap();
    let mut contents = String::new();

    file.read_to_string(&mut contents).unwrap();

    let response = format!("{}{}", status_line, contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

404.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <!-- ああ! -->
    <h1>Oops!</h1>
    <!-- すいません。要求しているものが理解できません -->
    <p>Sorry, I don't know what you're asking for.</p>
  </body>
</html>
cargo run

 ブラウザで127.0.0.1:7878/fooなど存在しないURLを指定すると、404.htmlを返す。

リファクタリング

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();

    let get = b"GET / HTTP/1.1\r\n";
    let (status_line, filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
    };

    let mut file = File::open(filename).unwrap();
    let mut contents = String::new();
    file.read_to_string(&mut contents).unwrap();
    let response = format!("{}{}", status_line, contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで