概要
rosettacodeにあったコードをほとんどそのまま書くので、自分で作ったというのではなくrosettacodeにあるコードが
どうなっているかということをゆっくり見ていく。また、Rustについては初心者のため、もっと理解したらその都度更新していく。
rosettacodeとは何なのかというと以下のとおり。私は知らなかった。
「Rosetta Code」は、「いろいろなタスク(例題)を、様々なプログラミング言語で書いたらどうなるか」を比較できるプログラミング言語の名句集サイトです。
https://www.softantenna.com/wp/webservice/rosetta-code/
やってみる(というか見てみる)
全体のコード
まず全体のコードが以下。これを分解してみていく。
use std::net::{Shutdown, TcpListener};
use std::thread;
use std::io::Write;
const RESPONSE: &'static [u8] = b"HTTP/1.1 200 OK\r
Content-Type: text/html; charset=UTF-8\r\n\r
<!DOCTYPE html><html><head><title>Bye-bye baby bye-bye</title>
<style>body { background-color: #111 }
h1 { font-size:4cm; text-align: center; color: black;
text-shadow: 0 0 2mm red}</style></head>
<body><h1>Goodbye, world!</h1></body></html>\r";
fn main() {
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
thread::spawn(move || {
let mut stream = stream.unwrap();
match stream.write(RESPONSE) {
Ok(_) => println!("Response sent!"),
Err(e) => println!("Failed sending response: {}!", e),
}
stream.shutdown(Shutdown::Write).unwrap();
});
}
}
宣言等
use std::net::{Shutdown, TcpListener};
use std::thread;
use std::io::Write;
const RESPONSE: &'static [u8] = b"HTTP/1.1 200 OK\r
Content-Type: text/html; charset=UTF-8\r\n\r
<!DOCTYPE html><html><head><title>Bye-bye baby bye-bye</title>
<style>body { background-color: #111 }
h1 { font-size:4cm; text-align: center; color: black;
text-shadow: 0 0 2mm red}</style></head>
<body><h1>Goodbye, world!</h1></body></html>\r";
しょっぱなではあるが、ここはこういうものなのかな、で済ませる。
定数Response
の型が&'static [u8]
として宣言されている理由は、この後見ていくがResponseを返す際にwrite()
を使用する。このwrite()
が引数としてu8
のデータを期待するため&'static [u8]
となっている。
'static
についてはライフタイムの記事を読んでみて、ライフサイクル全体で有効にしておきたい変数を定義する際に必要な記述なんだろうと解釈した。
TCPリスナーの作成
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
bind()
は引数に指定されたアドレスをもとに新しいTcpListerを作成する。
また、Result<TcpListener>
型でデータが返ってくるためunwrap()
をして内包されるTcpListerを取得する。
今回はbind()
のアドレスは1つしか指定していないが、
以下のように指定することでポート80への接続で失敗した際にポート443に向いたTcpListerを作成することもできる。
let addrs = [
SocketAddr::from(([127, 0, 0, 1], 80)),
SocketAddr::from(([127, 0, 0, 1], 443)),
];
let listener = TcpListener::bind(&addrs[..]).unwrap();
参考
TcpLister - bind
unwrap()について
Listenをする部分
for stream in listener.incoming() { ... }
incoming()
メソッドを利用するとIncoming構造体を取得することができる。
これは対象のTcpListerに対する接続要求を待ち続けるイテレータであるためループ処理を利用してListenをさせる。
参考
Incoming
thread作成
thread::spawn(move || { ... })
thread::spawn
はスレッド生成の標準ライブラリである。スレッドを動かす際の基本的な形はthread::spawn(|| { ... })
であり、渡された無名関数が別スレッドで動作する。
今回は生成したスレッドにおいて変数stream
を使用したい。そのためには、所有権の問題にぶち当たってしまうためstream
のコピーの所有権を渡るようにするために、基本構文にmoveキーワードを付与する。
レスポンスをする
let mut stream = stream.unwrap();
match stream.write(RESPONSE) {
Ok(_) => println!("Response sent!"),
Err(e) => println!("Failed sending response: {}!", e),}
stream.shutdown(Shutdown::Write).unwrap();
Incomingオブジェクトをループした際に取得したstream
の型はResult<TcpStream>
である。したがってunwrap()
をすることでTcpStream型のデータを取得することができる。
コネクションを通じたデータのやり取りはread()
もしくはwrite()
を通して行わる。今回はレスポンスだけを行うのでwrite()
を使用する。write()
の結果が成功した場合はOk
が呼ばれ失敗した場合はErr
が呼ばれる。
参考
matchの使い方
また、個々の接続は閉じられる必要があるためshutdown
メソッドを利用して閉じる。今回はwriteしか行っていないので引数としてShutdown::Write
を指定するが、readを行う場合はShutdown::Read
、両方を使用した場合はShutdown::Both
を使用する。ところで最後にshutdown
メソッドの戻り値に対してunwrap()
をする理由はわからなかった。どこで使われるわけでもない値なので、必要ないと思ったが…、ご存知の方がいればアドバイスをいただきたく思います。
結果
ちゃんとブラウザで結果を表示させることができた。
また、コンソール側でもResponse sent!
が表示された(なぜか3回出た…)。
(2018/5/7追記 3回出た理由はわかりませんが、2回表示される理由は以下のコメントにあります。)
もっと理解を深めて言葉を正確に使った解説をしていけるようになりたい。
Compiling hello_cargo v0.1.0 (file:///xxx/rust/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.91 secs
Running `target\debug\hello_cargo.exe`
Response sent!
Response sent!
Response sent!